Add attendance form page
- Author
- Maarten Vangeneugden
- Date
- Aug. 24, 2018, 4:09 a.m.
- Hash
- 3571706c34739595c87d79818f1a79b60f751911
- Parent
- 1968b80302e3b9f87ccdaa1b9cebd8fe6eb8b09e
- Modified files
- courses/templates/courses/attendance.djhtml
- courses/templates/courses/course.djhtml
- courses/urls.py
- courses/views.py
- static/css/tables.scss
courses/templates/courses/attendance.djhtml ¶
27 additions and 0 deletions.
View changes Hide changes
+ |
1 |
{% load static %} |
+ |
2 |
{% load i18n %} |
+ |
3 |
{% load humanize %} |
+ |
4 |
{% load joeni_org %} |
+ |
5 |
|
+ |
6 |
{% block title %} |
+ |
7 |
{{ course.name }} | {{ block.super }} |
+ |
8 |
{% endblock %} |
+ |
9 |
|
+ |
10 |
{% block main %} |
+ |
11 |
<h1>{{ course.name }} - {% trans "Student attendance form" %}</h1> |
+ |
12 |
<table class="attendance"> |
+ |
13 |
<tr> |
+ |
14 |
<th>{% trans "Student number" %}</th> |
+ |
15 |
<th>{% trans "Student name" %}</th> |
+ |
16 |
<th>{% trans "Signature" %}</th> |
+ |
17 |
</tr> |
+ |
18 |
{% for student in student_list %} |
+ |
19 |
<tr> |
+ |
20 |
<td>{{ student.student.number }}</td> |
+ |
21 |
<td>{{ student.student }}</td> |
+ |
22 |
<td></td> |
+ |
23 |
</tr> |
+ |
24 |
{% endfor %} |
+ |
25 |
</table> |
+ |
26 |
{% endblock main %} |
+ |
27 |
courses/templates/courses/course.djhtml ¶
3 additions and 0 deletions.
View changes Hide changes
1 |
1 |
{% load static %} |
2 |
2 |
{% load i18n %} |
3 |
3 |
{% load humanize %} |
4 |
4 |
{% load joeni_org %} |
5 |
5 |
|
6 |
6 |
{% block title %} |
7 |
7 |
{{ course.name }} | {{ block.super }} |
8 |
8 |
{% endblock %} |
9 |
9 |
|
10 |
10 |
{% block main %} |
11 |
11 |
<h1>{{ course.name }}</h1> |
12 |
12 |
|
13 |
13 |
<h2 id="{% trans "announcements" %}">{% trans "Announcements" %}</h2> |
14 |
14 |
<div class="flex-container"> |
15 |
15 |
{% for announcement in announcements %} |
16 |
16 |
<div style="border-color: #{{ course.color }};" class="flex-item"> |
17 |
17 |
<h3 id="{{ announcement.title|slugify }}">{{ announcement.title }}</h3> |
18 |
18 |
<time datetime="{{ announcement.posted|date:'c' }}"> |
19 |
19 |
{% trans "Posted:" %} {{ announcement.posted|naturaltime }} |
20 |
20 |
</time> |
21 |
21 |
<p>{{ announcement.text|org }}</p> |
22 |
22 |
</div> |
23 |
23 |
{% empty %} |
24 |
24 |
{% trans "No announcements have been made for this course." %} |
25 |
25 |
{% endfor %} |
26 |
26 |
</div> |
27 |
27 |
|
28 |
28 |
|
29 |
29 |
<h2 id="{% trans "course-items" %}">{% trans "Course items" %}</h2> |
30 |
30 |
<div class="flex-container"> |
31 |
31 |
{% for item in course_items %} |
32 |
32 |
<div style="border-color: #{{ course.color }};" class="flex-item"> |
33 |
33 |
<a href="{{ item.file.url }}" download>{{ item.canonical }}</a><br /> |
34 |
34 |
<time datetime="{{ item.timestamp|date:'c' }}"> |
35 |
35 |
{% trans "Posted:" %} {{ item.timestamp|naturaltime }} |
36 |
36 |
</time> |
37 |
37 |
{% if item.note %} |
38 |
38 |
<p>{{ item.note|org }}</p> |
39 |
39 |
{% endif %} |
40 |
40 |
</div> |
41 |
41 |
{% empty %} |
42 |
42 |
{% trans "There is no course material available for this course." %} |
43 |
43 |
{% endfor %} |
44 |
44 |
</div> |
45 |
45 |
|
46 |
46 |
|
47 |
47 |
<h2 id="{% trans "assignments" %}">{% trans "Assignments" %}</h2> |
48 |
48 |
<div class="flex-container"> |
49 |
49 |
{% for assignment in assignments %} |
50 |
50 |
<div style="border-color: #{{ course.color }};" class="flex-item"> |
51 |
51 |
<h3 id="{{ assignment.title|slugify }}">{{ assignment.title }}</h3> |
52 |
52 |
<time datetime="{{ assignment.posted|date:'c' }}"> |
53 |
53 |
{% trans "Posted:" %} {{ assignment.posted|date:"DATE_FORMAT" }} {# {{ assignment.posted|naturaltime }}#} |
54 |
54 |
</time><br /> |
55 |
55 |
<time datetime="{{ assignment.deadline|date:'c' }}"> |
56 |
56 |
{% trans "Deadline:" %} {{ assignment.deadline|date:"DATE_FORMAT" }} |
57 |
57 |
</time> |
58 |
58 |
|
59 |
59 |
{% if assignment.information %} |
60 |
60 |
<p>{{ assignment.information|org }}</p> |
61 |
61 |
{% endif %} |
62 |
62 |
{#{% trans "Posted" %}: {{ assignment.posted|date:"DATE_FORMAT" }}#} |
63 |
63 |
{% if assignment.digital_task %} |
64 |
64 |
<h4>{% trans "Your uploads" %}</h4> |
65 |
65 |
{% for upload in uploads %} |
66 |
66 |
{% if upload.assignment == assignment %} |
67 |
67 |
{% trans "Uploaded:"%} {{ upload.upload_time|date:"SHORT_DATETIME_FORMAT" }}<br /> |
68 |
68 |
{% if upload.comment %} |
69 |
69 |
<p>{{ upload.comment }}</p> |
70 |
70 |
{% endif %} |
71 |
71 |
{% if upload.upload_time > assignment.deadline %} |
72 |
72 |
<strong>{% trans "This upload is overdue." %}</strong> |
73 |
73 |
{% endif %} |
74 |
74 |
{% endif %} |
75 |
75 |
{% empty %} |
76 |
76 |
{% with now as current_time %} |
77 |
77 |
{% if current_time > assignment.deadline %} |
78 |
78 |
<p> |
79 |
79 |
<strong> |
80 |
80 |
{% blocktrans %} |
81 |
81 |
You have failed to provide an upload for this |
82 |
82 |
assignment. Any future uploads will be automatically |
83 |
83 |
overdue. |
84 |
84 |
{% endblocktrans %} |
85 |
85 |
</strong> |
86 |
86 |
</p> |
87 |
87 |
{% else %} |
88 |
88 |
<p> |
89 |
89 |
{% blocktrans %} |
90 |
90 |
You haven't uploaded anything for this assignment |
91 |
91 |
yet. |
92 |
92 |
{% endblocktrans %} |
93 |
93 |
</p> |
94 |
94 |
{% endif %} |
95 |
95 |
{% endwith %} |
96 |
96 |
{% endfor %} |
97 |
97 |
<h5>{% trans "Upload a task" %}</h5> |
98 |
98 |
<form enctype="multipart/form-data" action="{% url "courses-course-index" course.slug_name %}" method="post"> |
99 |
99 |
{% csrf_token %} {# todo i don't think that's necessary here #} |
100 |
100 |
{% include "joeni/form.djhtml" with form=upload_form %} |
101 |
101 |
{# TODO Enable submit button when this works #} |
102 |
102 |
<!--<input type="submit" value="{% trans "Submit" %}" />--> |
103 |
103 |
</form> |
104 |
104 |
{% endif %} |
105 |
105 |
</div> |
106 |
106 |
{% endfor %} |
107 |
107 |
</div> |
108 |
108 |
<h1 id="{% trans "management" %}">{% trans "Course management" %}</h1> |
109 |
109 |
<style> |
110 |
110 |
a.btn { |
111 |
111 |
color: #{{ course.color }}; |
112 |
112 |
border-color: #{{ course.color }}; |
113 |
113 |
} |
114 |
114 |
a.btn:hover { |
115 |
115 |
color: white; |
116 |
116 |
background-color: #{{ course.color }}; |
117 |
117 |
} |
118 |
118 |
</style> |
119 |
119 |
<a class="btn" href="{% url "courses-eci" course_slug=course.slug_name %}"> |
120 |
120 |
{% trans "Edit course page" %} |
121 |
121 |
</a> |
122 |
122 |
<a class="btn" href="{% url "courses-results" course_slug=course.slug_name %}"> |
123 |
123 |
{% trans "Student results" %} |
124 |
124 |
</a> |
125 |
125 |
<h2 id="{% trans "students" %}">{% trans "Students" %}</h2> |
+ |
126 |
{% trans "Student attendance form" %} |
+ |
127 |
</a> |
+ |
128 |
<h2 id="{% trans "students" %}">{% trans "Students" %}</h2> |
126 |
129 |
<table> |
127 |
130 |
<tr> |
128 |
131 |
<th>{% trans "Student name" %}</th> |
129 |
132 |
<th>{% trans "Student number" %}</th> |
130 |
133 |
<th>{% trans "First result" %}</th> |
131 |
134 |
<th>{% trans "Second result" %}</th> |
132 |
135 |
<th>{% trans "Decision" %}</th> |
133 |
136 |
</tr> |
134 |
137 |
{% for student in student_list %} |
135 |
138 |
<tr> |
136 |
139 |
<td>{{ student.student }}</td> |
137 |
140 |
<td>{{ student.student.number }}</td> |
138 |
141 |
<td>{{ student.first_score|default_if_none:"-" }}</td> |
139 |
142 |
<td>{{ student.second_score|default_if_none:"-" }}</td> |
140 |
143 |
<td>{% with result=student.result %} |
141 |
144 |
{% if result == "CRED" or result == "VRST" or result == "TLRD" or result == "ITLR"%} |
142 |
145 |
<span style="color:green;"> |
143 |
146 |
{% elif result == "FAIL" %} |
144 |
147 |
<span style="color:red;"> |
145 |
148 |
{% elif result == "BDRG" %} |
146 |
149 |
<span style="background-color:red; color:white;"> |
147 |
150 |
{% elif result == "STOP" %} |
148 |
151 |
<span style="color:black;"> |
149 |
152 |
{% endif %} |
150 |
153 |
{{ student.get_result_display }}</span> |
151 |
154 |
{% endwith %}</td> |
152 |
155 |
</tr> |
153 |
156 |
{% endfor %} |
154 |
157 |
</table> |
155 |
158 |
{% endblock main %} |
156 |
159 |
courses/urls.py ¶
1 addition and 0 deletions.
View changes Hide changes
1 |
1 |
from . import views |
2 |
2 |
from django.utils.translation import ugettext_lazy as _ |
3 |
3 |
|
4 |
4 |
urlpatterns = [ |
5 |
5 |
path('', views.index, name='courses-index'), |
6 |
6 |
path('<slug:course_slug>', views.course, name='courses-course-index'), |
7 |
7 |
path(_('<slug:course_slug>/<int:assignment_id>/upload'), views.upload, name='courses-upload'), |
8 |
8 |
path(_('<slug:course_slug>/new-item'), views.new_item, name='courses-new-item'), |
9 |
9 |
path(_('<slug:course_slug>/groups'), views.groups, name='courses-groups'), |
10 |
10 |
path(_('<slug:course_slug>/edit-course-items'), views.edit_course_items, name='courses-eci'), |
11 |
11 |
path(_('<slug:course_slug>/course-results'), views.course_results, name='courses-results'), |
12 |
12 |
] |
+ |
13 |
] |
13 |
14 |
courses/views.py ¶
9 additions and 0 deletions.
View changes Hide changes
1 |
1 |
import datetime |
2 |
2 |
from django.urls import reverse # Why? |
3 |
3 |
from django.utils.translation import gettext as _ |
4 |
4 |
from .models import * |
5 |
5 |
from .forms import * |
6 |
6 |
import administration |
7 |
7 |
from django.contrib.auth.decorators import login_required |
8 |
8 |
from joeni.constants import current_academic_year |
9 |
9 |
|
10 |
10 |
@login_required |
11 |
11 |
def index(request): |
12 |
12 |
""" Starting page regarding the courses. This serves two specific groups: |
13 |
13 |
- Students: Displays all courses that this student has in his/her curriculum |
14 |
14 |
for this academic year. Requires the curriculum to be accepted. |
15 |
15 |
- Staff: Displays all courses in which the staff member is part of the |
16 |
16 |
educating team, or is otherwise related to the course. |
17 |
17 |
Users who are not logged in will be sent to the login page. |
18 |
18 |
""" |
19 |
19 |
template = "courses/index.djhtml" |
20 |
20 |
courses = request.user.user_data.current_courses() |
21 |
21 |
|
22 |
22 |
context = { |
23 |
23 |
'courses': courses, |
24 |
24 |
} |
25 |
25 |
|
26 |
26 |
return render(request, template, context) |
27 |
27 |
|
28 |
28 |
@login_required |
29 |
29 |
def course(request, course_slug): |
30 |
30 |
template = "courses/course.djhtml" |
31 |
31 |
course_ = Course.objects.get(slug_name=course_slug) |
32 |
32 |
|
33 |
33 |
if request.method == 'POST': |
34 |
34 |
uploads = UploadFormSet(request.POST, request.FILES, prefix='uploads') |
35 |
35 |
if uploads.is_valid(): |
36 |
36 |
uploads.save(commit=False) |
37 |
37 |
for new_upload in uploads.new_objects: |
38 |
38 |
new_upload.course = course_ |
39 |
39 |
uploads.save() |
40 |
40 |
request.method = 'GET' |
41 |
41 |
return course(request, course_slug) |
42 |
42 |
else: |
43 |
43 |
uploads = UploadFormSet( |
44 |
44 |
queryset=Upload.objects.filter(course=course_).filter(student=request.user), |
45 |
45 |
prefix="uploads", |
46 |
46 |
) |
47 |
47 |
# Check if user can see this page |
48 |
48 |
if request.user.user_data.is_student: |
49 |
49 |
if course_ not in request.user.user_data.current_courses(): |
50 |
50 |
""" I'm currently just redirecting to the index page, but maybe it's |
51 |
51 |
just as good to make an announcement that this course cannot be |
52 |
52 |
used by this user. """ |
53 |
53 |
return index(request) |
54 |
54 |
|
55 |
55 |
context = { |
56 |
56 |
'course': course_, |
57 |
57 |
'main_color': course_.color, |
58 |
58 |
'announcements': Announcement.objects.filter(course=course_), |
59 |
59 |
'assignments': Assignment.objects.filter(course=course_), |
60 |
60 |
'course_items': CourseItem.objects.filter(course=course_), |
61 |
61 |
'study-groups': StudyGroup.objects.filter(course=course_), |
62 |
62 |
#'uploads': Upload.objects.filter(course=course).filter(student=request.user) |
63 |
63 |
} |
64 |
64 |
if request.user.user_data.is_student: |
65 |
65 |
context['upload_form'] = UploadForm() |
66 |
66 |
#else: |
67 |
67 |
context['student_list'] = administration.models.CourseResult.objects.filter(course_programme__course=course_)#.filter(year=current_academic_year()), |
68 |
68 |
# FIXME I disabled the year filter for testing purposes. Enable in deployment. |
69 |
69 |
|
70 |
70 |
return render(request, template, context) |
71 |
71 |
|
72 |
72 |
# TODO: Find a way to see if it's possible to require some permissions and to |
73 |
73 |
# put them in a decorator |
74 |
74 |
#@permission_required |
75 |
75 |
@login_required |
76 |
76 |
def new_item(request, course_slug): |
77 |
77 |
template = "courses/new_item.djhtml" |
78 |
78 |
course = Course.objects.get(slug_name=course_slug) |
79 |
79 |
|
80 |
80 |
if request.user.user_data.is_student or request.user not in course.course_team: |
81 |
81 |
# Students can't add new items. Redirect to index |
82 |
82 |
# Also redirect people who are not part of the course team |
83 |
83 |
redirect('courses-index') |
84 |
84 |
# Now able to assume user is allowed to add items to this course |
85 |
85 |
|
86 |
86 |
context = { |
87 |
87 |
'course': course, |
88 |
88 |
'announcements': Announcement.objects.filter(course=course), |
89 |
89 |
'assignments': Assignment.objects.filter(course=course), |
90 |
90 |
'course-items': CourseItem.objects.filter(course=course), |
91 |
91 |
'study-groups': StudyGroup.objects.filter(course=course), |
92 |
92 |
'uploads': Upload.objects.filter(course=course) |
93 |
93 |
} |
94 |
94 |
return render(request, template, context) |
95 |
95 |
|
96 |
96 |
def upload(request): |
97 |
97 |
return render(request, template, context) |
98 |
98 |
|
99 |
99 |
#@create_context # TODO |
100 |
100 |
@login_required |
101 |
101 |
def course_results(request, course_slug): |
102 |
102 |
template = "courses/course_results.djhtml" |
103 |
103 |
context = dict() |
104 |
104 |
course_ = Course.objects.get(slug_name=course_slug) |
105 |
105 |
if request.method == 'POST': |
106 |
106 |
course_results = CourseResultFormSet(request.POST, prefix='course_results') |
107 |
107 |
if course_results.is_valid(): |
108 |
108 |
course_results.save() |
109 |
109 |
request.method = 'GET' |
110 |
110 |
return course(request, course_slug) |
111 |
111 |
else: |
112 |
112 |
course_results = CourseResultFormSet( |
113 |
113 |
#queryset=administration.models.CourseResult.objects.filter(course_programme__course=course_).filter(year=current_academic_year()), |
114 |
114 |
queryset=administration.models.CourseResult.objects.filter(course_programme__course=course_), |
115 |
115 |
prefix="course_results", |
116 |
116 |
) |
117 |
117 |
|
118 |
118 |
context['course'] = course_ |
119 |
119 |
context['course_results'] = course_results |
120 |
120 |
return render(request, template, context) |
121 |
121 |
|
122 |
122 |
@login_required |
+ |
123 |
def course_attendance(request, course_slug): |
+ |
124 |
template = "courses/attendance.djhtml" |
+ |
125 |
context = dict() |
+ |
126 |
course = Course.objects.get(slug_name=course_slug) |
+ |
127 |
context['course'] = course |
+ |
128 |
context['student_list'] = administration.models.CourseResult.objects.filter(course_programme__course=course)#.filter(year=current_academic_year()), |
+ |
129 |
return render(request, template, context) |
+ |
130 |
|
+ |
131 |
@login_required |
123 |
132 |
def edit_course_items(request, course_slug): |
124 |
133 |
# TODO Only allow people on the course team to this page! |
125 |
134 |
template = "courses/edit_course_items.djhtml" |
126 |
135 |
context = dict() |
127 |
136 |
course_ = Course.objects.get(slug_name=course_slug) |
128 |
137 |
if request.method == 'POST': |
129 |
138 |
assignments = AssignmentFormSet(request.POST, prefix='assignments') |
130 |
139 |
announcements = AnnouncementFormSet(request.POST, prefix='announcements') |
131 |
140 |
course_items = CourseItemFormSet(request.POST, request.FILES, prefix='course_items') |
132 |
141 |
#course_results = CourseResultFormSet(request.POST, prefix='course_results') |
133 |
142 |
if assignments.is_valid() and announcements.is_valid() and course_items.is_valid(): #and course_results.is_valid(): |
134 |
143 |
assignments.save(commit=False) |
135 |
144 |
announcements.save(commit=False) |
136 |
145 |
course_items.save(commit=False) |
137 |
146 |
#course_results.save(commit=False) |
138 |
147 |
for new_assignment in assignments.new_objects: |
139 |
148 |
new_assignment.course = course_ |
140 |
149 |
for new_announcement in announcements.new_objects: |
141 |
150 |
new_announcement.course = course_ |
142 |
151 |
for new_course_item in course_items.new_objects: |
143 |
152 |
new_course_item.course = course_ |
144 |
153 |
#for new_course_result in course_results.new_objects: |
145 |
154 |
#new_coutse_result.course = course_ |
146 |
155 |
assignments.save() |
147 |
156 |
announcements.save() |
148 |
157 |
course_items.save() |
149 |
158 |
#course_results.save() |
150 |
159 |
request.method == 'GET' |
151 |
160 |
return course(request, course_slug) |
152 |
161 |
else: |
153 |
162 |
assignments = AssignmentFormSet( |
154 |
163 |
queryset=Assignment.objects.filter(course=course_), |
155 |
164 |
prefix="assignments", |
156 |
165 |
) |
157 |
166 |
announcements = AnnouncementFormSet( |
158 |
167 |
queryset=Announcement.objects.filter(course=course_), |
159 |
168 |
prefix="announcements", |
160 |
169 |
) |
161 |
170 |
course_items = CourseItemFormSet( |
162 |
171 |
queryset=CourseItem.objects.filter(course=course_), |
163 |
172 |
prefix="course_items", |
164 |
173 |
) |
165 |
174 |
#course_results = CourseResultFormSet( |
166 |
175 |
#queryset=administration.models.CourseResult.objects.filter(course_programme__course=course_).filter(year=current_academic_year()), |
167 |
176 |
#prefix="course_results", |
168 |
177 |
#) |
169 |
178 |
|
170 |
179 |
context['course'] = course_ |
171 |
180 |
context['assignments'] = assignments |
172 |
181 |
context['announcements'] = announcements |
173 |
182 |
context['course_items'] = course_items |
174 |
183 |
#context['course_results'] = course_results |
175 |
184 |
return render(request, template, context) |
176 |
185 |
|
177 |
186 |
|
178 |
187 |
|
179 |
188 |
@login_required |
180 |
189 |
def groups(request): |
181 |
190 |
pass |
182 |
191 |
|
183 |
192 |
def fiche(request, course_slug): |
184 |
193 |
"""Displays the fiche for the given course. Includes information about all |
185 |
194 |
course programs.""" |
186 |
195 |
template = "courses/fiche.djhtml" |
187 |
196 |
context = dict() |
188 |
197 |
course = Course.objects.get(slug_name=course_slug) |
189 |
198 |
static/css/tables.scss ¶
12 additions and 0 deletions.
View changes Hide changes
1 |
1 |
/*border-width: 0.5em; |
2 |
2 |
border-style: solid; |
3 |
3 |
border-radius: 1em; |
4 |
4 |
padding: 0.8em; |
5 |
5 |
margin: 1em;*/ |
6 |
6 |
border-width: 0.4em; |
7 |
7 |
border-style: solid; |
8 |
8 |
border-radius: 0.4em 1em 1em 1em; |
9 |
9 |
padding: 0; |
10 |
10 |
padding-left: 1em; |
11 |
11 |
color: inherit; |
12 |
12 |
padding-top: 0.5em; |
13 |
13 |
&:hover { |
14 |
14 |
text-decoration: none; |
15 |
15 |
} |
16 |
16 |
span.number { |
17 |
17 |
color: white; |
18 |
18 |
padding: 0.5em; |
19 |
19 |
margin-left: -1em; |
20 |
20 |
top: -1em; |
21 |
21 |
font-weight: bold; |
22 |
22 |
border-bottom-right-radius: 1em; |
23 |
23 |
} |
24 |
24 |
div.timetable-under { |
25 |
25 |
padding-left: 0.5em; |
26 |
26 |
margin-top: 0.5em; |
27 |
27 |
margin-bottom: 0.3em; |
28 |
28 |
margin-left: -1em; |
29 |
29 |
} |
30 |
30 |
} |
31 |
31 |
|
+ |
32 |
table.attendance { |
+ |
33 |
border: 1px solid black; |
+ |
34 |
border-collapse: collapse; |
+ |
35 |
td { |
+ |
36 |
padding: 1em; |
+ |
37 |
border: 1px solid black; |
+ |
38 |
} |
+ |
39 |
th { |
+ |
40 |
padding: 1em; |
+ |
41 |
} |
+ |
42 |
} |
+ |
43 |