views.py 17.5 KB
Newer Older
1
2
# standard library imports
from __future__ import absolute_import, print_function
3
import random
4
from distutils.util import strtobool
5
6
from operator import attrgetter
from itertools import chain
7
# core django imports
Daniel W Bond's avatar
Daniel W Bond committed
8
from django.shortcuts import get_object_or_404
9
from django.http import HttpResponseForbidden, HttpResponseRedirect, Http404
10
11
from django.views.generic import (CreateView, ListView, DetailView, UpdateView,
                                  FormView, DeleteView)
12
from django.core.urlresolvers import reverse
13
14
from django.contrib import messages
from django.utils.safestring import mark_safe
15
from django.forms.widgets import HiddenInput
16
# third party imports
17
from braces.views import LoginRequiredMixin, FormValidMessageMixin
18
from cas.views import login as cas_login
19
from ratelimit.decorators import ratelimit
20
# imports from your apps
Daniel W Bond's avatar
Daniel W Bond committed
21
22
from .models import Student, Major, Confirmation
from housing.models import Building, Floor, Room
23
from .forms import StudentUpdateForm
24

Daniel W Bond's avatar
Daniel W Bond committed
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
#########
bug_reporting = """Welcome back to SRCT Roomlist. This project is the
                   <a href="https://srct.gmu.edu/projects/">collaborative work
                   of students like you</a>. If you see anything amiss, or have ideas for
                   features or a better user experience, please send an email to
                   roomlist@lists.srct.gmu.edu, tweet
                   <a href="https://twitter.com/MasonSRCT/">@MasonSRCT</a>, or, for the
                   more technically experienced, review our
                   <a href="https://git.gmu.edu/srct/roomlist/issues">issues page</a>."""

privacy_reminder = """Welcome back to SRCT Roomlist. A friendly reminder you can change
                      your privacy settings at any time on your settings page by
                      clicking the cog in the upper right of your screen."""

disclaimer = """Welcome back to SRCT Roomlist. Just to be perfectly clear, this project
                is provided as a service by the
                <a href="https://gmu.collegiatelink.net/organization/srct">registered
                student organization</a>
                <a href="https://srct.gmu.edu/">Student-Run Computing and Technology</a>.
                We are not a part of <a href="http://housing.gmu.edu/">Mason Housing</a>:
                all information is voluntarily provided by participating students."""

whatsopen_plug = """Welcome back to SRCT Roomlist. Wondering what's open at this hour?
                    Check out another one of our
                    <a href="https://srct.gmu.edu/projects/">student-built and hosted</a>
                    projects: <a href="https://whatsopen.gmu.edu/">whatsopen.gmu.edu</a>."""

open_source = """Welcome back to SRCT Roomlist. For the curious at heart,
                 <a href="http://www.gnu.org/philosophy/free-sw.en.html">you can always
                 review</a> this project's
                 <a href="https://git.gmu.edu/srct/roomlist/tree/master">source code</a>.
                 Come <a href="https://srct.gmu.edu/">to a meeting</a> and learn how to
                 contribute!"""
59
#########
60
61
62

return_messages = [bug_reporting, privacy_reminder, disclaimer, whatsopen_plug, open_source]

63
64
65
66

def custom_cas_login(request, *args, **kwargs):
    response = cas_login(request, *args, **kwargs)
    # returns HttpResponseRedirect
67

68
69
    if request.user.is_authenticated():

70
71
72
        if not request.user.student.totally_done():

            if not request.user.student.completedName:
73
                return HttpResponseRedirect(reverse('welcomeName'))
74
            elif not request.user.student.completedPrivacy:
75
                return HttpResponseRedirect(reverse('welcomePrivacy'))
76
            elif not request.user.student.completedMajor:
77
                return HttpResponseRedirect(reverse('welcomeMajor'))
78
            elif not request.user.completedSocial:
79
                return HttpResponseRedirect(reverse('welcomeSocial'))
80
81
82
        else:
            welcome_back = random.choice(return_messages)
            messages.add_message(request, messages.INFO, mark_safe(welcome_back))
83
84
85
86

    return response


87
88
def on_the_same_floor(student, confirmer):
    if student == confirmer:
89
        # Student is confirmer
90
91
92
93
94
        return False
    student_floor = student.get_floor()
    confirmer_floor = confirmer.get_floor()
    # room hasn't been set yet
    if (student_floor is None) or (confirmer_floor is None):
95
        # one Student is None
96
97
        return False
    elif not(student_floor == confirmer_floor):
98
        # not the same floor
99
100
101
102
103
        return False
    else:
        return True


104
105
106
107
108
109
def pk_or_none(me, obj):
    if obj is None:
        return None
    else:
        return obj.pk

