models.py 9.74 KB
Newer Older
1
# standard library imports
2
from __future__ import absolute_import, print_function
3
import hashlib
4
from datetime import date
5
# core django imports
Jason D Yeomans's avatar
Jason D Yeomans committed
6
from django.db import models
7
from django.utils import timezone
8
from model_utils.models import TimeStampedModel
9
from django.contrib.auth.models import User
Daniel W Bond's avatar
Daniel W Bond committed
10
from django.core.urlresolvers import reverse
11
from django.utils.text import slugify
12
from django.contrib import messages
13
14
# third party imports
from autoslug import AutoSlugField
Daniel W Bond's avatar
Daniel W Bond committed
15
from randomslugfield import RandomSlugField
16
from multiselectfield import MultiSelectField
17
18
from allauth.socialaccount.models import SocialAccount
# imports from your apps
19
from housing.models import Room, Floor, Building
20

Jason D Yeomans's avatar
Jason D Yeomans committed
21

22
class Major(TimeStampedModel):
Daniel W Bond's avatar
Daniel W Bond committed
23
    name = models.CharField(max_length=50)
24
    # I believe the longest is "Government and International Politics"
25

26
27
28
29
30
    slug = AutoSlugField(populate_from='name', always_update=True, unique=True)
    # always_update is set to support migrating from previous versions' slugs
    # which were originally random characters
    # on always_update, the slug is modified whenever the populated_from field changes
    # to update from previous versions, call .save() on all existing models
31

Daniel W Bond's avatar
Daniel W Bond committed
32
33
34
    def first_letter(self):
        return self.name and self.name[0] or ''

Daniel W Bond's avatar
Daniel W Bond committed
35
36
    def __str__(self):
        return self.name
Daniel W Bond's avatar
Daniel W Bond committed
37

Daniel W Bond's avatar
Daniel W Bond committed
38
39
40
    def __unicode__(self):
        return unicode(self.name)

Daniel W Bond's avatar
Daniel W Bond committed
41
    def get_absolute_url(self):
42
        return reverse('detail_major', kwargs={'slug': self.slug})
Daniel W Bond's avatar
Daniel W Bond committed
43

Daniel W Bond's avatar
Daniel W Bond committed
44
45
    class Meta:
        ordering = ['name']
Daniel W Bond's avatar
Daniel W Bond committed
46

Daniel W Bond's avatar
Daniel W Bond committed
47

Daniel W Bond's avatar
Daniel W Bond committed
48
49
class StudentQuerySet(models.query.QuerySet):
    def floor(self):
Daniel W Bond's avatar
Daniel W Bond committed
50
        return self.filter(privacy='floor')
Daniel W Bond's avatar
Daniel W Bond committed
51
52

    def building(self):
Daniel W Bond's avatar
Daniel W Bond committed
53
        return self.filter(privacy='building')
Daniel W Bond's avatar
Daniel W Bond committed
54
55

    def students(self):
Daniel W Bond's avatar
Daniel W Bond committed
56
        return self.filter(privacy='students')
Daniel W Bond's avatar
Daniel W Bond committed
57

Daniel W Bond's avatar
Daniel W Bond committed
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
    # when a student is not on a floor, but in a building
    def building_students(self):
        building = self.building()
        students = self.students()
        return list(building) + list(set(students) - set(building))

    # when a student is on a floor
    def floor_building_students(self):
        floor = self.floor()
        building = self.building()
        students = self.students()

        # using the function above results in UnboundLocalError excpetion
        building_students = list(building) + list(set(students) - set(building))

        return list(floor) + list(set(building_students) - set(floor))

75
76
77
78
79
80
    def visible(self, student, housing):
        if isinstance(housing, Room):
            rooms = [housing]
        elif isinstance(housing, Floor):
            rooms = Room.objects.filter(floor=housing).order_by('number')
        elif isinstance(housing, Building):
Daniel W Bond's avatar
Daniel W Bond committed
81
            rooms = Room.objects.filter(floor__building=housing).order_by('number')
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
        else:
            raise TypeError("'housing' arg must be Building, Floor, or Room")

        visible_students = []

        for room in rooms:
            if student in room.floor:
                visible_students.extend(self.filter(room=room).floor_building_students())
            elif student in room.floor.building:
                visible_students.extend(self.filter(room=room).building_students())
            else:
                visible_students.extend(self.filter(room=room).students())

        return visible_students

Daniel W Bond's avatar
Daniel W Bond committed
97

Daniel W Bond's avatar
Daniel W Bond committed
98
class StudentManager(models.Manager):
Daniel W Bond's avatar
Daniel W Bond committed
99
100
101

    # this 'duplication' allows for queryset chaining

