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