source: asadb/forms/views.py @ 5e018d9

space-accessstablestage
Last change on this file since 5e018d9 was 5e018d9, checked in by Alex Dehnert <adehnert@…>, 13 years ago

Fix spelling of midway permission

  • Property mode set to 100644
File size: 24.3 KB
Line 
1import collections
2import csv
3import datetime
4import StringIO
5
6from django.conf import settings
7from django.contrib.auth.decorators import user_passes_test, login_required, permission_required
8from django.core.exceptions import PermissionDenied
9from django.views.generic import list_detail, ListView, DetailView
10from django.shortcuts import render_to_response, get_object_or_404
11from django.template import RequestContext
12from django.template import Context, Template
13from django.template.loader import get_template
14from django.http import Http404, HttpResponseRedirect, HttpResponse
15from django.core.urlresolvers import reverse
16from django.core.mail import EmailMessage, mail_admins
17from django.forms import FileField
18from django.forms import Form
19from django.forms import ModelForm
20from django.forms import ModelChoiceField, ModelMultipleChoiceField
21from django.forms import ValidationError
22from django.db import connection
23from django.db.models import Q, Count
24
25import django_filters
26
27import forms.models
28import groups.models
29import groups.views
30import util.emails
31
32#################
33# GENERIC VIEWS #
34#################
35
36class SelectGroupForm(Form):
37    group = ModelChoiceField(queryset=groups.models.Group.objects.all())
38    def __init__(self, *args, **kwargs):
39        queryset = None
40        if 'queryset' in kwargs:
41            queryset = kwargs['queryset']
42            del kwargs['queryset']
43        super(SelectGroupForm, self).__init__(*args, **kwargs)
44        if queryset is not None:
45            self.fields["group"].queryset = queryset
46
47def select_group(request, url_name_after, url_args=[], pagename='homepage', queryset=None, title="", msg=""):
48    if request.method == 'POST': # If the form has been submitted...
49        # A form bound to the POST data
50        form = SelectGroupForm(request.POST, queryset=queryset, )
51        if form.is_valid(): # All validation rules pass
52            group = form.cleaned_data['group'].id
53            return HttpResponseRedirect(reverse(url_name_after, args=url_args+[group],)) # Redirect after POST
54    else:
55        form = SelectGroupForm(queryset=queryset, ) # An unbound form
56
57    if not title: title = "Select group"
58    context = {
59        'form':form,
60        'title':title,
61        'msg':msg,
62        'pagename':pagename,
63    }
64    return render_to_response('forms/select.html', context, context_instance=RequestContext(request), )
65
66#############################
67# FIRST-YEAR SUMMER MAILING #
68#############################
69
70@login_required
71def fysm_by_years(request, year, category, ):
72    if year is None: year = datetime.date.today().year
73    queryset = forms.models.FYSM.objects.filter(year=year).order_by('group__name')
74    category_obj = None
75    category_name = 'main'
76    if category != None:
77        category_obj = get_object_or_404(forms.models.FYSMCategory, slug=category)
78        category_name = category_obj.name
79        queryset = queryset.filter(categories=category_obj)
80    forms.models.FYSMView.record_metric(request=request, fysm=None, year=year, page=category_name, )
81    categories = forms.models.FYSMCategory.objects.all()
82    return list_detail.object_list(
83        request,
84        queryset=queryset,
85        template_name="fysm/fysm_listing.html",
86        template_object_name="fysm",
87        extra_context={
88            "year": year,
89            "pagename": "fysm",
90            "category": category_obj,
91            "categories": categories,
92        }
93    )
94
95@login_required
96def fysm_view(request, year, submission, ):
97    submit_obj = get_object_or_404(forms.models.FYSM, pk=submission,)
98    all = forms.models.FYSM.objects.only("id", "display_name", )
99    try:
100        prev = all.filter(display_name__lt=submit_obj.display_name).order_by("-display_name")[0]
101    except IndexError:
102        prev = None
103    try:
104        next = all.filter(display_name__gt=submit_obj.display_name).order_by("display_name")[0]
105    except IndexError:
106        next = None
107    forms.models.FYSMView.record_metric(request=request, fysm=submit_obj, year=year, page="detail", )
108    return list_detail.object_detail(
109        request,
110        forms.models.FYSM.objects,
111        object_id=submission,
112        template_name="fysm/fysm_detail.html",
113        template_object_name="fysm",
114        extra_context={
115            "year": year,
116            "pagename": "fysm",
117            "prev": prev,
118            "next": next,
119        },
120    )
121
122def fysm_link(request, year, link_type, submission, ):
123    submit_obj = get_object_or_404(forms.models.FYSM, pk=submission,)
124    if submit_obj.year != int(year):
125        raise Http404("Year mismatch: fysm.year='%s', request's year='%s'" % (submit_obj.year, year, ))
126    if link_type == 'join':
127        url = submit_obj.join_url
128    elif link_type == 'website':
129        url = submit_obj.website
130    else:
131        raise Http404("Unknown link type")
132    forms.models.FYSMView.record_metric(request=request, fysm=submit_obj, year=year, page=link_type, )
133    return HttpResponseRedirect(url)
134
135def select_group_fysm(request, ):
136    qobj = Q(activity_category__isnull = True) | ~(Q(activity_category__name='Dorm') | Q(activity_category__name='FSILG'))
137    queryset = groups.models.Group.active_groups.filter(qobj)
138    return select_group(
139        request,
140        url_name_after='fysm-manage',
141        pagename='fysm',
142        queryset=queryset,
143    )
144
145class FYSMRequestForm(ModelForm):
146    class Meta:
147        model = forms.models.FYSM
148        fields = (
149            'display_name',
150            'website',
151            'join_url',
152            'contact_email',
153            'description',
154            'logo',
155            'slide',
156            'tags',
157            'categories',
158        )
159
160    def clean_display_name(self, ):
161        name = self.cleaned_data['display_name']
162        if ',' in name:
163            raise ValidationError("""In general, commas in a display name are a mistake and will look bad (group names like "Punctuation Society, MIT" should probably be "Punctuation Society"). If you do want a comma, contact asa-fysm@mit.edu and we'll put it in for you.""")
164        return name
165
166    def clean_description(self, ):
167        description = self.cleaned_data['description']
168        length = len(description)
169        if length > 400:
170            raise ValidationError("Descriptions are capped at 400 characters, and this one is %d characters." % (length, ))
171        return description
172
173@login_required
174def fysm_manage(request, group, ):
175    year = datetime.date.today().year
176    group_obj = get_object_or_404(groups.models.Group, pk=group)
177
178    initial = {}
179    try:
180        fysm_obj = forms.models.FYSM.objects.get(group=group_obj, year=year, )
181    except forms.models.FYSM.DoesNotExist:
182        fysm_obj = forms.models.FYSM()
183        fysm_obj.group = group_obj
184        fysm_obj.year = year
185        initial['display_name'] = group_obj.name
186        initial['year'] = year
187        initial['website'] = group_obj.website_url
188        initial['join_url'] = group_obj.website_url
189        initial['contact_email'] = group_obj.officer_email
190
191    if request.method == 'POST': # If the form has been submitted...
192        form = FYSMRequestForm(request.POST, request.FILES, instance=fysm_obj, ) # A form bound to the POST data
193
194        if form.is_valid(): # All validation rules pass
195            request_obj = form.save()
196
197            view_uri = request.build_absolute_uri(reverse('fysm-view', args=[year, request_obj.pk, ]))
198
199            # Send email
200            email = util.emails.email_from_template(
201                tmpl='fysm/update_email.txt',
202                context = Context({
203                    'group': group_obj,
204                    'fysm': fysm_obj,
205                    'view_uri': view_uri,
206                    'submitter': request.user,
207                    'request': request,
208                    'sender': "ASA FYSM team",
209                }),
210                subject='FYSM entry for "%s" updated by "%s"' % (
211                    group_obj.name,
212                    request.user,
213                ),
214                to=[group_obj.officer_email, request.user.email, ],
215                from_email='asa-fysm@mit.edu',
216            )
217            email.bcc = ['asa-fysm-submissions@mit.edu']
218            email.send()
219            return HttpResponseRedirect(reverse('fysm-thanks', args=[fysm_obj.pk],)) # Redirect after POST
220
221    else:
222        form = FYSMRequestForm(instance=fysm_obj, initial=initial, ) # An unbound form
223
224    context = {
225        'group':group_obj,
226        'fysm':fysm_obj,
227        'form':form,
228        'categories':forms.models.FYSMCategory.objects.all(),
229        'pagename':'fysm',
230    }
231    return render_to_response('fysm/submit.html', context, context_instance=RequestContext(request), )
232
233
234def fysm_thanks(request, fysm, ):
235    year = datetime.date.today().year
236    fysm_obj = get_object_or_404(forms.models.FYSM, pk=fysm)
237
238    context = {
239        'group':fysm_obj.group,
240        'fysm':fysm_obj,
241        'pagename':'fysm',
242    }
243    return render_to_response('fysm/thanks.html', context, context_instance=RequestContext(request), )
244
245#####################
246# Membership update #
247#####################
248
249membership_update_qs = groups.models.Group.objects.filter(group_status__slug__in=['active', 'suspended', ])
250
251@login_required
252def group_membership_update_select_group(request, ):
253    cycle = forms.models.GroupConfirmationCycle.latest()
254
255    users_groups = groups.models.Group.involved_groups(request.user.username)
256    qs = membership_update_qs.filter(pk__in=users_groups)
257
258    return select_group(request=request,
259        url_name_after='membership-update-group',
260        url_args=[cycle.slug],
261        pagename='groups',
262        queryset=qs,
263        title="Submit membership update for...",
264        msg="The list below contains only groups that list you as being involved. You must be an administrator of a group to submit an update.",
265    )
266
267class Form_GroupMembershipUpdate(ModelForm):
268    def __init__(self, *args, **kwargs):
269        super(Form_GroupMembershipUpdate, self).__init__(*args, **kwargs)
270        self.fields['no_hazing'].required = True
271
272    class Meta:
273        model = forms.models.GroupMembershipUpdate
274        fields = [
275            'updater_title',
276            'group_email',
277            'officer_email',
278            'email_preface',
279            'no_hazing',
280            'no_discrimination',
281            'membership_definition',
282            'num_undergrads',
283            'num_grads',
284            'num_alum',
285            'num_other_affiliate',
286            'num_other',
287            'membership_list',
288        ]
289
290@login_required
291def group_membership_update(request, cycle_slug, pk, ):
292    cycle = get_object_or_404(forms.models.GroupConfirmationCycle, slug=cycle_slug)
293    group_obj = get_object_or_404(groups.models.Group, pk=pk)
294    if not request.user.has_perm('groups.admin_group', group_obj):
295        raise PermissionDenied
296
297    try:
298        update_obj = forms.models.GroupMembershipUpdate.objects.get(group=group_obj, cycle=cycle, )
299    except forms.models.GroupMembershipUpdate.DoesNotExist:
300        update_obj = None
301
302    confirm_uri = request.build_absolute_uri(reverse('membership-confirm'))
303    submitted_uri = request.build_absolute_uri(reverse('membership-submitted'))
304
305    if request.method == 'POST':
306        form = Form_GroupMembershipUpdate(request.POST, request.FILES, instance=update_obj) # A form bound to the POST data
307
308        if form.is_valid(): # All validation rules pass
309            # Update the updater info
310            form.instance.group = group_obj
311            form.instance.cycle = cycle
312            form.instance.update_time  = datetime.datetime.now()
313            form.instance.updater_name = request.user.username
314            request_obj = form.save()
315
316            # Send email
317            tmpl = get_template('membership/anti-hazing.txt')
318            ctx = Context({
319                'update': request_obj,
320                'group': group_obj,
321                'submitter': request.user,
322            })
323            body = tmpl.render(ctx)
324            email = EmailMessage(
325                subject='Anti-Hazing and Non-Discrimination Acknowledgement for %s' % (
326                    group_obj.name,
327                ),
328                body=body,
329                from_email=request.user.email,
330                to=[request_obj.group_email, ],
331                cc=[request_obj.officer_email, ],
332                bcc=['asa-db-outgoing@mit.edu', ],
333            )
334            email.send()
335
336            # Send email
337            tmpl = get_template('membership/submit-confirm-email.txt')
338            ctx = Context({
339                'update': request_obj,
340                'group': group_obj,
341                'submitter': request.user,
342                'confirm_uri': confirm_uri,
343                'submitted_uri': submitted_uri,
344            })
345            body = tmpl.render(ctx)
346            email = EmailMessage(
347                subject='ASA Membership Information for %s' % (
348                    group_obj.name,
349                ),
350                body=body,
351                from_email=request.user.email,
352                to=[request_obj.officer_email, ],
353                bcc=['asa-db-outgoing@mit.edu', ],
354            )
355            email.send()
356
357            return HttpResponseRedirect(reverse('membership-thanks', )) # Redirect after POST
358
359    else:
360        form = Form_GroupMembershipUpdate(instance=update_obj)
361
362    context = {
363        'form':form,
364        'group':group_obj,
365        'cycle':cycle,
366        'confirm_uri': confirm_uri,
367        'pagename':'groups',
368    }
369    return render_to_response('membership/update.html', context, context_instance=RequestContext(request), )
370
371class Form_PersonMembershipUpdate(ModelForm):
372    groups = ModelMultipleChoiceField(queryset=membership_update_qs)
373    class Meta:
374        model = forms.models.PersonMembershipUpdate
375        fields = [
376            'groups',
377        ]
378
379@login_required
380def person_membership_update(request, ):
381    initial = {
382    }
383    cycle = forms.models.GroupConfirmationCycle.latest()
384
385    # Initialize/find the PersonMembershipUpdate for this user
386    try:
387        update_obj = forms.models.PersonMembershipUpdate.objects.get(
388            username=request.user.username,
389            deleted__isnull=True,
390            cycle=cycle,
391        )
392        selected_groups = update_obj.groups.all()
393    except forms.models.PersonMembershipUpdate.DoesNotExist:
394        update_obj = forms.models.PersonMembershipUpdate()
395        update_obj.update_time  = datetime.datetime.now()
396        update_obj.username = request.user.username
397        update_obj.cycle = cycle
398        selected_groups = []
399
400    # Determine whether the submitter is a student or not
401    accounts = groups.models.AthenaMoiraAccount
402    try:
403        person = accounts.active_accounts.get(username=request.user.username)
404        if person.is_student():
405            update_obj.valid = forms.models.VALID_AUTOVALIDATED
406        else:
407            update_obj.valid = forms.models.VALID_AUTOREJECTED
408    except accounts.DoesNotExist:
409        pass
410        update_obj.valid = forms.models.VALID_AUTOREJECTED
411
412    update_obj.save()
413
414    # Find groups that list a role for the user
415    office_holders = groups.models.OfficeHolder.current_holders.filter(person=request.user.username)
416    role_groups = {}
417    for office_holder in office_holders:
418        if office_holder.group.pk not in role_groups:
419            role_groups[office_holder.group.pk] = (office_holder.group, set())
420        role_groups[office_holder.group.pk][1].add(office_holder.role.display_name)
421
422    # Find groups the user searched for
423    filterset = groups.views.GroupFilter(request.GET, membership_update_qs)
424    filtered_groups = filterset.qs.all()
425    show_filtered_groups = ('search' in request.GET)
426
427    message = ""
428    message_type = "info"
429
430    if update_obj.valid <= forms.models.VALID_UNSET:
431        message = "You are not listed as a student. While you're welcome to confirm your membership in groups anyway, you will not count towards the five student member requirement. If you are a student, please contact asa-exec so that we can correct our records."
432        message_type = "warn"
433
434    # Handle the single group add/remove forms
435    # * removing previously confirmed groups
436    # * add/remove groups that list the user in a role
437    # * add/remove groups the user searched for
438    if request.method == 'POST' and 'add-remove' in request.POST:
439        group = groups.models.Group.objects.get(id=request.POST['group'])
440        if request.POST['action'] == 'remove':
441            if group in update_obj.groups.all():
442                update_obj.groups.remove(group)
443                message = "You have successfully unconfirmed membership in %s." % (group, )
444            else:
445                message = "Removal failed because you had not confirmed membership in %s." % (group, )
446                message_type = "warn"
447        elif request.POST['action'] == 'add':
448            if group in update_obj.groups.all():
449                message = "Membership in %s already confirmed." % (group, )
450                message_type = "warn"
451            else:
452                update_obj.groups.add(group)
453                message = "You have successfully confirmed membership in %s." % (group, )
454        else:
455            message = "Uh, somehow you tried to do something besides adding and removing..."
456            message_type = "alert"
457
458    # Handle the big list of groups
459    if request.method == 'POST' and 'list' in request.POST:
460        form = Form_PersonMembershipUpdate(request.POST, request.FILES, instance=update_obj)
461        if form.is_valid():
462            request_obj = form.save()
463            message = "Update saved"
464    else:
465        form = Form_PersonMembershipUpdate(initial=initial, instance=update_obj, )
466    form.fields['groups'].widget.attrs['size'] = 20
467
468    # Render the page
469    context = {
470        'role_groups':role_groups,
471        'form':form,
472        'filter':filterset,
473        'show_filtered_groups':show_filtered_groups,
474        'filtered_groups':filtered_groups,
475        'member_groups':selected_groups,
476        'message': message,
477        'message_type': message_type,
478        'pagename':'groups',
479    }
480    return render_to_response('membership/confirm.html', context, context_instance=RequestContext(request), )
481
482
483class View_GroupMembershipList(ListView):
484    context_object_name = "group_list"
485    template_name = "membership/submitted.html"
486
487    def get_queryset(self):
488        cycle = forms.models.GroupConfirmationCycle.latest()
489        group_updates = forms.models.GroupMembershipUpdate.objects.all()
490        group_updates = group_updates.filter(
491            cycle=cycle,
492            group__personmembershipupdate__cycle=cycle,
493            group__personmembershipupdate__deleted__isnull=True,
494            group__personmembershipupdate__valid__gt=0,
495        )
496        group_updates = group_updates.annotate(num_confirms=Count('group__personmembershipupdate'))
497        #print len(list(group_updates))
498        #for query in connection.queries: print query
499        return group_updates
500
501
502@permission_required('groups.view_group_private_info')
503def group_confirmation_issues(request, ):
504    account_numbers = ("accounts" in request.GET) and request.GET['accounts'] == "1"
505
506    active_groups = groups.models.Group.active_groups
507    group_updates = forms.models.GroupMembershipUpdate.objects.all()
508    people_confirmations = forms.models.PersonMembershipUpdate.objects.filter(
509        deleted__isnull=True,
510        valid__gt=0,
511    )
512
513    buf = StringIO.StringIO()
514    output = csv.writer(buf)
515    fields = ['group_id', 'group_name', 'issue', 'num_confirm', 'officer_email', ]
516    if account_numbers: fields.append("main_account")
517    output.writerow(fields)
518
519    q_present = Q(id__in=group_updates.values('group'))
520    missing_groups = active_groups.filter(~q_present)
521    #print len(list(group_updates))
522    for group in missing_groups:
523        num_confirms = len(people_confirmations.filter(groups=group))
524        fields = [
525            group.id,
526            group.name,
527            'unsubmitted',
528            num_confirms,
529            group.officer_email,
530        ]
531        if account_numbers: fields.append(group.main_account_id)
532        output.writerow(fields)
533
534    for group_update in group_updates:
535        group = group_update.group
536        num_confirms = len(people_confirmations.filter(groups=group))
537        problems = []
538
539        if num_confirms < 5:
540            problems.append("confirmations")
541
542        num_students = group_update.num_undergrads + group_update.num_grads
543        num_other = group_update.num_alum + group_update.num_other_affiliate + group_update.num_other
544        if num_students < num_other:
545            problems.append("50%")
546
547        for problem in problems:
548            fields = [
549                group.id,
550                group.name,
551                problem,
552                num_confirms,
553                group.officer_email,
554            ]
555            if account_numbers: fields.append(group.main_account_id)
556            output.writerow(fields)
557
558
559    return HttpResponse(buf.getvalue(), mimetype='text/csv', )
560
561
562
563##########
564# Midway #
565##########
566
567def midway_map_latest(request, ):
568    midways = forms.models.Midway.objects.order_by('-date')[:1]
569    if len(midways) == 0:
570        raise Http404("No midways found.")
571    else:
572        url = reverse('midway-map', args=(midways[0].slug, ))
573        return HttpResponseRedirect(url)
574
575
576class MidwayAssignmentFilter(django_filters.FilterSet):
577    name = django_filters.CharFilter(name='group__name', lookup_type='icontains', label="Name contains")
578    abbreviation = django_filters.CharFilter(name='group__abbreviation', lookup_type='iexact', label="Abbreviation is")
579    activity_category = django_filters.ModelChoiceFilter(
580        label='Activity category',
581        name='group__activity_category',
582        queryset=groups.models.ActivityCategory.objects,
583    )
584
585    class Meta:
586        model = forms.models.MidwayAssignment
587        fields = [
588            'name',
589            'abbreviation',
590            'activity_category',
591        ]
592        order_by = (
593            ('group__name', 'Name', ),
594            ('group__abbreviation', 'Abbreviation', ),
595            ('group__activity_category__name', 'Activity category', ),
596            ('location', 'Location', ),
597        )
598
599
600class MidwayMapView(DetailView):
601    context_object_name = "midway"
602    model = forms.models.Midway
603    template_name = 'midway/map.html'
604
605    def get_context_data(self, **kwargs):
606        # Call the base implementation first to get a context
607        context = super(MidwayMapView, self).get_context_data(**kwargs)
608       
609        filterset = MidwayAssignmentFilter(self.request.GET)
610        context['assignments'] = filterset.qs
611        context['filter'] = filterset
612        context['pagename'] = 'midway'
613
614        return context
615
616
617class MidwayAssignmentsUploadForm(Form):
618    assignments = FileField()
619
620@permission_required('forms.add_midwayassignment')
621def midway_assignment_upload(request, slug, ):
622    midway = get_object_or_404(forms.models.Midway, slug=slug, )
623
624    uploaded = False
625    found = []
626    issues = collections.defaultdict(list)
627
628    if request.method == 'POST': # If the form has been submitted...
629        form = MidwayAssignmentsUploadForm(request.POST, request.FILES, ) # A form bound to the POST data
630
631        if form.is_valid(): # All validation rules pass
632            uploaded = True
633            reader = csv.DictReader(request.FILES['assignments'])
634            for row in reader:
635                group_name = row['Group']
636                group_officers = row['officers']
637                table = row['Table']
638                issue = False
639                try:
640                    group = groups.models.Group.objects.get(name=group_name)
641                    assignment = forms.models.MidwayAssignment(
642                        midway=midway,
643                        location=table,
644                        group=group,
645                    )
646                    assignment.save()
647                    found.append(assignment)
648                    status = group.group_status.slug
649                    if status != 'active':
650                        issue = 'status=%s (added anyway)' % (status, )
651                except groups.models.Group.DoesNotExist:
652                    issue = 'unknown group (ignored)'
653                if issue:
654                    issues[issue].append((group_name, group_officers, table))
655            for issue in issues:
656                issues[issue] = sorted(issues[issue], key=lambda x: x[0])
657
658    else:
659        form = MidwayAssignmentsUploadForm() # An unbound form
660
661    context = {
662        'midway':midway,
663        'form':form,
664        'uploaded': uploaded,
665        'found': found,
666        'issues': dict(issues),
667        'pagename':'midway',
668    }
669    return render_to_response('midway/upload.html', context, context_instance=RequestContext(request), )
Note: See TracBrowser for help on using the repository browser.