Daniel W Bond's avatar
Daniel W Bond committed
102
    def get_queryset(self):
Daniel W Bond's avatar
Daniel W Bond committed
103
104
105
        return StudentQuerySet(self.model, using=self._db)

    def floor(self):
Daniel W Bond's avatar
Daniel W Bond committed
106
        return self.get_queryset().floor()
Daniel W Bond's avatar
Daniel W Bond committed
107
108

    def building(self):
Daniel W Bond's avatar
Daniel W Bond committed
109
        return self.get_queryset().building()
Daniel W Bond's avatar
Daniel W Bond committed
110
111

    def students(self):
Daniel W Bond's avatar
Daniel W Bond committed
112
        return self.get_queryset().students()
Daniel W Bond's avatar
Daniel W Bond committed
113
114

    def building_students(self):
Daniel W Bond's avatar
Daniel W Bond committed
115
        return self.get_queryset().building_students()
Daniel W Bond's avatar
Daniel W Bond committed
116

Daniel W Bond's avatar
Daniel W Bond committed
117
    def floor_building_students(self):
Daniel W Bond's avatar
Daniel W Bond committed
118
        return self.get_queryset().floor_building_students()
Daniel W Bond's avatar
Daniel W Bond committed
119

120
121
122
    def visible(self, student, housing):
        return self.get_queryset().visible(student, housing)

Daniel W Bond's avatar
Daniel W Bond committed
123

124
125
126
class Student(TimeStampedModel):
    user = models.OneToOneField(User)
    # Django user includes a username, password, email, first name, and last name
Daniel W Bond's avatar
Daniel W Bond committed
127
128
129
130
131
132
133
134
135
136
137

    FLOOR = 'floor'
    BUILDING = 'building'
    STUDENTS = 'students'

    PRIVACY_CHOICES = (
        (FLOOR, 'My Floor'),
        (BUILDING, 'My Building'),
        (STUDENTS, 'All Students'),
    )

Daniel W Bond's avatar
Daniel W Bond committed
138
139
140
    FEMALE = 'female'
    MALE = 'male'
    TRANS = 'trans'
141
142
    INTERSEX = 'intersex'
    GENDERLESS = 'genderless'
Daniel W Bond's avatar
Daniel W Bond committed
143
144
145
146
147
148
    OTHER = 'other'

    GENDER_CHOICES = (
        (FEMALE, 'female'),
        (MALE, 'male'),
        (TRANS, 'trans'),
149
150
        (INTERSEX, 'intersex'),
        (GENDERLESS, 'genderless'),
Daniel W Bond's avatar
Daniel W Bond committed
151
152
153
154
        (OTHER, 'other'),
    )

    # selectmultiple in forms
155
    gender = MultiSelectField(max_length=50, choices=GENDER_CHOICES, blank=True)
156
    show_gender = models.BooleanField(default=False)
Daniel W Bond's avatar
Daniel W Bond committed
157

Daniel W Bond's avatar
Daniel W Bond committed
158
159
    privacy = models.CharField(max_length=100, choices=PRIVACY_CHOICES, default=FLOOR)

160
    on_campus = models.BooleanField(default=True)
161
    room = models.ForeignKey(Room, null=True, blank=True)
162

163
    major = models.ForeignKey('Major', related_name='major', null=True, blank=True)
164

165
    times_changed_room = models.PositiveIntegerField(default=0)
166

167
168
169
    current_year = date.today().year
    graduating_year = models.IntegerField(default=current_year, blank=True)

Daniel W Bond's avatar
Daniel W Bond committed
170
    # from when first logged in through peoplefinder, stored for later
171
172
    original_major = models.ForeignKey('Major', related_name='original_major',
                                       null=True, blank=True)
173
174
    original_first_name = models.CharField(max_length=100, blank=True)
    original_last_name = models.CharField(max_length=100, blank=True)
Daniel W Bond's avatar
Daniel W Bond committed
175

176
177
    # social media accounts

178
    # welcome walkthrough completion
179
180
181
182
    completedName = models.BooleanField(default=False)
    completedPrivacy = models.BooleanField(default=False)
    completedMajor = models.BooleanField(default=False)
    completedSocial = models.BooleanField(default=False)
183

184
185
    slug = AutoSlugField(populate_from='user', unique=True)

Daniel W Bond's avatar
Daniel W Bond committed
186
187
    objects = StudentManager()

188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
    # this doesn't take into account superseniors or graduate students or negative values
    # hence private method
    def _get_class(self):
        time_to_graduate = self.graduating_year - self.current_year
        if time_to_graduate >= 4:
            return "freshman"
        elif time_to_graduate == 3:
            return "sophomore"
        elif time_to_graduate == 2:
            return "junior"
        elif time_to_graduate == 1:
            return "freshman"
        else:
            return "magic"

