SubmissionWithdrawView.form_valid()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
1
from datetime import datetime
2
import json
3
import os
4
5
from django.views.generic import TemplateView, RedirectView, ListView, DetailView
6
from django.views.generic.edit import UpdateView
7
from django.shortcuts import redirect
8
from django.contrib import auth, messages
9
from django.contrib.auth.mixins import LoginRequiredMixin
10
from django.core.urlresolvers import reverse_lazy
11
from django.forms.models import modelform_factory, model_to_dict
12
from django.core.exceptions import PermissionDenied
13
from django.shortcuts import get_object_or_404, render
14
from django.utils.safestring import mark_safe
15
from django.core.urlresolvers import reverse
16
17
from opensubmit import settings
18
from opensubmit.forms import SettingsForm, getSubmissionForm, SubmissionFileUpdateForm
19
from opensubmit.models import UserProfile, Submission, TestMachine, Course, Assignment, SubmissionFile
20
from opensubmit.models.userprofile import db_fixes
21
from opensubmit.views.helpers import BinaryDownloadMixin
22
23
24
class IndexView(TemplateView):
25
    template_name = 'index.html'
26
27
    def get(self, request):
28
        if request.user.is_authenticated():
29
            return redirect('dashboard')
30
        else:
31
            return super(IndexView, self).get(request)
32
33
class ImpressView(TemplateView):
34
    template_name = 'impress.html'
35
36
    def get(self, request):
37
        if settings.IMPRESS_PAGE:
38
            return redirect(settings.IMPRESS_PAGE)
39
        else:
40
            return super(ImpressView, self).get(request)
41
42
class PrivacyView(TemplateView):
43
    template_name = 'privacy.html'
44
45
    def get(self, request):
46
        if settings.PRIVACY_PAGE:
47
            return redirect(settings.PRIVACY_PAGE)
48
        else:
49
            return super(PrivacyView, self).get(request)
50
51
52
class LogoutView(LoginRequiredMixin, RedirectView):
53
    '''
54
    TODO: Not needed with Django 1.11, which has own LogoutView.
55
    '''
56
    permanent = False
57
    pattern_name = 'index'
58
59
    def get(self, request):
60
        auth.logout(request)
61
        return super().get(request)
62
63
64
class SettingsView(LoginRequiredMixin, UpdateView):
65
    template_name = 'settings.html'
66
    form_class = SettingsForm
67
    success_url = reverse_lazy('dashboard')
68
69
    def form_valid(self, form):
70
        messages.info(self.request, 'User settings saved.')
71
        return super().form_valid(form)
72
73
    def get_object(self, queryset=None):
74
        return self.request.user
75
76
77
class ValidityScriptView(LoginRequiredMixin, BinaryDownloadMixin, DetailView):
78
    model = Assignment
79
80
    def get_object(self, queryset=None):
81
        ass = super().get_object(queryset)
82
        self.f = ass.attachment_test_validity
83
        self.fname = self.f.name[self.f.name.rfind('/') + 1:]
84
        return ass
85
86
87
class FullScriptView(LoginRequiredMixin, BinaryDownloadMixin, DetailView):
88
    model = Assignment
89
90
    def get_object(self, queryset=None):
91
        ass = super().get_object(queryset)
92
        self.f = ass.attachment_test_full
93
        self.fname = self.f.name[self.f.name.rfind('/') + 1:]
94
        return ass
95
96
97
class CoursesView(LoginRequiredMixin, UpdateView):
98
    template_name = 'courses.html'
99
    form_class = modelform_factory(UserProfile, fields=['courses'])
100
    success_url = reverse_lazy('dashboard')
101
102
    def form_valid(self, form):
103
        messages.info(self.request, 'Your choice of courses was saved.')
104
        return super().form_valid(form)
105
106
    def get_object(self, queryset=None):
107
        return self.request.user.profile
108
109
    def get_context_data(self, **kwargs):
110
        context = super().get_context_data(**kwargs)
111
        context['courses'] = self.request.user.profile.user_courses()
112
        return context
113
114
115
class ArchiveView(LoginRequiredMixin, ListView):
116
    template_name = 'archive.html'
117
118
    def get_queryset(self):
119
        archived = self.request.user.authored.all().exclude(assignment__course__active=False).filter(state=Submission.WITHDRAWN).order_by('-created')
120
        return archived
121
122
123
class DashboardView(LoginRequiredMixin, TemplateView):
124
    template_name = 'dashboard.html'
125
126
    def get_context_data(self, **kwargs):
127
        context = super().get_context_data(**kwargs)
128
129
        # Student submissions under validation / grading