110

111
112
113
# details about the student
class DetailStudent(LoginRequiredMixin, DetailView):
    model = Student
114
115
116
117
    context_object_name = 'student'
    template_name = 'detailStudent.html'

    login_url = 'login'
Daniel W Bond's avatar
Daniel W Bond committed
118

119
120
121
122
123
124
125
126
127
128
129
    def get(self, request, *args, **kwargs):

        current_url = self.request.get_full_path()
        url_uname = current_url.split('/')[3]
        detailed_student = Student.objects.get(user__username=url_username)

        if (detailed_student in self.request.user.student.blocked_kids.all()):
            raise Http404
        else:
            return super(DetailStudent, self).get(request, *args, **kwargs)

Daniel W Bond's avatar
Daniel W Bond committed
130
131
132
    def get_context_data(self, **kwargs):
        context = super(DetailStudent, self).get_context_data(**kwargs)

133
        requesting_student = Student.objects.get(user=self.request.user)
Daniel W Bond's avatar
Daniel W Bond committed
134

135
136
137
138
139
        same_floor = on_the_same_floor(self.get_object(), requesting_student)

        flags = Confirmation.objects.filter(confirmer=requesting_student,
                                            student=self.get_object()).count()

Daniel W Bond's avatar
Daniel W Bond committed
140
141
142
143
144
        if flags:
            try:
                my_flag = Confirmation.objects.get(confirmer=requesting_student,
                                                   student=self.get_object())
            except Exception as e:
145
146
                print("Students are not supposed to be able to make more than one flag per student.")
                print(e)
147

Daniel W Bond's avatar
Daniel W Bond committed
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
        def onFloor():
            floor_status = False
            if requesting_student.get_floor() == self.get_object().get_floor():
                floor_status = True
            return floor_status

        def inBuilding():
            floor_status = False
            if requesting_student.get_building() == self.get_object().get_building():
                floor_status = True
            return floor_status

        def shares():
            student_shares = False
            # if the student's privacy is floor and the requesting user is on their floor
            if(self.get_object().privacy == 'floor') and onFloor():
                student_shares = True
Daniel W Bond's avatar
Daniel W Bond committed
165
166
            # if the student's privacy is building and the requesting users is
            # on their floor or in their building
Daniel W Bond's avatar
Daniel W Bond committed
167
168
169
170
171
172
            elif(self.get_object().privacy == 'building') and inBuilding():
                student_shares = True
            # if the student's privacy is set to 'student'
            elif(self.get_object().privacy == 'students'):
                student_shares = True
            return student_shares
Daniel W Bond's avatar
Daniel W Bond committed
173

Daniel W Bond's avatar
Daniel W Bond committed
174
        context['shares'] = shares()
175
176
        context['same_floor'] = same_floor
        context['has_flagged'] = bool(flags)
Daniel W Bond's avatar
Daniel W Bond committed
177
178
        if flags:
            context['my_flag'] = my_flag
Daniel W Bond's avatar
Daniel W Bond committed
179
180
        return context

Daniel W Bond's avatar
Daniel W Bond committed
181

182
class DetailCurrentStudent(LoginRequiredMixin, DetailView):
183
184
185
186
187
    model = Student
    context_object_name = 'student'
    template_name = 'detailStudent.html'

    login_url = 'login'
188
189
190
191

    def get_object(self):
        return get_object_or_404(Student, pk=self.request.session['_auth_user_id'])

Daniel W Bond's avatar
Daniel W Bond committed
192

193
194
195
# changeable student settings
class DetailStudentSettings(LoginRequiredMixin, DetailView):
    model = Student
196
197
198
199
    context_object_name = 'student'
    template_name = 'studentSettings.html'

    login_url = 'login'
200

Daniel W Bond's avatar
Daniel W Bond committed
201

202
class DetailCurrentStudentSettings(LoginRequiredMixin, DetailView):
203
    model = Student
204
205
206
207
    context_object_name = 'student'
    template_name = 'studentSettings.html'

    login_url = 'login'
208
209
210

    def get_object(self):
        return get_object_or_404(Student, pk=self.request.session['_auth_user_id'])
Daniel W Bond's avatar
Daniel W Bond committed
211

212
# update a student, but FormView to allow name update on same page
213
class UpdateStudent(LoginRequiredMixin, FormValidMessageMixin, FormView):
214
    template_name = 'updateStudent.html'
215
    form_class = StudentUpdateForm
216
217
    login_url = 'login'

218
219
    form_valid_message = "Your profile was successfully updated!"

220
221
222
223
224
225
226
227
228
229
    def get(self, request, *args, **kwargs):

        current_url = self.request.get_full_path()
        url_uname = current_url.split('/')[3]

        if not(url_uname == self.request.user.username):
            return HttpResponseForbidden()
        else:
            return super(UpdateStudent, self).get(request, *args, **kwargs)