203
    def recent_changes(self):
204
205
206
207
208
209
210
        # part of TimeStampedModel
        now = timezone.now()
        created = self.created

        # could make this more formal with dateutil, but...
        days = (now - created).days

211
212
        # must be int-- floor function
        third_years = (days / (30 * 4)) + 1
213

214
        return (self.times_changed_room / third_years)
215

Daniel W Bond's avatar
Daniel W Bond committed
216
    def get_floor(self):
217
218
219
220
221
        try:
            floor = self.room.floor
            return floor
        except AttributeError:
            return None
Daniel W Bond's avatar
Daniel W Bond committed
222
223

    def get_building(self):
224
225
226
227
228
        try:
            building = self.room.floor.building
            return building
        except AttributeError:
            return None
Daniel W Bond's avatar
Daniel W Bond committed
229

230
231
232
233
234
235
    def totally_done(self):
        if self.completedName and self.completedPrivacy and self.completedMajor and self.completedSocial:
            return True
        else:
            return False

Jason D Yeomans's avatar
Jason D Yeomans committed
236
    def profile_image_url(self):
237
        fb_uid = SocialAccount.objects.filter(user=self.user.id, provider='facebook')
238
        #print("profile_image")
Jason D Yeomans's avatar
Jason D Yeomans committed
239

Daniel W Bond's avatar
Daniel W Bond committed
240
        if len(fb_uid) > 0:
241
            return "https://graph.facebook.com/{}/picture?width=175&height=175".format(fb_uid[0].uid)
Jason D Yeomans's avatar
Jason D Yeomans committed
242

243
        return "https://secure.gravatar.com/avatar/{}?s=175&d=mm".format(hashlib.md5(self.user.email).hexdigest())
Jason D Yeomans's avatar
Jason D Yeomans committed
244

Daniel W Bond's avatar
Daniel W Bond committed
245
    def get_absolute_url(self):
Daniel W Bond's avatar
Daniel W Bond committed
246
        return reverse('detail_student', kwargs={'slug': self.slug})
Daniel W Bond's avatar
Daniel W Bond committed
247

248
249
250
    def get_flag_count(self):
        my_flag_num = Confirmation.objects.filter(student=self, lives_there=False).count()
        return my_flag_num
251
    
252
253
    def get_first_name_or_uname(self):
        if not(self.user.get_short_name()):
254
            return self.user.username
255
256
257
        else:
            return self.user.get_short_name()
    
258
259
    def get_last_name_or_uname(self):
        if not(self.user.last_name):
260
            return self.user.username
261
262
263
        else:
            return self.user.last_name
    
264
    def get_full_name_or_uname(self):
265
266
        if not(self.user.get_full_name()):
            return self.user.username
267
        else:
268
            return self.user.get_full_name()
269

270
271
272
273
274
275
276
277
    def is_noob(self):
        now = timezone.now()
        days = (now - self.created).days
        if days > 2:  # more than two days
            return False
        else:
            return True

Daniel W Bond's avatar
Daniel W Bond committed
278
279
280
    class Meta:
        ordering = ['user']

281
282
    def __str__(self):              # __unicode__ on Python 2
        return self.user.username
Daniel W Bond's avatar
Daniel W Bond committed
283

Daniel W Bond's avatar
Daniel W Bond committed
284
285
    def __unicode__(self):
        return unicode(self.user.username)
286
    
287
    # def save(self, *args, **kwargs):
Daniel W Bond's avatar
Daniel W Bond committed
288
289
290
291
292
        #print('we be savin\'!')
        #from django.db.models.signals import pre_save, post_save
        #for signal in [pre_save, post_save]:
            #print(signal, signal.receivers)
        #super(Student, self).save(*args, **kwargs)
293

Daniel W Bond's avatar
Daniel W Bond committed
294

Daniel W Bond's avatar
Daniel W Bond committed
295
296
297
298
299
300
class Confirmation(TimeStampedModel):

    confirmer = models.ForeignKey(Student, related_name='confirmer_set')
    student = models.ForeignKey(Student, related_name='student_set')

    lives_there = models.BooleanField(default=False)
Daniel W Bond's avatar
Daniel W Bond committed
301
    # is RA? -- for later
Daniel W Bond's avatar
Daniel W Bond committed
302
303
304
305

    slug = RandomSlugField(length=6)

    def __unicode__(self):
Daniel W Bond's avatar
Daniel W Bond committed
306
307
308
309
        if self.lives_there:  # implicitly is True
            return "%s Confirmed %s" % (self.confirmer.user.username, self.student.user.username)
        else:  # implicitly is False
            return "%s Flagged %s" % (self.confirmer.user.username, self.student.user.username)