source: asadb/forms/views.py @ cf509c0

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

Basic list of groups at the midway

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