views.py 19.9 KB
Newer Older
1 2
# standard library imports
from __future__ import absolute_import, print_function
3
from distutils.util import strtobool
4 5
from collections import OrderedDict
from itertools import groupby
6
# core django imports
7
from django.http import HttpResponseForbidden, HttpResponseRedirect, Http404
Daniel W Bond's avatar
pep8 me  
Daniel W Bond committed
8
from django.views.generic import CreateView, ListView, DetailView, FormView, DeleteView
9
from django.core.urlresolvers import reverse
10 11
from django.contrib import messages
from django.utils.safestring import mark_safe
12
from django.forms.widgets import HiddenInput
Daniel W Bond's avatar
Daniel W Bond committed
13
from django.core.mail import get_connection
14
from django.core.exceptions import ObjectDoesNotExist
15
# third party imports
16
from braces.views import LoginRequiredMixin, FormValidMessageMixin
17
from ratelimit.decorators import ratelimit
18
# imports from your apps
Daniel W Bond's avatar
Daniel W Bond committed
19
from .models import Student, Major, Confirmation
20
from .forms import StudentUpdateForm, FarewellFeedbackForm
21
from housing.models import Room
22 23
from core.utils import (shadowbanning, on_the_same_floor, pk_or_none, create_email,
                        no_nums)
24 25


26 27 28
# details about the student
class DetailStudent(LoginRequiredMixin, DetailView):
    model = Student
29
    context_object_name = 'student'
Daniel W Bond's avatar
Daniel W Bond committed
30
    template_name = 'detail_student.html'
31 32

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

34 35 36 37
    def get(self, request, *args, **kwargs):

        current_url = self.request.get_full_path()
        url_uname = current_url.split('/')[3]
38 39 40 41
        try:
            detailed_student = Student.objects.get(user__username=url_uname)
        except ObjectDoesNotExist:
            raise Http404
42 43 44 45 46 47

        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
48 49 50
    def get_context_data(self, **kwargs):
        context = super(DetailStudent, self).get_context_data(**kwargs)

51
        requesting_student = self.request.user.student
Daniel W Bond's avatar
Daniel W Bond committed
52

53 54 55 56 57
        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
58 59 60 61
        if flags:
            try:
                my_flag = Confirmation.objects.get(confirmer=requesting_student,
                                                   student=self.get_object())
62
            # students are not supposed to be able to make more than one flag per student
Daniel W Bond's avatar
Daniel W Bond committed
63
            except Exception as e:
64
                print(e)
65

Daniel W Bond's avatar
pep8 me  
Daniel W Bond committed
66
        # recognizably too complex
Daniel W Bond's avatar
Daniel W Bond committed
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
        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
84 85
            # 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
86 87 88 89 90 91
            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
92

Daniel W Bond's avatar
Daniel W Bond committed
93
        context['shares'] = shares()
94 95
        context['same_floor'] = same_floor
        context['has_flagged'] = bool(flags)
Daniel W Bond's avatar
Daniel W Bond committed
96 97
        if flags:
            context['my_flag'] = my_flag
Daniel W Bond's avatar
Daniel W Bond committed
98 99
        return context

Daniel W Bond's avatar
Daniel W Bond committed
100

101
# update a student, but FormView to allow name update on same page
102
class UpdateStudent(LoginRequiredMixin, FormValidMessageMixin, FormView):
103
    template_name = 'update_student.html'
104
    form_class = StudentUpdateForm
105 106
    login_url = 'login'

107 108
    form_valid_message = "Your profile was successfully updated!"

109 110 111 112
    def get(self, request, *args, **kwargs):

        current_url = self.request.get_full_path()
        url_uname = current_url.split('/')[3]
113 114 115 116
        try:
            student = Student.objects.get(user__username=url_uname)
        except ObjectDoesNotExist:
            raise Http404
117 118

        if not(url_uname == self.request.user.username):
119 120
            return HttpResponseRedirect(reverse('update_student',
                                                kwargs={'slug': self.request.user.username}))
121 122 123
        else:
            return super(UpdateStudent, self).get(request, *args, **kwargs)

124 125
    def get_initial(self):
        initial = super(UpdateStudent, self).get_initial()
126

127
        me = self.request.user.student
128
        majors = [pk_or_none(me, major) for major in me.major.all()]
