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
|
|
|
|