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