|
1
|
|
|
from datetime import datetime |
|
2
|
|
|
import json |
|
3
|
|
|
|
|
4
|
|
|
from django.views.generic import TemplateView, RedirectView, ListView, DetailView |
|
5
|
|
|
from django.views.generic.edit import UpdateView |
|
6
|
|
|
from django.shortcuts import redirect |
|
7
|
|
|
from django.contrib import auth, messages |
|
8
|
|
|
from django.contrib.auth.decorators import login_required |
|
9
|
|
|
from django.utils.decorators import method_decorator |
|
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
|
|
|
|
|
14
|
|
|
from opensubmit.forms import SettingsForm |
|
15
|
|
|
from opensubmit.models import UserProfile, Submission, TestMachine, Course |
|
16
|
|
|
from opensubmit.models.userprofile import db_fixes |
|
17
|
|
|
|
|
18
|
|
|
|
|
19
|
|
|
class IndexView(TemplateView): |
|
20
|
|
|
template_name = 'index.html' |
|
21
|
|
|
|
|
22
|
|
|
def get(self, request): |
|
23
|
|
|
if request.user.is_authenticated(): |
|
24
|
|
|
return redirect('dashboard') |
|
25
|
|
|
else: |
|
26
|
|
|
return super(IndexView, self).get(request) |
|
27
|
|
|
|
|
28
|
|
|
|
|
29
|
|
|
@method_decorator(login_required, name='dispatch') |
|
30
|
|
|
class LogoutView(RedirectView): |
|
31
|
|
|
''' |
|
32
|
|
|
TODO: Not needed with Django 1.11, which has own LogoutView. |
|
33
|
|
|
''' |
|
34
|
|
|
permanent = False |
|
35
|
|
|
pattern_name = 'index' |
|
36
|
|
|
|
|
37
|
|
|
def get(self, request): |
|
38
|
|
|
auth.logout(request) |
|
39
|
|
|
return super().get(request) |
|
40
|
|
|
|
|
41
|
|
|
|
|
42
|
|
|
@method_decorator(login_required, name='dispatch') |
|
43
|
|
|
class SettingsView(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
|
|
|
@method_decorator(login_required, name='dispatch') |
|
57
|
|
|
class CoursesView(UpdateView): |
|
58
|
|
|
template_name = 'courses.html' |
|
59
|
|
|
form_class = modelform_factory(UserProfile, fields=['courses']) |
|
60
|
|
|
success_url = reverse_lazy('dashboard') |
|
61
|
|
|
|
|
62
|
|
|
def form_valid(self, form): |
|
63
|
|
|
messages.info(self.request, 'Your choice of courses was saved.') |
|
64
|
|
|
return super().form_valid(form) |
|
65
|
|
|
|
|
66
|
|
|
def get_object(self, queryset=None): |
|
67
|
|
|
return self.request.user.profile |
|
68
|
|
|
|
|
69
|
|
|
def get_context_data(self, **kwargs): |
|
70
|
|
|
context = super().get_context_data(**kwargs) |
|
71
|
|
|
context['courses'] = self.request.user.profile.user_courses() |
|
72
|
|
|
return context |
|
73
|
|
|
|
|
74
|
|
|
|
|
75
|
|
|
@method_decorator(login_required, name='dispatch') |
|
76
|
|
|
class ArchiveView(ListView): |
|
77
|
|
|
template_name = 'archive.html' |
|
78
|
|
|
|
|
79
|
|
|
def get_queryset(self): |
|
80
|
|
|
archived = self.request.user.authored.all().exclude(assignment__course__active=False).filter(state=Submission.WITHDRAWN).order_by('-created') |
|
81
|
|
|
return archived |
|
82
|
|
|
|
|
83
|
|
|
|
|
84
|
|
|
@method_decorator(login_required, name='dispatch') |
|
85
|
|
|
class DashboardView(TemplateView): |
|
86
|
|
|
template_name = 'dashboard.html' |
|
87
|
|
|
|
|
88
|
|
|
def get_context_data(self, **kwargs): |
|
89
|
|
|
context = super().get_context_data(**kwargs) |
|
90
|
|
|
|
|
91
|
|
|
# Student submissions under validation / grading |
|
92
|
|
|
context['subs_in_progress'] = self.request.user.authored.all(). \ |
|
93
|
|
|
exclude(assignment__course__active=False). \ |
|
94
|
|
|
exclude(state=Submission.RECEIVED). \ |
|
95
|
|
|
exclude(state=Submission.WITHDRAWN). \ |
|
96
|
|
|
exclude(state=Submission.CLOSED). \ |
|
97
|
|
|
exclude(state=Submission.CLOSED_TEST_FULL_PENDING). \ |
|
98
|
|
|
order_by('-created') |
|
99
|
|
|
|
|
100
|
|
|
# Closed student submissions, graded ones first |
|
101
|
|
|
context['subs_finished'] = self.request.user.authored.all(). \ |
|
102
|
|
|
exclude(assignment__course__active=False). \ |
|
103
|
|
|
filter(state__in=[Submission.CLOSED, Submission.CLOSED_TEST_FULL_PENDING]). \ |
|
104
|
|
|
order_by('-assignment__gradingScheme', '-created') |
|
105
|
|
|
|
|
106
|
|
|
context['machines'] = TestMachine.objects.filter(enabled=True) |
|
107
|
|
|
context['today'] = datetime.now() |
|
108
|
|
|
context['user'] = self.request.user |
|
109
|
|
|
return context |
|
110
|
|
|
|
|
111
|
|
|
def get(self, request): |
|
112
|
|
|
# Check and fix database on lower levels for the current user |
|
113
|
|
|
db_fixes(request.user) |
|
114
|
|
|
|
|
115
|
|
|
# LTI keys and passwords are defined per course |
|
116
|
|
|
# We use this here to register students automatically for |
|
117
|
|
|
# courses based on their LTI credentials. |
|
118
|
|
|
# Note: Authentication is already over here. |
|
119
|
|
|
if 'passthroughauth' in request.session: |
|
120
|
|
|
if 'ltikey' in request.session['passthroughauth']: |
|
121
|
|
|
try: |
|
122
|
|
|
ltikey = request.session['passthroughauth']['ltikey'] |
|
123
|
|
|
request.session['ui_disable_logout'] = True |
|
124
|
|
|
course = Course.objects.get(lti_key=ltikey) |
|
125
|
|
|
request.user.profile.courses.add(course) |
|
126
|
|
|
request.user.profile.save() |
|
127
|
|
|
except Exception: |
|
128
|
|
|
# LTI-based course registration is only a comfort function, |
|
129
|
|
|
# so we should not crash the app if that goes wrong |
|
130
|
|
|
pass |
|
131
|
|
|
|
|
132
|
|
|
# This is the first view than can check |
|
133
|
|
|
# if the user settings are complete. |
|
134
|
|
|
# This depends on the amount of information the authentication provider |
|
135
|
|
|
# already handed in. |
|
136
|
|
|
# If incomplete, then we drop annyoing popups until the user gives up. |
|
137
|
|
|
settingsform = SettingsForm(model_to_dict(request.user), instance=request.user) |
|
138
|
|
|
if not settingsform.is_valid(): |
|
139
|
|
|
messages.error(request, "Your user settings are incomplete.") |
|
140
|
|
|
|
|
141
|
|
|
return super().get(request) |
|
142
|
|
|
|
|
143
|
|
|
|
|
144
|
|
|
@method_decorator(login_required, name='dispatch') |
|
145
|
|
|
class SubmissionDetailsView(DetailView): |
|
146
|
|
|
template_name = 'details.html' |
|
147
|
|
|
model = Submission |
|
148
|
|
|
|
|
149
|
|
|
def get_object(self, queryset=None): |
|
150
|
|
|
subm = super().get_object(queryset) |
|
151
|
|
|
# only authors should be able to look into submission details |
|
152
|
|
|
if not (self.request.user in subm.authors.all() or self.request.user.is_staff): |
|
153
|
|
|
raise PermissionDenied() |
|
154
|
|
|
return subm |
|
155
|
|
|
|
|
156
|
|
|
|
|
157
|
|
|
@method_decorator(login_required, name='dispatch') |
|
158
|
|
|
class MachineDetailsView(DetailView): |
|
159
|
|
|
template_name = 'machine.html' |
|
160
|
|
|
model = TestMachine |
|
161
|
|
|
|
|
162
|
|
|
def get_context_data(self, **kwargs): |
|
163
|
|
|
context = super().get_context_data(**kwargs) |
|
164
|
|
|
try: |
|
165
|
|
|
context['config'] = json.loads(self.object.config) |
|
166
|
|
|
except Exception: |
|
167
|
|
|
context['config'] = [] |
|
168
|
|
|
context['queue'] = Submission.pending_student_tests.all() |
|
169
|
|
|
context['additional'] = len(Submission.pending_full_tests.all()) |
|
170
|
|
|
return context |
|
171
|
|
|
|