130
        context['subs_in_progress'] = self.request.user.authored.all(). \
131
            exclude(assignment__course__active=False). \
132
            exclude(state=Submission.RECEIVED). \
133
            exclude(state=Submission.WITHDRAWN). \
134
            exclude(state=Submission.CLOSED). \
135
            exclude(state=Submission.CLOSED_TEST_FULL_PENDING). \
136
            order_by('-created')
137
138
        # Closed student submissions, graded ones first
139
        context['subs_finished'] = self.request.user.authored.all(). \
140
            exclude(assignment__course__active=False). \
141
            filter(state__in=[Submission.CLOSED, Submission.CLOSED_TEST_FULL_PENDING]). \
142
            order_by('-assignment__gradingScheme', '-created')
143
144
        context['machines'] = TestMachine.objects.filter(enabled=True)
145
        context['today'] = datetime.now()
146
        context['user'] = self.request.user
147
        return context
148
149
    def get(self, request):
150
        # Check and fix database on lower levels for the current user
151
        db_fixes(request.user)
152
153
        # LTI keys and passwords are defined per course
154
        # We use this here to register students automatically for
155
        # courses based on their LTI credentials.
156
        # Note: Authentication is already over here.
157
        if 'passthroughauth' in request.session:
158
            if 'ltikey' in request.session['passthroughauth']:
159
                try:
160
                    ltikey = request.session['passthroughauth']['ltikey']
161
                    request.session['ui_disable_logout'] = True
162
                    course = Course.objects.get(lti_key=ltikey)
163
                    request.user.profile.courses.add(course)
164
                    request.user.profile.save()
165
                except Exception:
166
                    # LTI-based course registration is only a comfort function,
167
                    # so we should not crash the app if that goes wrong
168
                    pass
169
170
        # This is the first view than can check
171
        # if the user settings are complete.
172
        # This depends on the amount of information the authentication provider
173
        # already handed in.
174
        # If incomplete, then we drop annyoing popups until the user gives up.
175
        settingsform = SettingsForm(model_to_dict(request.user), instance=request.user)
176
        if not settingsform.is_valid():
177
            msg = 'Your <a href="{0}">user settings</a> are incomplete.'.format(reverse('settings'))
178
            messages.error(request, mark_safe(msg))
179
180
        return super().get(request)
181
182
183
class SubmissionDetailsView(LoginRequiredMixin, DetailView):
184
    template_name = 'details.html'
185
    model = Submission
186
187
    def get_object(self, queryset=None):
188
        subm = super().get_object(queryset)
189
        # only authors should be able to look into submission details
190
        if not (self.request.user in subm.authors.all() or self.request.user.is_staff):
191
            raise PermissionDenied()
192
        return subm
193
194
195
class MachineDetailsView(LoginRequiredMixin, DetailView):
196
    template_name = 'machine.html'
197
    model = TestMachine
198
199
    def get_context_data(self, **kwargs):
200
        context = super().get_context_data(**kwargs)
201
        try:
202
            context['config'] = json.loads(self.object.config)
203
        except Exception:
204
            context['config'] = []
205
        context['queue'] = Submission.pending_student_tests.all()
206
        context['additional'] = len(Submission.pending_full_tests.all())
207
        return context
208
209
210
class SubmissionNewView(LoginRequiredMixin, TemplateView):
211
    '''
212
    TODO: Currently an ugly direct conversion of the old view
213
    function implementation. Using something like CreateView
214
    demands tailoring of the double-form setup here.
215
    '''
216
    template_name = 'new.html'
217
218
    def dispatch(self, request, *args, **kwargs):
219
        self.ass = get_object_or_404(Assignment, pk=kwargs['pk'])
220
221
        # Check whether submissions are allowed.
222
        if not self.ass.can_create_submission(user=request.user):
223
            raise PermissionDenied(
224
                "You are not allowed to create a submission for this assignment")
225
226
        # get submission form according to the assignment type
227
        self.SubmissionForm = getSubmissionForm(self.ass)
228
229
        return super().dispatch(request, *args, **kwargs)
230
231
    def post(self, request, *args, **kwargs):
232
        # we need to fill all forms here,
233
        # so that they can be rendered on validation errors
234
        submissionForm = self.SubmissionForm(request.user,
235
                                             self.ass,
236
                                             request.POST,
237
                                             request.FILES)
238
        if submissionForm.is_valid():
239
            # commit=False to set submitter in the instance
240
            submission = submissionForm.save(commit=False)
241
            submission.submitter = request.user
242
            submission.assignment = self.ass
243
            submission.state = submission.get_initial_state()
244
            # take uploaded file from extra field