230
231
232
233
234
235
    def get_context_data(self, **kwargs):
        context = super(UpdateStudent, self).get_context_data(**kwargs)

        me = Student.objects.get(user=self.request.user)

        form = StudentUpdateForm(initial={'first_name': me.user.first_name,
236
237
                                          'last_name': me.user.last_name,
                                          'gender': me.gender,
238
                                          'show_gender': me.show_gender,
239
240
                                          'room': pk_or_none(me, me.room),
                                          'privacy': me.privacy,
241
                                          'blocked_kids': me.blocked_kids.all(),
242
                                          'major': pk_or_none(me, me.major),
243
244
                                          'graduating_year': me.graduating_year,
                                          'on_campus': me.on_campus, })
245

246
        if me.recent_changes() > 2:
247
            form.fields['room'].widget = HiddenInput()
248
249
            form.fields['privacy'].widget = HiddenInput()
            form.fields['on_campus'].widget = HiddenInput()
250
251
        else:
            form.fields['room'].widget.user = self.request.user
252

253
254
255
256
257
        # bootstrap
        form.fields['first_name'].widget.attrs['class'] = 'form-control'
        form.fields['last_name'].widget.attrs['class'] = 'form-control'
        form.fields['graduating_year'].widget.attrs['class'] = 'form-control'

258
        context['my_form'] = form
Daniel W Bond's avatar
Daniel W Bond committed
259

260
261
        return context

Daniel W Bond's avatar
Daniel W Bond committed
262
263
264
    @ratelimit(key='user', rate='5/m', method='POST', block=True)
    @ratelimit(key='user', rate='10/d', method='POST', block=True)
    def post(self, request, *args, **kwargs):
265
266
        #for key, value in request.POST.iteritems():
            #print(key, value)
Daniel W Bond's avatar
Daniel W Bond committed
267
268
        return super(UpdateStudent, self).post(request, *args, **kwargs)

269
270
271
    def form_valid(self, form):
        me = Student.objects.get(user=self.request.user)

272
        #print("In form valid method!")
Daniel W Bond's avatar
Daniel W Bond committed
273

274
275
        #for key, value in form.data.iteritems():
            #print(key, value)
Daniel W Bond's avatar
Daniel W Bond committed
276

277
        current_room = me.room
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292

        # if you somehow got around the hidden widget, you're still outta luck
        if me.recent_changes() > 2:
            form_room = current_room
        else:
            try:
                form_room = Room.objects.get(pk=form.data['room'])
            except:
                form_room = None

        # casts to an integer, 0 or 1
        on_campus = strtobool(form.data.get('on_campus', 'True'))

        # no room if you move off campus
        if not on_campus:
293
294
            form_room = None

295
        # note this is after the 'on campus' check
296
297
        if current_room != form_room:
            me.times_changed_room += 1
298
            Confirmation.objects.filter(student=me).delete()
299

300
        me.on_campus = on_campus
301
302
303
304
305
306
307
        me.room = form_room

        try:
            me.major = Major.objects.get(pk=form.data['major'])
        except:
            me.major = None

308
309
310
        me.user.first_name = form.data['first_name']
        me.user.last_name = form.data['last_name']
        me.gender = form.data.getlist('gender')
311
        me.show_gender = strtobool(form.data.get('show_gender', 'False'))
312
        me.privacy = form.data['privacy']
313
        me.blocked_kids = form.data['blocked_kids']
314
        me.graduating_year = form.data['graduating_year']
315
316
317
318
319
320
        me.user.save()
        me.save()

        return super(UpdateStudent, self).form_valid(form)

    def get_success_url(self):
321
322
323
324
325

        if self.request.user.student.recent_changes() == 2:

            messages.add_message(self.request, messages.WARNING, 'To safeguard everyone\'s privacy, you have just one remaining room change for the semester before you\'ll need to send us an email at roomlist@lists.srct.gmu.edu.')

326
327
        return reverse('detail_student',
                       kwargs={'slug':self.request.user.username})
328

329

330
# majors pages
Daniel W Bond's avatar
Daniel W Bond committed
331
332
333
334
335
336
class ListMajors(LoginRequiredMixin, ListView):
    model = Major
    queryset = Major.objects.all().order_by('name')
    context_object_name = 'majors'
    template_name = 'list_majors.html'

337
338
339
340
341
342
343
344
345
346
347
348
    login_url = 'login'


class DetailMajor(LoginRequiredMixin, DetailView):
    model = Major
    context_object_name = 'major'
    template_name = 'detail_major.html'

    login_url = 'login'

    def get_context_data(self, **kwargs):
        context = super(DetailMajor, self).get_context_data(**kwargs)
