Add contest to course (#126)

This commit is contained in:
Phuoc Anh Kha Le 2024-10-02 15:06:33 -05:00 committed by GitHub
parent 72eada0a4e
commit 3d67fb274e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 1258 additions and 433 deletions

View file

@ -87,11 +87,16 @@
</div>
{% endif %}
{% if editable_organizations or is_clonable %}
{% if editable_organizations or is_clonable or editable_course %}
<div style="display: flex; gap: 0.5em;">
{% for org in editable_organizations %}
<span> [<a href="{{ url('organization_contest_edit', org.id , org.slug , contest.key) }}">{{ _('Edit in') }} {{org.slug}}</a>]</span>
{% endfor %}
{% if editable_course %}
<span>
[<a href="{{url('edit_course_contest', editable_course.slug, contest.key)}}"}}>{{ _('Edit in') }} {{editable_course.slug}}</a>]
</span>
{% endif %}
{% if is_clonable %}
<span>
[<a href="{{url('contest_clone', contest.key)}}"}}>{{_('Clone')}}</a>]

View file

@ -0,0 +1,13 @@
{% extends "course/base.html" %}
{% block js_media %}
{{ form.media.js }}
{% endblock %}
{% block two_col_media %}
{{ form.media.css }}
{% endblock %}
{% block middle_content %}
{% include "organization/form.html" %}
{% endblock %}

View file

@ -0,0 +1,35 @@
{% extends "course/base.html" %}
{% block two_col_media %}
<style>
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
</style>
{% endblock %}
{% block middle_content %}
<div class="container">
{% if course_contests %}
{% for course_contest in course_contests %}
<div class="course-contest-card">
<div>
<h5><a href="{{url('contest_view', course_contest.contest.key)}}">{{ loop.index }}. {{ course_contest.contest.name }}</a></h5>
<p><strong>{{_("Order")}}:</strong> {{ course_contest.order }}</p>
<p><strong>{{_("Points")}}:</strong> {{ course_contest.points }}</p>
<p><strong>{{_("Start")}}:</strong> {{ course_contest.contest.start_time|date(_("H:i d/m/Y")) }}</p>
<p><strong>{{_("End")}}:</strong> {{ course_contest.contest.end_time|date(_("H:i d/m/Y")) }}</p>
</div>
<a href="{{url('edit_course_contest', course.slug, course_contest.contest.key)}}" class="button">{{ _('Edit') }}</a>
</div>
{% endfor %}
{% else %}
<p style="text-align: center;">{{_("No contests available")}}.</p>
{% endif %}
<a href="{{url('add_course_contest', course.slug)}}">
<button>{{ _('Add') }}</button>
</a>
</div>
{% endblock %}

View file

