-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathviews.py
544 lines (444 loc) · 20.4 KB
/
views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
from django.conf import settings
from django.contrib.sites.shortcuts import get_current_site
from django.contrib.auth.decorators import login_required
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth import login, authenticate, logout
from django.shortcuts import render, redirect
from django.http import HttpResponse, Http404
from django.contrib.auth.models import User
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from django.utils.http import urlsafe_base64_decode
from django.template.loader import render_to_string
from .tokens import account_activation_token
from django.utils.encoding import force_text
from django.views.decorators.csrf import csrf_exempt
from django.core import serializers
from django.utils import timezone
from django.utils.encoding import smart_str
from django.http import JsonResponse
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import PasswordChangeForm
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from .models import Student, Course, Assignment, Submission, AssignmentExtension, OtherFile
from .forms import SignUpForm, EnrollForm, ChangeEmailForm
from django.contrib import messages
from .grader import run_student_tests
from multiprocessing import Process, Manager, Queue
import mimetypes
import json
import zipfile
import shutil
import logging
import os
import sys
import re
import time
import urllib
from datetime import datetime
import dateutil.relativedelta
logger = logging.getLogger(__name__)
@login_required(login_url='login')
def home(request):
user = request.user
student = Student.objects.filter(user=user).first()
# If user is student and also staff member then allow him the access to student panel.
if not student and (user.is_staff or user.is_superuser):
return HttpResponseRedirect(reverse('admin:index'))
form = EnrollForm()
if request.method == "POST":
form = EnrollForm(request.POST)
if form.is_valid():
secret_key = form.cleaned_data['secret_key']
course = Course.objects.filter(enroll_key=secret_key).first()
if course:
already_registered = Student.objects.filter(pk=student.id, courses__id=course.id).exists()
if already_registered:
messages.warning(request, 'You have already registered that course')
else:
student.courses.add(course)
student.save()
messages.success(request, 'You are successfully registered to the course')
else:
messages.error(request, 'Invalid Enroll Key')
return redirect('home')
errors = form.errors or None
return render(request,
'home.html',
{
'courses': student.courses.all(),
'form': form,
'errors': errors,
'student': student
}
)
def signup(request):
if request.method == 'POST':
form = SignUpForm(request.POST)
if form.is_valid():
user = form.save(commit=False)
user.is_active = False
user.save()
student = Student.objects.create(user=user)
student.save()
current_site = get_current_site(request)
subject = 'Activate Your FAST AutoGrader Account'
message = render_to_string('account/account_activation_email.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': account_activation_token.make_token(user),
})
user.email_user(subject, message)
"""
try:
user.email_user(subject, message)
return redirect('account_activation_sent')
except Exception:
form.add_error(None, "Email sending failed, try again later.")
user.delete()
"""
raw_password = form.cleaned_data.get('password1')
user = authenticate(username=user.username, password=raw_password)
login(request, user)
request.session['username'] = user.username
return redirect('home')
else:
form = SignUpForm()
return render(request, 'signup.html', {'form': form})
def activate(request, uidb64, token):
try:
uid = force_text(urlsafe_base64_decode(uidb64))
user = User.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
if user is not None and account_activation_token.check_token(user, token):
user.is_active = True
user.student.email_confirmed = True
user.save()
login(request, user)
return redirect('home')
else:
return render(request, 'account/account_activation_invalid.html')
def account_activation_sent(request):
return render(request, 'account/account_activation_sent.html')
@login_required(login_url='login')
def download(request):
# TODO: break when user try to download Instructor Test file
submission_id = request.GET.get('sid')
assignment_id = request.GET.get('aid')
raw = request.GET.get('raw')
action = request.GET.get('action')
# Check
if assignment_id:
student = Student.objects.get(user=request.user);
assignment = Assignment.objects.filter(id=assignment_id, course__in=student.courses.all(), open_date__lte=timezone.now()).distinct()
if assignment.exists():
assignment = assignment.first()
if action == "student_test":
path = assignment.student_test.url
elif action == "zip_file":
path = os.path.dirname(assignment.student_test.url) + "/assignment" + assignment_id + ".zip"
elif action == "config_file":
path = os.path.dirname(assignment.student_test.url) + "/config.json"
elif action == "assignment_file":
path = assignment.assignment_file.url
else:
return Http404
else:
path = ""
elif submission_id:
submission = Submission.objects.get(id=submission_id)
# Download modifiable_file of student when user is staff or admin or user himself
allow_downloading_modifiable_file = request.user.is_staff or request.user.is_superuser or (submission.student.user == request.user)
if submission and action == "modifiable_file" and allow_downloading_modifiable_file:
path = submission.get_modifiable_file()
elif submission and settings.ALLOW_INSTRUCTOR_TEST_LOG_VIEW:
path = submission.get_log_file()
else:
path = ""
else:
path = ""
file_path = os.path.join(settings.MEDIA_ROOT, path)
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
if raw:
try:
url = urllib.request.pathname2url(file_path)
except AttributeError:
# fix for python2 compatability idiocy
url = urllib.pathname2url(file_path)
content_type = mimetypes.guess_type(url)[0]
if not content_type:
content_type = "text/plain"
else:
content_type = 'application/force-download'
response = HttpResponse(fh.read(), content_type=content_type)
response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path)
return response
raise Http404
@login_required(login_url='login')
def course(request, course_id, assignment_id=0):
user = User.objects.get(pk=request.user.id)
student = Student.objects.filter(user=user, courses=course_id).first()
if not student:
return redirect("home");
course = Course.objects.get(id=course_id)
assignments = Assignment.objects.filter(course=course, open_date__lte=timezone.now()).order_by('due_date')
selected_assignment = None
submission_history = None
assignment_zip_file = None
time_left = ''
modifiable_filename = None
expired = False
can_request_extension = False
late_days_left = student.get_late_days_left(course)
late_days_needed = 0
try:
show_view_log_button = settings.ALLOW_INSTRUCTOR_TEST_LOG_VIEW
except AttributeError:
show_view_log_button = True
if (assignment_id != 0):
try:
selected_assignment = Assignment.objects.get(id=assignment_id, open_date__lte=timezone.now())
except ObjectDoesNotExist:
raise Http404;
submission_history = Submission.objects.filter(student=student,assignment=selected_assignment).order_by("-publish_date")
assignment_zip_file = os.path.split(selected_assignment.student_test.url)[0] + "/assignment" + str(assignment_id) + ".zip"
due_date = selected_assignment.corrected_due_date(student)
now_time = timezone.now()
if due_date > now_time:
rd = dateutil.relativedelta.relativedelta (due_date, now_time)
time_left = "%d days, %d hours and %d minutes" % (rd.days, rd.hours, rd.minutes) + " left"
else:
time_left = "Submission date has passed!"
expired = True
# late_delta = dateutil.relativedelta.relativedelta(now_time, due_date) # relativedelta doesn't work. Use date subtraction
late_delta = now_time - due_date
late_days_needed = late_delta.days + 1 # a second above is a full day
if late_days_needed <= late_days_left:
can_request_extension = True
# only one assignment file for now
modifiable_filename = os.path.basename(selected_assignment.assignment_file.url)
else:
due_date = None # no assignment therefore no due date
return render(request, 'course.html', {
'assignment_zip_file': assignment_zip_file,
'corrected_due_date': due_date,
'late_days_left': late_days_left,
'late_days_needed': late_days_needed,
'assignment_id': int(assignment_id),
'can_request_extension': can_request_extension,
'course': course,
'assignments': assignments,
'show_view_log_button': show_view_log_button,
'selected_assignment': selected_assignment,
'submission_history': submission_history,
'time_left' : time_left,
'modifiable_filename': modifiable_filename,
'assignment_expired': expired
}
)
@csrf_exempt
def api(request, action):
email = request.POST.get('email')
submission_pass = request.POST.get('submission_pass')
student = Student.objects.filter(user__email=email, submission_pass=submission_pass)
if student.exists():
student = student[0]
if student.user.is_active == False:
response_data = {"status": 400, "type": "ERROR",
"message": "Verify your email first."}
elif (action == "submit_assignment"):
if request.method == 'POST':
assignment = Assignment.objects.filter(id=request.POST.get('assignment'), course__in=student.courses.all(), open_date__lte=timezone.now()).distinct().first()
if not assignment:
response_data = {"status": 404, "type": "ERROR",
"message": "Assignment doesn't exists"}
elif assignment and timezone.now() > assignment.corrected_due_date(student):
response_data = {"status": 400, "type": "ERROR",
"message": "Assignment submission date expired. You may be able to request an extension from the web interface."}
else:
submission = Submission(submission_file=request.FILES['submission_file'],
assignment=assignment,
student=student)
submission.save()
submission_file_url = submission.submission_file.url
extract_directory = submission_file_url.replace(".zip","/")
zip_file = zipfile.ZipFile(submission.submission_file.url, 'r')
zip_file.extractall(extract_directory)
zip_file.close()
# Move Instructor Test File
shutil.copy(assignment.instructor_test.url, extract_directory)
# Move Student Test File
shutil.copy(assignment.student_test.url, extract_directory)
# Copy OtherFiles
other_files = OtherFile.objects.filter(assignment=assignment)
for other_file in other_files:
other_file_url = other_file.file.url
logging.debug("Copying other file: " + str(other_file_url))
shutil.copy(other_file_url, extract_directory)
score, timeout = run_student_tests(extract_directory, assignment.total_points, assignment.timeout)
submission.passed = score[0]
submission.failed = score[1]
submission.save()
if timeout:
response_data = {"status": 400, "type": "ERROR",
"message": "Timeout error. You received zero for this submission."}
else:
response_data = {"status": 200, "type": "SUCCESS",
"message": [score[0], score[1], submission.get_score()]}
else:
response_data = {"status": 400, "type": "ERROR",
"message": "Use POST method"}
else:
response_data = {"status": 400, "type": "ERROR",
"message": "Invalid action"}
else:
response_data = {"status": 403, "type": "ERROR",
"message": "Invalid student"}
r = JsonResponse(response_data, safe=False)
r.status_code = response_data['status']
return r
@login_required
def change_password(request):
if request.method == 'POST':
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
user = form.save()
update_session_auth_hash(request, user) # Important!
messages.success(request, 'Your password was successfully updated!')
return redirect('/autograde')
else:
form = PasswordChangeForm(request.user)
return render(request, 'account/change_password.html', {
'form': form
})
@login_required
def resend_signup_email(request):
user = request.user
current_site = get_current_site(request)
subject = 'Activate Your FAST AutoGrader Account'
message = render_to_string('account/account_activation_email.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': account_activation_token.make_token(user),
})
user.email_user(subject, message)
messages.success(request, 'Verification email sent, check your email account.')
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
@login_required
def change_email(request):
email = request.POST.get("email")
form = ChangeEmailForm(request.POST)
if form.is_valid():
user = request.user
user.email = form.cleaned_data.get('email')
user.save()
current_site = get_current_site(request)
subject = 'Activate Your FAST AutoGrader Account'
message = render_to_string('account/account_activation_email.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': account_activation_token.make_token(user),
})
user.email_user(subject, message)
messages.success(request, 'Email updated and verification email sent.')
else:
for field in form:
for error in field.errors:
messages.warning(request, field.label + ": " + error)
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
@staff_member_required
def assignment_report(request, assignment_id):
assignment = Assignment.objects.get(id=assignment_id)
submissions = assignment.get_student_and_latest_submissions()
return render(request, 'admin/assignment_report.html', {
'submissions': submissions,
'assignment': assignment,
'generated_on': timezone.now()
})
@staff_member_required
def moss_submit(request, assignment_id):
assignment = Assignment.objects.get(id=assignment_id)
assignment.moss_submit() # This will enable the view button if report is generated
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
@staff_member_required
def moss_view(request, assignment_id):
assignment = Assignment.objects.get(id=assignment_id)
file_path = os.path.join(settings.MEDIA_ROOT, assignment.moss_report())
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
content_type = 'text/html'
response = HttpResponse(fh.read(), content_type=content_type)
response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path)
return response
raise Http404
@staff_member_required
def assignment_aggregate_report(request, assignment_id):
assignment = Assignment.objects.get(id=assignment_id)
submissions = assignment.get_student_and_latest_submissions()
all_submissions = {}
for submission, student, _ in submissions:
if submission:
file_path = submission.get_modifiable_file()
file_path = os.path.join(settings.MEDIA_ROOT, file_path)
with open(file_path, 'rb') as fh:
submission_content = fh.read()
submission.file_content = submission_content
return render(request, 'admin/assignment_aggregate_report.html', {
'submissions': submissions,
'assignment': assignment,
'generated_on': timezone.now()
})
@staff_member_required
def loginas(request, student_id):
# Save staff id so when student account logout so that staff is logged in
staff_user_id = request.user.id
student = Student.objects.get(id=student_id)
user = student.user
login(request, student.user)
request.session['username'] = user.username
request.session['staff_loginas'] = True
request.session['staff_loginas_referer'] = request.META.get('HTTP_REFERER')
request.session['staff_loginas_userid'] = staff_user_id
return redirect('home')
def logout_student(request):
if "staff_loginas" in request.session and request.session['staff_loginas']:
staff_user_id = request.session['staff_loginas_userid']
loginas_referer = request.session['staff_loginas_referer']
login(request, User.objects.get(id=staff_user_id))
return HttpResponseRedirect(loginas_referer)
else:
logout(request)
return redirect('login')
@login_required(login_url='login')
def request_extension(request):
student = Student.objects.get(user = request.user)
assignment_id = request.GET.get('aid')
selected_assignment = Assignment.objects.get(id=assignment_id)
logging.warn("Processing extension request for: " + str(student) + " on "
+ str(assignment_id) + " - " + str(selected_assignment))
now_time = timezone.now()
due_date = selected_assignment.corrected_due_date(student)
# late_delta = dateutil.relativedelta.relativedelta(now_time, due_date)
late_delta = now_time - due_date
late_days_needed = late_delta.days + 1 # a second above is a full day
late_days_left = student.get_late_days_left(selected_assignment.course)
if not due_date > now_time:
if late_days_needed <= late_days_left:
extension = AssignmentExtension(assignment=selected_assignment, student = student, days=late_days_needed)
extension.save()
status = 200
out = "Your extension request has been processed succesfully. "
else:
status = 500
out = "Insufficient number of late days remaining. Cannot process extension request. "
else:
status = 500
out = "There is still time left in assignment submission. Cannot process extension request. "
return HttpResponse(out, content_type="text/plain", status=status)