stage 2: message on save
[edumed.git] / stage2 / views.py
1 # -*- coding: utf-8 -*-
2 from django.contrib import messages
3 from django.contrib.auth.decorators import login_required
4 from django.core.urlresolvers import reverse
5 from django.http import Http404
6 from django.http.response import HttpResponseRedirect, HttpResponse, HttpResponseForbidden
7 from django.shortcuts import get_object_or_404, render
8 from django.utils import timezone
9 from django.utils.cache import patch_cache_control
10 from django.views.decorators.cache import never_cache
11 from unidecode import unidecode
12
13 from stage2.forms import AttachmentForm, MarkForm, AssignmentFieldForm
14 from stage2.models import Participant, Assignment, Answer, Attachment, Mark
15
16
17 def all_assignments(participant, sent_forms):
18     assignments = Assignment.objects.all()
19     if sent_forms:
20         sent_assignment, field_forms, attachment_forms = sent_forms
21     else:
22         sent_assignment = field_forms = attachment_forms = None
23     for assignment in assignments:
24         assignment.answer, created = Answer.objects.get_or_create(participant=participant, assignment=assignment)
25         if assignment == sent_assignment:
26             assignment.field_forms = field_forms
27         else:
28             assignment.field_forms = [
29                 AssignmentFieldForm(label=label, field_no=i, options=options, answer=assignment.answer)
30                 for i, (label, options) in enumerate(assignment.field_descriptions, 1)]
31         # in theory, if assignment == sent_assignment, it should be copied like field_forms,
32         # but somehow it doesn't work as expected
33         assignment.attachment_forms = [
34             (AttachmentForm(assignment=assignment, file_no=i, label=label, options=options),
35              assignment.answer.attachment_set.filter(file_no=i).first() if assignment.answer else None)
36             for i, (label, options) in enumerate(assignment.file_descriptions, 1)]
37     return assignments
38
39
40 @never_cache
41 def participant_view(request, participant_id, key):
42     participant = get_object_or_404(Participant, id=participant_id)
43     if not participant.check(key):
44         raise Http404
45     if request.POST:
46         # ugly :/
47         assignment_id = None
48         for post_key, value in request.POST.iteritems():
49             if post_key.endswith('assignment_id'):
50                 assignment_id = int(value)
51         assert assignment_id
52
53         assignment = get_object_or_404(Assignment, id=assignment_id)
54         now = timezone.now()
55         if assignment.deadline < now:
56             return HttpResponseForbidden('Not Allowed')
57         attachments_valid, attachment_forms = get_attachment_forms(assignment, participant, request)
58         fields_valid, field_forms = get_field_forms(assignment, participant, request)
59         # tutaj w zasadzie powinno być też sprawdzenie attachments_valid, ale to powoduje,
60         # że jeśli jakiś plik nie został wysłany, to traktujemy to jako błąd, a tego nie chcemy.
61         # trzeba by znaleźć sensowny sposób odrózniania błędnego pliku od braku pliku.
62         # na szczęście pliki walidujemy też javascriptem, więc jakoś ujdzie
63         if fields_valid:
64             messages.info(request, u'Dane zostały poprawnie zapisane na serwerze')
65             return HttpResponseRedirect(reverse('stage2_participant', args=(participant_id, key)))
66         else:
67             sent_forms = (assignment, field_forms, attachment_forms)
68     else:
69         sent_forms = None
70     response = render(request, 'stage2/participant.html', {
71         'participant': participant,
72         'assignments': all_assignments(participant, sent_forms)})
73     # not needed in Django 1.8
74     patch_cache_control(response, no_cache=True, no_store=True, must_revalidate=True)
75     return response
76
77
78 def get_attachment_forms(assignment, participant, request):
79     all_valid = True
80     attachment_forms = []
81     for i, (label, options) in enumerate(assignment.file_descriptions, 1):
82         answer, created = Answer.objects.get_or_create(participant=participant, assignment=assignment)
83         attachment, created = Attachment.objects.get_or_create(answer=answer, file_no=i)
84         form = AttachmentForm(
85             data=request.POST, files=request.FILES,
86             assignment=assignment, file_no=i, label=label, instance=attachment, options=options)
87         if form.is_valid():
88             form.save()
89         else:
90             all_valid = False
91         attachment_forms.append(form)
92     return all_valid, attachment_forms
93
94
95 def get_field_forms(assignment, participant, request):
96     all_valid = True
97     field_forms = []
98     for i, (label, options) in enumerate(assignment.field_descriptions, 1):
99         answer = Answer.objects.get(participant=participant, assignment=assignment)
100         form = AssignmentFieldForm(data=request.POST, label=label, field_no=i, options=options, answer=answer)
101         if form.is_valid():
102             form.save()
103         else:
104             all_valid = False
105         field_forms.append(form)
106     return all_valid, field_forms
107
108
109 def attachment_download(attachment):
110     response = HttpResponse(content_type='application/force-download')
111     response.write(attachment.file.read())
112     # workaround to this: https://code.djangoproject.com/ticket/20889
113     response['Content-Disposition'] = 'attachment; filename="%s"' % unidecode(attachment.filename().replace('\n', ' '))
114     response['Content-Length'] = response.tell()
115     return response
116
117
118 def get_file(request, assignment_id, file_no, participant_id, key):
119     """We want to serve submitted files back to participants, but also validate their keys,
120        so static files are not good"""
121     participant = get_object_or_404(Participant, id=participant_id)
122     if not participant.check(key):
123         raise Http404
124     assignment = get_object_or_404(Assignment, id=assignment_id)
125     answer = get_object_or_404(Answer, participant=participant, assignment=assignment)
126     attachment = get_object_or_404(Attachment, answer=answer, file_no=file_no)
127     return attachment_download(attachment)
128
129
130 @login_required
131 def assignment_list(request):
132     expert = request.user
133     assignments = expert.stage2_assignments.all()
134     if not assignments:
135         return HttpResponseForbidden('Not allowed')
136     for assignment in assignments:
137         assignment.marked_count = assignment.available_answers(expert, marked=True).count()
138         assignment.to_mark_count = assignment.available_answers(expert).count()
139         assignment.supervisor = expert in assignment.supervisors.all()
140         assignment.arbiter_count = assignment.needing_arbiter().count()
141
142     non_empty_assignments = [ass for ass in assignments if ass.marked_count > 0 or ass.to_mark_count > 0]
143     if len(non_empty_assignments) == 1 and non_empty_assignments[0].to_mark_count > 0:
144         return HttpResponseRedirect(reverse('stage2_answer_list', args=[non_empty_assignments[0].id]))
145     return render(request, 'stage2/assignment_list.html', {'assignments': assignments})
146
147
148 def available_answers(assignment, expert, sent_forms=None, marked=False):
149     if marked:
150         answers = assignment.available_answers(expert, marked=True)
151     else:
152         answers = assignment.available_answers(expert).order_by('participant__last_name')
153     answers = answers.prefetch_related('attachment_set')
154     if sent_forms:
155         sent_answer_id, mark_forms = sent_forms
156     else:
157         sent_answer_id = mark_forms = None
158     for answer in answers:
159         attachments = answer.attachment_set.all()
160         attachments_by_file_no = {attachment.file_no: attachment for attachment in attachments}
161         answer.attachments = [
162             (desc, attachments_by_file_no.get(i))
163             for (i, (desc, ext)) in enumerate(assignment.file_descriptions, 1)]
164         if answer.id == sent_answer_id:
165             answer.forms = mark_forms
166         else:
167             answer.forms = []
168             for criterion in assignment.markcriterion_set.all():
169                 answer.forms.append(MarkForm(
170                     answer=answer,
171                     criterion=criterion,
172                     instance=answer.mark_set.filter(expert=expert, criterion=criterion).first(),
173                     prefix='mark%s-%s' % (answer.id, criterion.id)))
174     return answers
175
176
177 @login_required
178 def answer_list(request, assignment_id, marked=False):
179     assignment = get_object_or_404(Assignment, id=assignment_id)
180     expert = request.user
181     if expert not in assignment.experts.all():
182         return HttpResponseForbidden('Not allowed')
183     if request.POST:
184         # ugly :/
185         answer_id = None
186         for post_key, value in request.POST.iteritems():
187             if post_key.endswith('answer_id'):
188                 answer_id = int(value)
189         answer = get_object_or_404(Answer, id=answer_id)
190
191         if answer not in assignment.available_answers(expert, marked=marked):
192             return HttpResponseForbidden('Not allowed')
193         if answer.assignment.is_active():
194             return HttpResponseForbidden('Not allowed')
195         all_valid, forms = get_mark_forms(answer, request)
196         if all_valid:
197             if marked:
198                 return HttpResponseRedirect(reverse('stage2_marked_answers', args=[answer.assignment_id]))
199             else:
200                 return HttpResponseRedirect(reverse('stage2_answer_list', args=[answer.assignment_id]))
201         else:
202             sent_forms = answer_id, forms
203     else:
204         sent_forms = None
205     answers = available_answers(assignment, expert, sent_forms=sent_forms, marked=marked)
206     return render(request, 'stage2/answer_list.html', {
207         'answers': answers,
208         'assignment': assignment,
209         'field_counts': assignment.field_counts(answers) if not marked else None,
210         'supervisor': expert in assignment.supervisors.all(),
211         'marked': marked
212     })
213
214
215 def get_mark_forms(answer, request):
216     all_valid = True
217     created_marks = []
218     forms = []
219     for criterion in answer.assignment.markcriterion_set.all():
220         mark, created = Mark.objects.get_or_create(
221             answer=answer, criterion=criterion, expert=request.user, defaults={'points': 0})
222         if created:
223             created_marks.append(mark)
224         form = MarkForm(
225             data=request.POST, answer=answer, criterion=criterion, instance=mark,
226             prefix='mark%s-%s' % (answer.id, criterion.id))
227         if form.is_valid():
228             form.save()
229         else:
230             all_valid = False
231         forms.append(form)
232     if not all_valid:
233         for mark in created_marks:
234             mark.delete()
235     return all_valid, forms
236
237
238 @login_required
239 def expert_download(request, attachment_id):
240     attachment = get_object_or_404(Attachment, id=attachment_id)
241     return attachment_download(attachment)
242
243
244 def write_row(row, writer):
245     writer.writerow([unicode(item).encode('utf-8') for item in row])
246
247
248 @login_required
249 def csv_results(request):
250     import csv
251     response = HttpResponse(content_type='text/csv')
252     writer = csv.writer(response)
253     assignments = Assignment.objects.all()
254     participants = Participant.objects.filter(complete_set=True)
255     headers = [u'imię', u'nazwisko', u'szkoła', u'adres szkoły']
256     assignments_experts = []
257     for assignment in assignments:
258         for expert in assignment.experts.filter(mark__answer__assignment=assignment).distinct():
259             assignments_experts.append((assignment, expert))
260             headers.append(u'%s %s' % (assignment.title, expert.last_name))
261     for assignment in assignments:
262         headers.append(u'%s - średnia' % assignment.title.encode('utf-8'))
263     headers.append(u'ostateczny wynik')
264     write_row(headers, writer)
265     for participant in participants:
266         row = [
267             participant.first_name,
268             participant.last_name,
269             participant.contact.body['school'],
270             participant.contact.body['school_address'],
271         ]
272         for assignment, expert in assignments_experts:
273             marks = expert.mark_set.filter(answer__assignment=assignment, answer__participant=participant)
274             if marks:
275                 row.append(sum(mark.points for mark in marks))
276             else:
277                 row.append('')
278         for assignment in assignments:
279             row.append('%.2f' % participant.answer_set.get(assignment=assignment).score())
280         row.append('%.2f' % participant.score())
281         write_row(row, writer)
282     response['Content-Disposition'] = 'attachment; filename="wyniki.csv"'
283     return response
284
285
286 @login_required
287 def csv_details(request, assignment_id):
288     import csv
289     response = HttpResponse(content_type='text/csv')
290     writer = csv.writer(response)
291     assignment = get_object_or_404(Assignment, id=assignment_id)
292     criteria = tuple(assignment.markcriterion_set.all())
293     expert_headers = (u"ekspert",) + criteria
294     headers = (u"imię", u"nazwisko", u"numer", u"wynik") + expert_headers * 2
295     write_row(headers, writer)
296
297     for a in assignment.answer_set.filter(participant__complete_set=True).order_by('participant__last_name'):
298         row = (
299             a.participant.first_name,
300             a.participant.last_name,
301             str(a.participant.id),
302             '%.2f' % a.score(),
303         )
304         experts = a.mark_set.values_list('expert__username', flat=True).order_by().distinct()
305         for expert in experts:
306             row += (expert,)
307             marks = a.mark_set.filter(expert__username=expert).order_by('criterion__order')
308             row += tuple(marks.values_list('points', flat=True))
309         write_row(row, writer)
310     response['Content-Disposition'] = 'attachment; filename="%s.csv"' % assignment.title
311     return response