source: asadb/forms/views.py @ 6e247d3

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

Add links to the submitted membership updates page

While the page with information about which membership updates have been
submitted was linked on the front page, some people were still not finding it.
This adds a link on the update page and in the email that gets sent to
officers. Fixes ASA-#183.

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