349
        requesting_student = Student.objects.get(user=self.request.user)
350

351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
	# retrieve every room that has a student with the major in question
	neighbourhoods = ("aq", "ra", "sh")
	visible_by_neighbourhood = {}
	for neighbourhood in neighbourhoods:
	    rooms = [
		room
		for room in Room.objects.filter(floor__building__neighbourhood=neighbourhood)
		if room.student_set.filter(major=self.get_object())
	    ]

	    # identify if the student(s) in that room are visible to the requesting student
	    # 'chain' is necessary if there are multiple students in one room with the same major
	    #
	    # we sort each of the lists of students by their username
	    # as elsewhere, this is imperfect if a student changes their display name
	    # this is necessary as a separate step because .visible returns a list type
	    # note we're using '.' instead of '__', because who likes syntactical consistency
	    visible_by_neighbourhood[neighbourhood] = sorted(list(chain(*[
		Student.objects.visible(requesting_student, room)
		for room in rooms
	    ])), key=attrgetter('user.username'))
372

373
        # print(visible_by_neighbourhood)
374

375
376
377
378
379
380
381
382
383
        # see what students are left over (aren't visible)
        hidden = set(Student.objects.filter(major=self.get_object()).order_by('user__username'))
        # print(hidden)
	for visible in visible_by_neighbourhood.values():
            # print('visible', visible)
	    hidden = hidden.difference(set(visible))
            # print(hidden)

	for neighbourhood, visible in visible_by_neighbourhood.iteritems():
384
	    context['%s_location_visible' % neighbourhood] = visible
385
        context['location_hidden'] = hidden
386
387

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


class CreateConfirmation(LoginRequiredMixin, CreateView):
    model = Confirmation
    fields = []
    template_name = 'create_confirmation.html'

    login_url = 'login'

    def get(self, request, *args, **kwargs):

        current_url = self.request.get_full_path()
        # [u'', u'accounts', u'student', u'gmason', u'flag', u'']
        url_uname = current_url.split('/')[3]

        confirmer = Student.objects.get(user=self.request.user)
        student = Student.objects.get(slug=url_uname)

        flags = Confirmation.objects.filter(confirmer=confirmer,
                                            student=student).count()

        # you can't flag yourself
        if confirmer == student:
            return HttpResponseForbidden()

        # check that the confirmer is on the floor of the student
        if not on_the_same_floor(student, confirmer):
            return HttpResponseForbidden()

        # check if the confirmer has already flagged the student
        if flags >= 1:
            return HttpResponseForbidden()

        return super(CreateConfirmation, self).get(request, *args, **kwargs)


    def get_context_data(self, **kwargs):
        context = super(CreateConfirmation, self).get_context_data(**kwargs)

        # duplicated code
        current_url = self.request.get_full_path()
        url_uname = current_url.split('/')[3]

        student = Student.objects.get(slug=url_uname)

        context['student'] = student

        return context

437
438
439
440
441
    @ratelimit(key='user', rate='10/m', method='POST', block=True)
    @ratelimit(key='user', rate='50/d', method='POST', block=True)
    def post(self, request, *args, **kwargs):
        return super(CreateConfirmation, self).post(request, *args, **kwargs)

442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
    def form_valid(self, form):

        # duplicated code
        current_url = self.request.get_full_path()
        url_uname = current_url.split('/')[3]

        confirmer = Student.objects.get(user=self.request.user)
        student = Student.objects.get(slug=url_uname)

        form.instance.confirmer = confirmer
        form.instance.student = student

        return super(CreateConfirmation, self).form_valid(form)

    def get_success_url(self):
        # redirect to the flagged student page when saving
        return reverse('detail_student',
                       kwargs={'slug':self.object.student.slug})


class DeleteConfirmation(LoginRequiredMixin, DeleteView):
    model = Confirmation
Daniel W Bond's avatar
Daniel W Bond committed
464
    template_name = 'delete_confirmation.html'
465
466
467

    login_url = 'login'

Daniel W Bond's avatar
Daniel W Bond committed
468
469
470
    def get(self, request, *args, **kwargs):
        requester = Student.objects.get(user=self.request.user)
        confirmer = self.get_object().confirmer
471

Daniel W Bond's avatar
Daniel W Bond committed
472
473
474
475
        if not(requester == confirmer):
            return HttpResponseForbidden()
        else:
            return super(DeleteConfirmation, self).get(request, *args, **kwargs)
476
477
478

    def get_success_url(self):
        return reverse('detail_student',
Daniel W Bond's avatar
Daniel W Bond committed
479
                       kwargs={'slug':self.object.student.slug})