129

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
        initial = {'first_name': me.user.first_name,
                   'last_name': me.user.last_name,
                   'gender': me.gender,
                   'show_gender': me.show_gender,
                   'room': pk_or_none(me, me.room),
                   'privacy': me.privacy,
                   'blocked_kids': me.blocked_kids.all(),
                   'major': majors,
                   'graduating_year': me.graduating_year,
                   'on_campus': me.on_campus, }

        return initial

    def get_form(self):
        form = super(UpdateStudent, self).get_form(StudentUpdateForm)

        me = self.request.user.student
147

148
        form.fields['blocked_kids'].queryset = Student.objects.exclude(user=self.request.user)
149
        form.fields['room'].widget.user = self.request.user
150

151
        if me.recent_changes() > 2:
152
            form.fields['room'].required = False
153
            form.fields['room'].widget = HiddenInput()
154
            form.fields['on_campus'].required = False
155
            form.fields['on_campus'].widget = HiddenInput()
156

157 158
        # RAs and RDs cannot move off campus
        if me.is_staff():
159
            form.fields['on_campus'].required = False
160 161
            form.fields['on_campus'].disabled = True

162
        return form
163

Daniel W Bond's avatar
Daniel W Bond committed
164 165 166
    @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):
Daniel W Bond's avatar
pep8 me  
Daniel W Bond committed
167
        # for key, value in request.POST.iteritems():
168
        #     print(key, value)
Daniel W Bond's avatar
Daniel W Bond committed
169 170
        return super(UpdateStudent, self).post(request, *args, **kwargs)

171
    def form_valid(self, form):
172
        me = self.request.user.student
173

Daniel W Bond's avatar
pep8 me  
Daniel W Bond committed
174
        # print("In form valid method!")
Daniel W Bond's avatar
Daniel W Bond committed
175

Daniel W Bond's avatar
pep8 me  
Daniel W Bond committed
176
        # for key, value in form.data.iteritems():
177
        #     print(key, value)
Daniel W Bond's avatar
Daniel W Bond committed
178

179
        current_room = me.room
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194

        # 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:
195 196
            form_room = None

197
        # note this is after the 'on campus' check
198 199
        if current_room != form_room:
            me.times_changed_room += 1
200
            Confirmation.objects.filter(student=me).delete()
201

202
        me.on_campus = on_campus
203 204
        me.room = form_room

205 206 207 208 209 210 211
        # if you don't live on campus you can't limit yourself to a nonexistent
        # floor or room
        if not(me.on_campus):
            me.privacy = 'students'
        else:
            me.privacy = form.data['privacy']

212 213 214 215
        # if you are an RA or an RD, you live on campus
        if me.is_staff():
            me.on_campus = True

216
        try:
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
            # in case someone disabled the js, limit processing to only the first
            # two majors passed by the user
            # we also eliminate the potential a student manipulates the form to
            # pass in two majors of the same type by casting to a set
            form_major_pks = set(form.data.getlist('major')[:2])
            # retrieve the major objects from the list of pk strings
            form_majors = [Major.objects.get(pk=pk) for pk in form_major_pks]
            # print(form_majors)
            # iterate over a student's current majors
            for current_major in me.major.all():
                # remove m2m relationship if not in majors from form
                if current_major not in form_majors:
                    me.major.remove(current_major)
            # iterate over the majors in the form
            for form_major in form_majors:
                # add new m2m relationship to student
                if form_major not in me.major.all():
                    me.major.add(form_major)
235
        except:
236 237
            # don't change majors
            pass
238

239
        # replicate the same thing for the other m2m field
240
        try:
241 242 243
            form_blocked_pks = set(form.data.getlist('blocked_kids'))
            current_blocked = me.blocked_kids.all()
            # most people will not being blocking other students
244
            if form_blocked_pks or current_blocked:
245 246 247 248 249 250 251
                form_blocked = [Student.objects.get(pk=pk) for pk in form_blocked_pks]
                for current_block in current_blocked:
                    if current_block not in form_blocked:
                        me.blocked_kids.remove(current_block)
                for form_block in form_blocked:
                    if form_block not in current_blocked:
                        me.blocked_kids.add(form_block)
252
        except:
253
            pass
254

255 256
        me.user.first_name = no_nums(form.data['first_name'])
        me.user.last_name = no_nums(form.data['last_name'])
257
        me.gender = form.data.getlist('gender')
258
        me.show_gender = strtobool(form.data.get('show_gender', 'False'))
259
        me.graduating_year = form.data['graduating_year']
