Passed
Push — master ( b1ab39...9c18ff )
by Peter
01:21
created

UserProfile.open_assignments()   B

Complexity

Conditions 5

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
dl 0
loc 21
rs 8.439
c 2
b 0
f 0
cc 5
1
from django.db import models, transaction
2
from django.contrib.auth.models import User
3
from django.utils import timezone
4
from django.shortcuts import get_object_or_404
5
from django.db.models.functions import Length
6
7
from .assignment import Assignment
8
from .course import Course
9
from .submission import Submission
10
from .studyprogram import StudyProgram
11
12
13
class UserProfile(models.Model):
14
    user = models.OneToOneField(User, related_name='profile')
15
    student_id = models.CharField(max_length=30, blank=True, null=True)
16
    courses = models.ManyToManyField(
17
        Course, blank=True, related_name='participants', limit_choices_to={'active__exact': True})
18
    study_program = models.ForeignKey(
19
        StudyProgram, blank=True, null=True, related_name='students')
20
21
    class Meta:
22
        app_label = 'opensubmit'
23
24
    def add_course_safe(self, id):
25
        '''
26
            Adds a course for the user after conducting a set of sanity checks.
27
            Return the title of the course or an exception.
28
        '''
29
        course = get_object_or_404(Course, pk=int(id), active=True)
30
        if course not in self.courses.all():
31
            self.courses.add(course)
32
        return course.title
33
34
    def can_see_future(self):
35
        '''
36
        Decides if this user is allowed to work with assignments that
37
        have their starting date in the future.
38
        '''
39
        return self.user.is_staff
40
41
    def tutor_courses(self):
42
        '''
43
            Returns the list of courses this user is tutor or owner for.
44
        '''
45
        tutoring = self.user.courses_tutoring.all().filter(active__exact=True)
46
        owning = self.user.courses.all().filter(active__exact=True)
47
        result = (tutoring | owning).distinct()
48
        return result
49
50
    def user_courses(self):
51
        '''
52
            Returns the list of courses this user is subscribed for,
53
            or owning, or tutoring.
54
            This leads to the fact that tutors and owners don't need
55
            course membership.
56
        '''
57
        registered = self.courses.filter(active__exact=True).distinct()
58
        return (self.tutor_courses() | registered).distinct()
59
60
    def open_assignments(self):
61
        '''
62
            Returns the list of open assignments from the
63
            viewpoint of this user.
64
        '''
65
        # Include only assignments with future, or no, hard deadline
66
        qs = Assignment.objects.filter(hard_deadline__gt=timezone.now(
67
        )) | Assignment.objects.filter(hard_deadline__isnull=True)
68
        # Include only assignments that are already published,
69
        # as long as you are not a tutor / course owner
70
        if not self.can_see_future():
71
            qs = qs.filter(publish_at__lt=timezone.now())
72
        # Include only assignments from courses that you are registered for
73
        qs = qs.filter(course__in=self.user_courses())
74
        # Ordering of resulting list
75
        qs = qs.annotate(deadline_isnull=Length('soft_deadline'))
76
        qs = qs.order_by('-deadline_isnull', 'soft_deadline',
77
                         '-gradingScheme', 'title')
78
        waiting_for_action = [subm.assignment for subm in self.user.authored.all(
79
        ).exclude(state=Submission.WITHDRAWN)]
80
        return [ass for ass in qs if ass not in waiting_for_action]
81
82
    def gone_assignments(self):
83
        '''
84
            Returns the list of past assignments the user did not submit for
85
            before the hard deadline.
86
        '''
87
        # Include only assignments with past hard deadline
88
        qs = Assignment.objects.filter(hard_deadline__lt=timezone.now())
89
        # Include only assignments from courses this user is registered for
90
        qs = qs.filter(course__in=self.user_courses())
91
        # Include only assignments this user has no submission for
92
        return qs.order_by('-hard_deadline')
93
94
95
def db_fixes(user):
96
    '''
97
        Fix users that already exist and never got a user profile attached.
98
        This may be user accounts that were created by the Django Social or manually by the admin.
99
100
        TODO: This belongs into a User post_save handler.
101
    '''
102
    profile, created = UserProfile.objects.get_or_create(user=user)
103
104
105
def user_unicode(self):
106
    '''
107
        Monkey patch for getting better user name stringification,
108
        user proxies did not make the job.
109
        Django's custom user model feature would have needed to be introduced
110
        before the first syncdb, which does not work for existing installations.
111
'''
112
    if self.email:
113
        shortened = self.email.split('@')[0]
114
        return '%s %s (%s@...)' % (self.first_name, self.last_name, shortened)
115
    elif self.first_name or self.last_name:
116
        return '%s %s' % (self.first_name, self.last_name)
117
    elif self.username:
118
        return '%s' % (self.username)
119
    else:
120
        return 'User %u' % (self.pk)
121
122
123
User.__str__ = user_unicode
124
125
126
@transaction.atomic
127
def move_user_data(primary, secondary):
128
    '''
129
        Moves all submissions and other data linked to the secondary user into the primary user.
130
        Nothing is deleted here, we just modify foreign user keys.
131
    '''
132
    # Update all submission authorships of the secondary to the primary
133
    submissions = Submission.objects.filter(authors__id=secondary.pk)
134
    for subm in submissions:
135
        if subm.submitter == secondary:
136
            subm.submitter = primary
137
        subm.authors.remove(secondary)
138
        subm.authors.add(primary)
139
        subm.save()
140
    # Transfer course registrations
141
    try:
142
        for course in secondary.profile.courses.all():
143
            primary.profile.courses.add(course)
144
            primary.profile.save()
145
    except UserProfile.DoesNotExist:
146
        # That's a database consistency problem, but he will go away anyway
147
        pass
148