Add contest to course (#126)
This commit is contained in:
parent
72eada0a4e
commit
3d67fb274e
22 changed files with 1258 additions and 433 deletions
|
@ -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>]
|
||||
|
|
13
templates/course/add_contest.html
Normal file
13
templates/course/add_contest.html
Normal 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 %}
|
35
templates/course/contest_list.html
Normal file
35
templates/course/contest_list.html
Normal 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 %}
|
|
@ -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'] %}
|
||||
|
|
92
templates/course/edit_contest.html
Normal file
92
templates/course/edit_contest.html
Normal 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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue