1
|
|
|
from django.db import models |
2
|
|
|
from django.contrib.auth.models import User |
3
|
|
|
from django.utils import timezone |
4
|
|
|
from django.core.urlresolvers import reverse |
5
|
|
|
|
6
|
|
|
from .assignment import Assignment |
7
|
|
|
from .submission import Submission |
8
|
|
|
|
9
|
|
|
class ValidCoursesManager(models.Manager): |
10
|
|
|
''' |
11
|
|
|
A model manager used by the Course model. It returns a sorted list |
12
|
|
|
of courses that are not inactive. |
13
|
|
|
''' |
14
|
|
|
|
15
|
|
|
def get_queryset(self): |
16
|
|
|
return Course.objects.exclude(active=False).order_by('title') |
17
|
|
|
|
18
|
|
|
class Course(models.Model): |
19
|
|
|
title = models.CharField(max_length=200) |
20
|
|
|
created = models.DateTimeField(auto_now_add=True, editable=False) |
21
|
|
|
owner = models.ForeignKey(User, related_name='courses', help_text="Only this user can change the course details and create new assignments.") |
22
|
|
|
tutors = models.ManyToManyField(User, blank=True, related_name='courses_tutoring', help_text="These users can edit / grade submissions for the course.") |
23
|
|
|
homepage = models.URLField(max_length=200, verbose_name="Course description link") |
24
|
|
|
active = models.BooleanField(default=True, help_text="Only assignments and submissions of active courses are shown to students and tutors. Use this flag for archiving past courses.") |
25
|
|
|
lti_key = models.CharField(max_length=100, null=True, blank=True, help_text="Key to be used by an LTI consumer when accessing this course.") |
26
|
|
|
lti_secret = models.CharField(max_length=100, null=True, blank=True, help_text="Secret to be used by an LTI consumer when accessing this course.") |
27
|
|
|
|
28
|
|
|
objects = models.Manager() |
29
|
|
|
valid_ones = ValidCoursesManager() |
30
|
|
|
|
31
|
|
|
class Meta: |
32
|
|
|
app_label = 'opensubmit' |
33
|
|
|
|
34
|
|
|
def __str__(self): |
35
|
|
|
if self.active: |
36
|
|
|
return self.title |
37
|
|
|
else: |
38
|
|
|
return self.title+' (inactive)' |
39
|
|
|
|
40
|
|
|
def directory_name(self): |
41
|
|
|
''' The course name in a format that is suitable for a directory name. ''' |
42
|
|
|
return self.title.replace(" ", "_").replace("\\", "_").replace("/", "_").replace(",","").lower() |
43
|
|
|
|
44
|
|
|
def open_assignments(self): |
45
|
|
|
qs = Assignment.objects.filter(hard_deadline__gt=timezone.now()) | Assignment.objects.filter(hard_deadline__isnull=True) |
46
|
|
|
qs = qs.filter(publish_at__lt=timezone.now()) |
47
|
|
|
qs = qs.filter(course=self) |
48
|
|
|
qs = qs.order_by('soft_deadline').order_by('hard_deadline').order_by('title') |
49
|
|
|
return qs |
50
|
|
|
|
51
|
|
|
def gradable_submissions(self): |
52
|
|
|
''' |
53
|
|
|
Queryset for the gradable submissions that are worth a look by tutors. |
54
|
|
|
''' |
55
|
|
|
qs = self._valid_submissions() |
56
|
|
|
qs = qs.filter(state__in=[Submission.GRADING_IN_PROGRESS, Submission.SUBMITTED_TESTED, Submission.TEST_FULL_FAILED, Submission.SUBMITTED]) |
57
|
|
|
return qs |
58
|
|
|
|
59
|
|
|
def graded_submissions(self): |
60
|
|
|
''' |
61
|
|
|
Queryset for the graded submissions, which are worth closing. |
62
|
|
|
''' |
63
|
|
|
qs = self._valid_submissions().filter(state__in=[Submission.GRADED]) |
64
|
|
|
return qs |
65
|
|
|
|
66
|
|
|
def grading_url(self): |
67
|
|
|
''' |
68
|
|
|
Determines the teacher backend link to the filtered list of gradable submissions for this course. |
69
|
|
|
''' |
70
|
|
|
grading_url="%s?coursefilter=%u&statefilter=tobegraded"%( |
71
|
|
|
reverse('teacher:opensubmit_submission_changelist'), |
72
|
|
|
self.pk |
73
|
|
|
) |
74
|
|
|
return grading_url |
75
|
|
|
|
76
|
|
|
|
77
|
|
|
def authors(self): |
78
|
|
|
''' |
79
|
|
|
Queryset for all distinct authors this course had so far. Important for statistics. |
80
|
|
|
Note that this may be different from the list of people being registered for the course, |
81
|
|
|
f.e. when they submit something and the leave the course. |
82
|
|
|
''' |
83
|
|
|
qs = self._valid_submissions().values_list('authors',flat=True).distinct() |
84
|
|
|
return qs |
85
|
|
|
|
86
|
|
|
def _valid_submissions(self): |
87
|
|
|
qs = Submission.objects.filter(assignment__course=self).exclude(state=Submission.WITHDRAWN) |
88
|
|
|
return qs |
89
|
|
|
|
90
|
|
|
|