260 261 262 263 264 265
        me.user.save()
        me.save()

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

    def get_success_url(self):
266 267 268

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

269 270 271 272
            msg = """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."""
            messages.add_message(self.request, messages.WARNING, msg)
273

274
        return reverse('detail_student',
Daniel W Bond's avatar
pep8 me  
Daniel W Bond committed
275
                       kwargs={'slug': self.request.user.username})
276

277

278 279 280
class DeleteStudent(FormView):
    form_class = FarewellFeedbackForm
    template_name = 'delete_student.html'
281

282 283 284 285
    def get(self, request, *args, **kwargs):

        current_url = self.request.get_full_path()
        url_uname = current_url.split('/')[3]
286 287 288 289
        try:
            student = Student.objects.get(user__username=url_uname)
        except ObjectDoesNotExist:
            raise Http404
290

291 292 293 294 295 296 297 298 299
        if student.is_staff():
            staff_msg = """To delete your account, contact us if you are no longer a
                           Resident Advisor or Director at
                           <a href="mailto:roomlist@lists.srct.gmu.edu">
                           roomlist@lists.srct.gmu.edu</a>."""
            messages.add_message(self.request, messages.ERROR, mark_safe(staff_msg))
            return HttpResponseRedirect(reverse('update_student',
                                                kwargs={'slug': self.request.user.username}))
        elif not(url_uname == self.request.user.username):
300 301
            return HttpResponseRedirect(reverse('delete_student',
                                                kwargs={'slug': self.request.user.username}))
302
        else:
303
            return super(DeleteStudent, self).get(request, *args, **kwargs)
304

305
    def get_context_data(self, **kwargs):
306
        context = super(DeleteStudent, self).get_context_data(**kwargs)
307

308
        me = self.request.user.student
309

310
        context['student'] = me
311 312 313

        return context

Daniel W Bond's avatar
Daniel W Bond committed
314
    def form_valid(self, form):
315 316 317
        user = self.request.user
        student = self.request.user.student

318 319 320 321 322 323 324 325 326 327 328 329
        # we're using this api because opening smtp connections is taxing in
        # that it takes time-- we want to send both emails at once without
        # having to log in and out and back in and out again
        connection = get_connection()

        # send email to the student
        text_path = 'email/farewell.txt'
        html_path = 'email/farewell.html'

        if form.cleaned_data['leaving']:
            context = {
                'student_name': student.get_first_name_or_uname,
330
                'special_message': "We're glad you gave our service a try."
331
            }
332
        else:
333 334 335 336
            context = {
                'student_name': student.get_first_name_or_uname,
                'special_message': "We wish you luck in your time after Mason!"
            }
337

338 339
        subject = "You successfully deleted your account on Roomlist"
        to = user.email
340

341
        student_email = create_email(text_path, html_path, subject, to, context)
342

343 344 345 346
        # send feedback to the admins if there is feedback to send
        if form.cleaned_data['feedback']:
            text_path = 'email/feedback.txt'
            html_path = 'email/feedback.html'
347

348
            date_text = student.created.strftime('%A, %B %d, %Y')
349

350 351 352 353
            if form.cleaned_data['leaving']:
                leaving = ""
            else:
                leaving = "not"
354

355 356 357 358 359 360
            context = {
                'student_name':  student.get_first_name_or_uname,
                'signup_date': date_text,
                'leaving': leaving,
                'feedback': form.cleaned_data['feedback']
            }
361

362 363
            subject = "Feedback from Roomlist account deletion"
            to = 'roomlist@lists.srct.gmu.edu'
364

365
            feedback_email = create_email(text_path, html_path, subject, to, context)
366

367
            connection.send_messages([student_email, feedback_email])
368
        else:
369
            connection.send_messages([student_email])
370

371 372
        # yes, we do have to manually close the connection
        connection.close()
373

374 375 376 377 378
        # delete both the student object and the student object
        confirmations = Confirmation.objects.filter(confirmer=student)
        if confirmations:
            for confirmation in confirmations:
                confirmation.delete()
379 380
        student.delete()
        user.delete()
381

382
        return super(DeleteStudent, self).form_valid(form)
383

384
    def get_success_url(self):
385
        return reverse('homepage')
386

Daniel W Bond's avatar
Daniel W Bond committed
387

388
# majors pages
389
class ListMajors(ListView):
Daniel W Bond's avatar
Daniel W Bond committed
390 391 392 393 394
    model = Major
    queryset = Major.objects.all().order_by('name')
    context_object_name = 'majors'
    template_name = 'list_majors.html'