@ -1,36 +1,118 @@
{% extends "course/base.html" %}
{% block two_col_media %}
<style type="text/css">
.contest-name {
font-weight: bold;
font-size: 1.1em;
}
.contest-details {
font-size: 0.9em;
}
</style>
{% endblock %}
{% block js_media %}
<script type="text/javascript">
$(document).ready(function () {
$('.time-remaining').each(function () {
count_down($(this));
});
});
</script>
{% endblock %}
{% block middle_content %}
<center><h2>{{title}}</h2></center>
<h3 class="course-content-title">{{_("About")}}</h3>
<div>
{{ course.about|markdown|reference|str|safe }}
</div>
<h3 class="course-content-title">{{_("Lessons")}}</h3>
<ul class="lesson-list">
{% for lesson in lessons %}
<a href="{{url('course_lesson_detail', course.slug, lesson.id)}}">
{% set progress = lesson_progress[lesson.id] %}
<li>
<div class="lesson-title">
{{ lesson.title }}
<div class="lesson-points">
{% if progress['total_points'] %}
{{(progress['achieved_points'] / progress['total_points'] * lesson.points) | floatformat(1)}} / {{lesson.points}}
{% else %}
0 / {{lesson.points}}
{% endif %}
{% if lessons %}
<br>
<h3 class="course-content-title">{{_("Lessons")}}</h3>
<ul class="lesson-list">
{% for lesson in lessons %}
<a href="{{url('course_lesson_detail', course.slug, lesson.id)}}">
{% set progress = lesson_progress[lesson.id] %}
<li>
<div class="lesson-title">
{{ lesson.title }}
<div class="lesson-points">
{% if progress['total_points'] %}
{{(progress['achieved_points'] / progress['total_points'] * lesson.points) | floatformat(1)}} / {{lesson.points}}
{% else %}
0 / {{lesson.points}}
{% endif %}
</div>
</div>
</div>
<div class="progress-container">
<div class="progress-bar" style="width: {{progress['percentage']}}%;">{{progress['percentage']|floatformat(0)}}%</div>
</div>
</li>
</a>
{% endfor %}
</ul>
<div class="progress-container">
<div class="progress-bar" style="width: {{progress['percentage']}}%;">{{progress['percentage']|floatformat(0)}}%</div>
</div>
</li>
</a>
{% endfor %}
</ul>
{% endif %}
{% if course_contests %}
<br>
<h3 class="course-content-title">{{_("Contests")}}</h3>
<br>
<table class="table striped">
<thead>
<tr>
<th>{{_("Name")}}</th>
<th>{{_("Start")}}</th>
<th>{{_("End")}}</th>
<th>{{_("Length")}}</th>
<th>{{_("Score")}}</th>
</tr>
</thead>
<tbody>
{% for course_contest in course_contests %}
{% set contest = course_contest.contest %}
{% set progress = contest_progress[course_contest.id] %}
<tr>
<td>
<a href="{{ url('contest_view', contest.key) }}" class="contest-name">{{ contest.name }}</a>
</td>
<td>
{{ contest.start_time|date(_("H:i d/m/Y")) }}
<div class="contest-details">
{% if contest.time_before_start %}
<span class="time">{{ _('Starting in %(countdown)s.', countdown=contest.start_time|as_countdown) }}</span>
{% endif %}
</div>
</td>
<td>
{{ contest.end_time|date(_("H:i d/m/Y"))}}
<div class="contest-details">
{% if contest.time_before_end %}
<span class="time">{% trans countdown=contest.end_time|as_countdown %}Ends in {{ countdown }}{% endtrans %}</span>
{% endif %}
</div>
</td>
<td>
{% if contest.time_limit %}
{% trans time_limit=contest.time_limit|timedelta('localized-no-seconds') %}{{ time_limit }}{% endtrans %}
{% else %}
{% trans duration=contest.contest_window_length|timedelta('localized-no-seconds') %}{{ duration }}{% endtrans %}
{% endif %}
</td>
<td>
{% if progress['total_points'] %}
{{ (progress['achieved_points'] / progress['total_points'] * course_contest.points) | floatformat(1) }} / {{ course_contest.points }}
{% else %}
0 / {{ course_contest.points }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<br>
<h3 class="course-content-title">
{% set total_progress = lesson_progress['total'] %}
{% set achieved_points = total_progress['achieved_points'] %}
{% set total_points = total_progress['total_points'] %}
{% set percentage = total_progress['percentage'] %}

View file

@ -0,0 +1,92 @@
{% extends "course/base.html" %}
{% block js_media %}
{{ form.media.js }}
{% endblock %}
{% block two_col_media %}
{{ form.media.css }}
<style>
#org-field-wrapper-order,
#org-field-wrapper-points,
#org-field-wrapper-scoreboard_visibility,
#org-field-wrapper-points_precision,
#org-field-wrapper-start_time,
#org-field-wrapper-end_time,
#org-field-wrapper-time_limit,
#org-field-wrapper-format_name,
#org-field-wrapper-freeze_after,
#org-field-wrapper-rate_limit {
display: inline-flex;
}
.problems-problem {
max-width: 60vh;
}
input[type=number] {
width: 5em;
}
.middle-content {
z-index: 1;
}
#three-col-container {
overflow: auto;
}
</style>
{% endblock %}
{% block middle_content %}
<center><h2>{{content_title}}</h2></center>
<form action="" method="post">
{% csrf_token %}
{% if form.errors or problems_form.errors %}
<div class="alert alert-danger alert-dismissable">
<a href="#" class="close">x</a>
{{_("Please fix below errors")}}
</div>
{% endif %}
{% for field in form %}
{% if not field.is_hidden %}
<div style="margin-bottom: 1em;">
{{ field.errors }}
<label for="{{field.id_for_label }}"><b>{{ field.label }}{% if field.field.required %}<span class="red"> * </span>{% endif %}:</b> </label>
<div class="org-field-wrapper" id="org-field-wrapper-{{field.html_name }}">
{{ field }}
</div>
{% if field.help_text %}
<small class="org-help-text"><i class="fa fa-exclamation-circle"></i> {{ field.help_text|safe }}</small>
{% endif %}
</div>
{% endif %}
{% endfor %}
<hr><br>
{{ problems_form.management_form }}
<i>{{_('If you run out of rows, click Save')}}</i>
<table class="table">
<thead>
<tr>
{% for field in problems_form[0] %}
{% if not field.is_hidden %}
<th class="problems-{{field.name}}">
{{field.label}}
</th>
{% endif %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for form in problems_form %}
<tr>
{% for field in form %}
<td class="problems-{{field.name}}" title="
{{ field.help_text|safe if field.help_text }}"
style="{{ 'display:none' if field.is_hidden }}"
>{{field}}<div class="red">{{field.errors}}</div></td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<button type="submit">{{ _('Save') }}</button>
</form>
{% endblock %}

