|
1
|
|
|
''' |
|
2
|
|
|
OpenSubmit backend views that are not realized with Django admin. |
|
3
|
|
|
''' |
|
4
|
|
|
|
|
5
|
|
|
from django.views.generic import TemplateView, DetailView |
|
6
|
|
|
from django.shortcuts import get_object_or_404 |
|
7
|
|
|
from django.contrib.auth.models import User |
|
8
|
|
|
from django.contrib import messages |
|
9
|
|
|
from django.shortcuts import redirect |
|
10
|
|
|
from django.core.mail import send_mass_mail |
|
11
|
|
|
from django.core.exceptions import ViewDoesNotExist |
|
12
|
|
|
|
|
13
|
|
|
from formtools.preview import FormPreview |
|
14
|
|
|
|
|
15
|
|
|
from opensubmit.models import Submission, Assignment, Course |
|
16
|
|
|
from opensubmit.models.userprofile import move_user_data |
|
17
|
|
|
from opensubmit.views.helpers import StaffRequiredMixin, ZipDownloadDetailView |
|
18
|
|
|
|
|
19
|
|
|
|
|
20
|
|
|
class AssignmentArchiveView(StaffRequiredMixin, ZipDownloadDetailView): |
|
21
|
|
|
model = Assignment |
|
22
|
|
|
|
|
23
|
|
|
def fill_zip_file(self, z): |
|
24
|
|
|
assignment = self.object |
|
25
|
|
|
assignment.add_to_zipfile(z) |
|
26
|
|
|
subs = Submission.valid_ones.filter(assignment=assignment).order_by('submitter') |
|
27
|
|
|
for sub in subs: |
|
28
|
|
|
sub.add_to_zipfile(z) |
|
29
|
|
|
return assignment.directory_name() |
|
30
|
|
|
|
|
31
|
|
|
|
|
32
|
|
|
class CourseArchiveView(StaffRequiredMixin, ZipDownloadDetailView): |
|
33
|
|
|
model = Course |
|
34
|
|
|
|
|
35
|
|
|
def fill_zip_file(self, z): |
|
36
|
|
|
course = self.object |
|
37
|
|
|
assignments = course.assignments.order_by('title') |
|
38
|
|
|
for ass in assignments: |
|
39
|
|
|
ass.add_to_zipfile(z) |
|
40
|
|
|
subs = ass.submissions.all().order_by('submitter') |
|
41
|
|
|
for sub in subs: |
|
42
|
|
|
sub.add_to_zipfile(z) |
|
43
|
|
|
return course.directory_name() |
|
44
|
|
|
|
|
45
|
|
|
|
|
46
|
|
|
class PreviewView(StaffRequiredMixin, DetailView): |
|
47
|
|
|
template_name = 'file_preview.html' |
|
48
|
|
|
model = Submission |
|
49
|
|
|
|
|
50
|
|
|
|
|
51
|
|
|
class DuplicatesView(StaffRequiredMixin, DetailView): |
|
52
|
|
|
template_name = 'duplicates.html' |
|
53
|
|
|
model = Assignment |
|
54
|
|
|
|
|
55
|
|
|
|
|
56
|
|
|
class GradingTableView(StaffRequiredMixin, DetailView): |
|
57
|
|
|
template_name = 'gradingtable.html' |
|
58
|
|
|
model = Course |
|
59
|
|
|
|
|
60
|
|
|
def get_context_data(self, **kwargs): |
|
61
|
|
|
course = self.object |
|
62
|
|
|
assignments = course.assignments.all().order_by('title') |
|
63
|
|
|
|
|
64
|
|
|
# find all gradings per author and assignment |
|
65
|
|
|
author_submissions = {} |
|
66
|
|
|
for assignment in assignments: |
|
67
|
|
|
for submission in assignment.submissions.all().filter(state=Submission.CLOSED): |
|
68
|
|
|
for author in submission.authors.all(): |
|
69
|
|
|
# author_submissions is a dict mapping authors to another dict |
|
70
|
|
|
# This second dict maps assignments to submissions (for this author) |
|
71
|
|
|
# A tuple as dict key does not help here, since we want to iterate over the assignments later |
|
72
|
|
|
if author not in list(author_submissions.keys()): |
|
73
|
|
|
author_submissions[author] = {assignment.pk: submission} |
|
74
|
|
|
else: |
|
75
|
|
|
author_submissions[author][assignment.pk] = submission |
|
76
|
|
|
resulttable = [] |
|
77
|
|
|
for author, ass2sub in list(author_submissions.items()): |
|
78
|
|
|
columns = [] |
|
79
|
|
|
numpassed = 0 |
|
80
|
|
|
numgraded = 0 |
|
81
|
|
|
pointsum = 0 |
|
82
|
|
|
columns.append(author.last_name if author.last_name else '') |
|
83
|
|
|
columns.append(author.first_name if author.first_name else '') |
|
84
|
|
|
columns.append( |
|
85
|
|
|
author.profile.student_id if author.profile.student_id else '') |
|
86
|
|
|
columns.append( |
|
87
|
|
|
author.profile.study_program if author.profile.study_program else '') |
|
88
|
|
|
# Process all assignments in the table order, once per author (loop above) |
|
89
|
|
|
for assignment in assignments: |
|
90
|
|
|
if assignment.pk in ass2sub: |
|
91
|
|
|
# Ok, we have a submission for this author in this assignment |
|
92
|
|
|
submission = ass2sub[assignment.pk] |
|
93
|
|
|
if assignment.is_graded(): |
|
94
|
|
|
# is graded, make part of statistics |
|
95
|
|
|
numgraded += 1 |
|
96
|
|
|
if submission.grading_means_passed(): |
|
97
|
|
|
numpassed += 1 |
|
98
|
|
|
try: |
|
99
|
|
|
pointsum += int(str(submission.grading)) |
|
100
|
|
|
except Exception: |
|
101
|
|
|
pass |
|
102
|
|
|
# considers both graded and ungraded assignments |
|
103
|
|
|
columns.append(submission.grading_value_text()) |
|
104
|
|
|
else: |
|
105
|
|
|
# No submission for this author in this assignment |
|
106
|
|
|
# This may or may not be bad, so we keep it neutral here |
|
107
|
|
|
columns.append('-') |
|
108
|
|
|
columns.append("%s / %s" % (numpassed, numgraded)) |
|
109
|
|
|
columns.append("%u" % pointsum) |
|
110
|
|
|
resulttable.append(columns) |
|
111
|
|
|
|
|
112
|
|
|
context = super().get_context_data(**kwargs) |
|
113
|
|
|
context['assignments'] = assignments |
|
114
|
|
|
context['resulttable'] = sorted(resulttable) |
|
115
|
|
|
return context |
|
116
|
|
|
|
|
117
|
|
|
|
|
118
|
|
|
class MergeUsersView(StaffRequiredMixin, TemplateView): |
|
119
|
|
|
template_name = 'mergeusers.html' |
|
120
|
|
|
|
|
121
|
|
|
def get_context_data(self, **kwargs): |
|
122
|
|
|
context = super().get_context_data(**kwargs) |
|
123
|
|
|
context['primary'] = get_object_or_404(User, pk=kwargs['primary_pk']) |
|
124
|
|
|
context['secondary'] = get_object_or_404(User, pk=kwargs['secondary_pk']) |
|
125
|
|
|
return context |
|
126
|
|
|
|
|
127
|
|
|
def post(self, request, *args, **kwargs): |
|
128
|
|
|
primary = get_object_or_404(User, pk=kwargs['primary_pk']) |
|
129
|
|
|
secondary = get_object_or_404(User, pk=kwargs['secondary_pk']) |
|
130
|
|
|
try: |
|
131
|
|
|
move_user_data(primary, secondary) |
|
132
|
|
|
messages.info(request, 'Submissions moved to user %u.' % |
|
133
|
|
|
(primary.pk)) |
|
134
|
|
|
except Exception: |
|
135
|
|
|
messages.error( |
|
136
|
|
|
request, 'Error during data migration, nothing changed.') |
|
137
|
|
|
return redirect('admin:index') |
|
138
|
|
|
messages.info(request, 'User %u updated, user %u deleted.' % (primary.pk, secondary.pk)) |
|
139
|
|
|
secondary.delete() |
|
140
|
|
|
return redirect('admin:index') |
|
141
|
|
|
|
|
142
|
|
|
|
|
143
|
|
|
class MailFormPreview(StaffRequiredMixin, FormPreview): |
|
144
|
|
|
form_template = 'mail_form.html' |
|
145
|
|
|
preview_template = 'mail_preview.html' |
|
146
|
|
|
|
|
147
|
|
|
def get_context(self, request, form): |
|
148
|
|
|
context = super().get_context(request, form) |
|
149
|
|
|
mailadrs_qs = self.state['receivers'].order_by('email').distinct().values('first_name', 'last_name', 'email') |
|
150
|
|
|
mailadrs = [mailadr for mailadr in mailadrs_qs] |
|
151
|
|
|
context['receivers'] = mailadrs |
|
152
|
|
|
context['sender'] = request.user |
|
153
|
|
|
return context |
|
154
|
|
|
|
|
155
|
|
|
def parse_params(self, request, *args, **kwargs): |
|
156
|
|
|
if 'user_list' in kwargs: |
|
157
|
|
|
id_list = [int(val) for val in kwargs['user_list'].split(',')] |
|
158
|
|
|
self.state['receivers'] = User.objects.filter(pk__in=id_list).distinct() |
|
159
|
|
|
elif 'course_id' in kwargs: |
|
160
|
|
|
course = get_object_or_404(Course, pk=kwargs['course_id']) |
|
161
|
|
|
self.state['receivers'] = User.objects.filter(profile__courses__pk=course.pk) |
|
162
|
|
|
else: |
|
163
|
|
|
raise ViewDoesNotExist |
|
164
|
|
|
|
|
165
|
|
|
def done(self, request, cleaned_data): |
|
166
|
|
|
from opensubmit.templatetags.projecttags import replace_macros |
|
167
|
|
|
|
|
168
|
|
|
tosend = [[replace_macros(cleaned_data['subject'], {'first_name': recv.first_name, 'last_name': recv.last_name}), |
|
169
|
|
|
replace_macros(cleaned_data['message'], {'first_name': recv.first_name, 'last_name': recv.last_name}), |
|
170
|
|
|
request.user.email, |
|
171
|
|
|
[recv.email]] for recv in self.state['receivers']] |
|
172
|
|
|
sent = send_mass_mail(tosend, fail_silently=True) |
|
173
|
|
|
messages.add_message(request, messages.INFO, '%u message(s) sent.' % sent) |
|
174
|
|
|
return redirect('teacher:index') |
|
175
|
|
|
|
|
176
|
|
|
|