views.py 16.9 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
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

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

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

124
125
126
127
128
        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
129
130
131
132
133
        if flags:
            try:
                my_flag = Confirmation.objects.get(confirmer=requesting_student,
                                                   student=self.get_object())
            except Exception as e:
134
135
                print("Students are not supposed to be able to make more than one flag per student.")
                print(e)
136

Daniel W Bond's avatar
Daniel W Bond committed
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
        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
154
155
            # 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
156
157
158
159
160
161
            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
162

Daniel W Bond's avatar
Daniel W Bond committed
163
        context['shares'] = shares()
164
165
        context['same_floor'] = same_floor
        context['has_flagged'] = bool(flags)
Daniel W Bond's avatar
Daniel W Bond committed
166
167
        if flags:
            context['my_flag'] = my_flag
Daniel W Bond's avatar
Daniel W Bond committed
168
169
        return context

Daniel W Bond's avatar
Daniel W Bond committed
170

171
class DetailCurrentStudent(LoginRequiredMixin, DetailView):
172
173
174
175
176
    model = Student
    context_object_name = 'student'
    template_name = 'detailStudent.html'

    login_url = 'login'
177
178
179
180

    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
181

182
183
184
# changeable student settings
class DetailStudentSettings(LoginRequiredMixin, DetailView):
    model = Student
185
186
187
188
    context_object_name = 'student'
    template_name = 'studentSettings.html'

    login_url = 'login'
189

Daniel W Bond's avatar
Daniel W Bond committed
190

191
class DetailCurrentStudentSettings(LoginRequiredMixin, DetailView):
192
    model = Student
193
194
195
196
    context_object_name = 'student'
    template_name = 'studentSettings.html'

    login_url = 'login'
197
198
199

    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
200

201
# update a student, but FormView to allow name update on same page
202
class UpdateStudent(LoginRequiredMixin, FormValidMessageMixin, FormView):
203
    template_name = 'updateStudent.html'
204
    form_class = StudentUpdateForm
205
206
    login_url = 'login'

207
208
    form_valid_message = "Your profile was successfully updated!"

209
210
211
212
213
214
215
216
217
218
    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)

219
220
221
222
223
224
    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,
225
226
                                          'last_name': me.user.last_name,
                                          'gender': me.gender,
227
                                          'show_gender': me.show_gender,
228
229
230
                                          'room': pk_or_none(me, me.room),
                                          'privacy': me.privacy,
                                          'major': pk_or_none(me, me.major),
231
232
                                          'graduating_year': me.graduating_year,
                                          'on_campus': me.on_campus, })
233

234
        if me.recent_changes() > 2:
235
            form.fields['room'].widget = HiddenInput()
236
237
            form.fields['privacy'].widget = HiddenInput()
            form.fields['on_campus'].widget = HiddenInput()
238
239
        else:
            form.fields['room'].widget.user = self.request.user
240

241
242
243
244
245
        # 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'

246
        context['my_form'] = form
Daniel W Bond's avatar
Daniel W Bond committed
247

248
249
        return context

Daniel W Bond's avatar
Daniel W Bond committed
250
251
252
    @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):
253
254
        #for key, value in request.POST.iteritems():
            #print(key, value)
Daniel W Bond's avatar
Daniel W Bond committed
255
256
        return super(UpdateStudent, self).post(request, *args, **kwargs)

257
258
259
    def form_valid(self, form):
        me = Student.objects.get(user=self.request.user)

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

262
263
        #for key, value in form.data.iteritems():
            #print(key, value)
Daniel W Bond's avatar
Daniel W Bond committed
264

265
        current_room = me.room
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280

        # 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:
281
282
            form_room = None

283
        # note this is after the 'on campus' check
284
285
        if current_room != form_room:
            me.times_changed_room += 1
286
            Confirmation.objects.filter(student=me).delete()
287

288
        me.on_campus = on_campus
289
290
291
292
293
294
295
        me.room = form_room

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

296
297
298
        me.user.first_name = form.data['first_name']
        me.user.last_name = form.data['last_name']
        me.gender = form.data.getlist('gender')
299
        me.show_gender = strtobool(form.data.get('show_gender', 'False'))
300
        me.privacy = form.data['privacy']
301
302
        me.graduating_year = form.data['graduating_year']

303
304
305
306
307
308
        me.user.save()
        me.save()

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

    def get_success_url(self):
309
310
311
312
313

        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.')

314
315
        return reverse('detail_student',
                       kwargs={'slug':self.request.user.username})
316

317

318
# majors pages
319
class ListMajors(ListView):
Daniel W Bond's avatar
Daniel W Bond committed
320
321
322
323
324
    model = Major
    queryset = Major.objects.all().order_by('name')
    context_object_name = 'majors'
    template_name = 'list_majors.html'

325
326
327
328
329
330
331
332
333
334

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)
335
        requesting_student = Student.objects.get(user=self.request.user)
336

337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
	# 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'))
358

359
        # print(visible_by_neighbourhood)
360

361
362
363
364
365
366
367
368
369
        # 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():
370
	    context['%s_location_visible' % neighbourhood] = visible
371
        context['location_hidden'] = hidden
372
373

        return context
374
375
376
377
378
379
380
381
382
383
384
385
386
387
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


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

423
424
425
426
427
    @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)

428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
    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
450
    template_name = 'delete_confirmation.html'
451
452
453

    login_url = 'login'

Daniel W Bond's avatar
Daniel W Bond committed
454
455
456
    def get(self, request, *args, **kwargs):
        requester = Student.objects.get(user=self.request.user)
        confirmer = self.get_object().confirmer
457

Daniel W Bond's avatar
Daniel W Bond committed
458
459
460
461
        if not(requester == confirmer):
            return HttpResponseForbidden()
        else:
            return super(DeleteConfirmation, self).get(request, *args, **kwargs)
462
463
464

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