245
            if self.ass.has_attachment:
246
                upload_file = request.FILES['attachment']
247
                submissionFile = SubmissionFile(
248
                    attachment=submissionForm.cleaned_data['attachment'],
249
                    original_filename=upload_file.name)
250
                submissionFile.save()
251
                submission.file_upload = submissionFile
252
            submission.save()
253
            # because of commit=False, we first need to add
254
            # the form-given authors
255
            submissionForm.save_m2m()
256
            submission.save()
257
            messages.info(request, "New submission saved.")
258
            if submission.state == Submission.SUBMITTED:
259
                # Initial state is SUBMITTED,
260
                # which means that there is no validation
261
                if not submission.assignment.is_graded():
262
                    # No validation, no grading. We are done.
263
                    submission.state = Submission.CLOSED
264
                    submission.save()
265
            return redirect('dashboard')
266
        else:
267
            messages.error(request, "Please correct your submission information.")
268
            return render(request, 'new.html', {'submissionForm': submissionForm, 'assignment': self.ass})
269
270
    def get(self, request, *args, **kwargs):
271
        submissionForm = self.SubmissionForm(request.user, self.ass)
272
        return render(request, 'new.html', {'submissionForm': submissionForm, 'assignment': self.ass})
273
274
275
class SubmissionWithdrawView(LoginRequiredMixin, UpdateView):
276
    template_name = 'withdraw.html'
277
    model = Submission
278
    form_class = modelform_factory(Submission, fields=[])  # make form_valid() work
279
    success_url = reverse_lazy('dashboard')
280
281
    def get_object(self, queryset=None):
282
        submission = super().get_object(queryset)
283
        if not submission.can_withdraw(user=self.request.user):
284
            raise PermissionDenied(
285
                "Withdrawal for this assignment is no longer possible, or you are unauthorized to access that submission.")
286
        return submission
287
288
    def form_valid(self, form):
289
        messages.info(self.request, 'Submission successfully withdrawn.')
290
        self.object.state = Submission.WITHDRAWN
291
        self.object.save()
292
        return super().form_valid(form)
293
294
295
class SubmissionUpdateView(LoginRequiredMixin, UpdateView):
296
    template_name = 'update.html'
297
    model = Submission
298
    form_class = SubmissionFileUpdateForm
299
    success_url = reverse_lazy('dashboard')
300
301
    def get_object(self, queryset=None):
302
        submission = super().get_object(queryset)
303
        if not submission.can_reupload():
304
            raise PermissionDenied("Update of submission not / no longer possible.")
305
        if self.request.user not in submission.authors.all():
306
            raise PermissionDenied("Update of submission is only allowed for authors.")
307
        return submission
308
309
    def form_valid(self, form):
310
        upload_file = self.request.FILES['attachment']
311
        new_file = SubmissionFile(
312
            attachment=form.files['attachment'],
313
            original_filename=upload_file.name)
314
        new_file.save()
315
        # fix status of old uploaded file
316
        self.object.file_upload.replaced_by = new_file
317
        self.object.file_upload.save()
318
        # store new file for submissions
319
        self.object.file_upload = new_file
320
        self.object.state = self.object.get_initial_state()
321
        self.object.notes = form.data['notes']
322
        self.object.save()
323
        messages.info(self.request, 'Submission files successfully updated.')
324
        return super().form_valid(form)
325
326
327
class AttachmentFileView(LoginRequiredMixin, BinaryDownloadMixin, DetailView):
328
    model = Submission
329
330
    def get_object(self, queryset=None):
331
        subm = super().get_object(queryset)
332
        if not (self.request.user in subm.authors.all() or self.request.user.is_staff):
333
            raise PermissionDenied()
334
        self.f = subm.file_upload.attachment
335
        self.fname = subm.file_upload.basename()
336
        return subm
337
338
339
class GradingFileView(LoginRequiredMixin, BinaryDownloadMixin, DetailView):
340
    model = Submission
341
342
    def get_object(self, queryset=None):
343
        subm = super().get_object(queryset)
344
        if not (self.request.user in subm.authors.all() or self.request.user.is_staff):
345
            raise PermissionDenied()
346
        self.f = subm.grading_file
347
        self.fname = os.path.basename(subm.grading_file.name)
348
        return subm
349
350
351
class DescriptionFileView(LoginRequiredMixin, BinaryDownloadMixin, DetailView):
352
    model = Assignment
353
354
    def get_object(self, queryset=None):
355
        ass = super().get_object(queryset)
356
        self.f = ass.description
357
        self.fname = self.f.name[self.f.name.rfind('/') + 1:]
358
        return ass
359
360