395 396 397 398 399 400 401 402 403 404

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)
405
        me = self.request.user.student
406

407 408
        # all students in the major-- needs to be ordered for groupby
        major_students = Student.objects.filter(major__in=[self.get_object()]).order_by('graduating_year')
409

410
        students_by_year = OrderedDict()  # we're ordering from senior on down
411

412 413 414 415
        for year, students in groupby(major_students, lambda student: student.graduating_year):
            student_list = list(students)  # students without the casting is an iterator
            visible_students = shadowbanning(me, student_list)  # remove blocked students
            students_by_year[year] = visible_students  # remembers insertion order
416

417
        context['students_by_year'] = students_by_year
418 419

        return context
420 421 422


class CreateConfirmation(LoginRequiredMixin, CreateView):
423 424 425
    """Students on the same floor may flag one another.

    This is our attempt at crowdsourced verification."""
426 427 428 429 430 431 432 433 434
    model = Confirmation
    fields = []
    template_name = 'create_confirmation.html'

    login_url = 'login'

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

        current_url = self.request.get_full_path()
435 436 437
        # [u'', u'accounts', u'student', u'gmason', u'flag', u'confirmer']
        confirmer_uname = current_url.split('/')[3]
        student_uname = current_url.split('/')[5]
438 439 440 441 442 443 444
        try:
            confirmer = Student.objects.get(user__username=confirmer_uname)
            student = Student.objects.get(user__username=student_uname)
            flags = Confirmation.objects.filter(confirmer=confirmer,
                                                student=student).count()
        except ObjectDoesNotExist:
            raise Http404
445 446 447

        # you can't flag yourself
        if confirmer == student:
448
            raise Http404
449 450 451 452 453 454 455 456 457

        # 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()

458 459 460
        # you can't see the page if the person has banned you
        if confirmer in student.blocked_kids.all():
            raise Http404
461 462 463 464 465 466 467 468

        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()
469
        url_uname = current_url.split('/')[5]
470 471 472 473 474 475 476

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

        context['student'] = student

        return context

477 478 479 480 481
    @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)

482 483 484 485
    def form_valid(self, form):

        # duplicated code
        current_url = self.request.get_full_path()
486
        url_uname = current_url.split('/')[5]
487 488 489 490 491 492 493 494 495 496 497 498

        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',
Daniel W Bond's avatar
pep8 me  
Daniel W Bond committed
499
                       kwargs={'slug': self.object.student.slug})
500 501 502 503


class DeleteConfirmation(LoginRequiredMixin, DeleteView):
    model = Confirmation
Daniel W Bond's avatar
Daniel W Bond committed
504
    template_name = 'delete_confirmation.html'
505 506 507

    login_url = 'login'

Daniel W Bond's avatar
Daniel W Bond committed
508
    def get(self, request, *args, **kwargs):
509 510
        requester = self.request.user.student

511 512
        current_url = self.request.get_full_path()
        confirmer_uname = current_url.split('/')[3]
513
        # not catching exceptions here; that's handled in get_object
Daniel W Bond's avatar
Daniel W Bond committed
514
        confirmer = Student.objects.get(user__username=confirmer_uname)
515

516
        # only the person who created the confirmation may delete it
Daniel W Bond's avatar
Daniel W Bond committed
517 518
        if not(requester == confirmer):
            return HttpResponseForbidden()
519
        # however, if the confirmation just flat out doesn't exist...
Daniel W Bond's avatar
Daniel W Bond committed
520
        else:
521 522 523 524 525 526
            try:
                confirmer = self.get_object().confirmer
            except ObjectDoesNotExist:
                raise Http404
            else:
                return super(DeleteConfirmation, self).get(request, *args, **kwargs)
527

528 529 530 531 532 533
    def get_object(self):
        current_url = self.request.get_full_path()
        # [u'', u'accounts', u'student', u'gmason', u'flag', u'confirmer', delete]
        confirmer_uname = current_url.split('/')[3]
        student_uname = current_url.split('/')[5]

534 535 536 537 538 539
        try:
            confirmer = Student.objects.get(user__username=confirmer_uname)
            student = Student.objects.get(user__username=student_uname)
            confirmation = Confirmation.objects.get(confirmer=confirmer, student=student)
        except ObjectDoesNotExist:
            raise Http404
540
        return confirmation
541 542 543

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