View file

@ -75,58 +75,84 @@
{% block middle_content %}
<center><h2>{{content_title}}</h2></center>
{% set lessons = course.lessons.order_by('order') %}
{{_("Sort by")}}:
<select id="sortSelect">
<option value="username">{{_("Username")}}</option>
<option value="total">{{_("Score")}}</option>
</select>
<input type="text" id="search-input" placeholder="{{_('Search')}}" autofocus>
<table class="table striped" id="users-table">
<thead>
<tr>
<th>{{_('Student')}}</th>
{% if grades|length > 0 %}
<div style="overflow-x: auto; margin-top: 1em">
<table class="table striped" id="users-table">
<thead>
<tr>
<th>{{_('Student')}}</th>
<th>{{_('Total')}}</th>
{% for lesson in lessons %}
<th class="points">
<th class="points" title="{{lesson.title}}">
<a href="{{url('course_grades_lesson', course.slug, lesson.id)}}">
{{ lesson.title }}
L{{ loop.index0 + 1 }}
<div class="point-denominator">{{lesson.points}}</div>
</a>
</th>
{% endfor %}
{% endif %}
<th>{{_('Total')}}</th>
</tr>
</thead>
<tbody>
{% for student, grade in grades.items() %}
<tr>
<td class="user-name">
<div>
{{link_user(student)}}
</div>
<div>
{{student.first_name}}
</div>
</td>
{% for lesson in lessons %}
{% set val = grade.get(lesson.id) %}
<td class="partial-score">
<a href="{{url('course_lesson_detail', course.slug, lesson.id)}}?user={{student.username}}">
{% if val and val['total_points'] %}
{{ (val['achieved_points'] / val['total_points'] * lesson.points) | floatformat(0) }}
{% else %}
0
{% endif %}
{% for course_contest in course_contests %}
<th class="points" title="{{course_contest.contest.name}}">
<a href="{{url('contest_ranking', course_contest.contest.key)}}">
C{{ loop.index0 + 1 }}
<div class="point-denominator">{{course_contest.points}}</div>
</a>
</td>
</th>
{% endfor %}
<td style="font-weight: bold">
{{ grade['total']['percentage'] | floatformat(0) }}%
</td>
</tr>
{% endfor %}
</tbody>
</table>
</thead>
<tbody>
{% for student in grade_total.keys() %}
{% set grade_lessons_student = grade_lessons.get(student) %}
{% set grade_contests_student = grade_contests.get(student) %}
{% set grade_total_student = grade_total.get(student) %}
<tr>
<td class="user-name">
<div>
{{link_user(student)}}
</div>
<div>
{{student.first_name}}
</div>
</td>
<td style="font-weight: bold">
{% if grade_total_student %}
{{ grade_total_student['percentage'] | floatformat(0) }}%
{% else %}
0
{% endif %}
</td>
{% for lesson in lessons %}
{% set val = grade_lessons_student.get(lesson.id) %}
<td class="partial-score">
<a href="{{url('course_lesson_detail', course.slug, lesson.id)}}?user={{student.username}}">
{% if val and val['total_points'] %}
{{ (val['achieved_points'] / val['total_points'] * lesson.points) | floatformat(0) }}
{% else %}
0
{% endif %}
</a>
</td>
{% endfor %}
{% for course_contest in course_contests %}
{% set val = grade_contests_student.get(course_contest.id) %}
<td class="partial-score">
<a href="{{url('contest_ranking', course_contest.contest.key)}}#!{{student.username}}">
{% if val and val['total_points'] %}
{{ (val['achieved_points'] / val['total_points'] * course_contest.points) | floatformat(0) }}
{% else %}
0
{% endif %}
</a>
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View file

@ -2,6 +2,7 @@
{{ make_tab_item('home', 'fa fa-home', course.get_absolute_url(), _('Home')) }}
{% if is_editable %}
{{ make_tab_item('edit_lesson', 'fa fa-edit', url('edit_course_lessons', course.slug), _('Edit lessons')) }}
{{ make_tab_item('contests', 'fa fa-ranking-star', url('course_contest_list', course.slug), _('Contests')) }}
{{ make_tab_item('grades', 'fa fa-check-square', url('course_grades', course.slug), _('Grades')) }}
{% endif %}
{% if perms.judge.change_course %}