Passed
Push — master ( b8e570...5310e7 )
by Peter
01:38
created

FullScriptView.get_object()   A

Complexity

Conditions 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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