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
Line 
1import forms.models
2import groups.models
3import groups.views
4import settings
5import util.emails
6
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 Form
18from django.forms import ModelForm
19from django.forms import ModelChoiceField, ModelMultipleChoiceField
20from django.forms import ValidationError
21from django.db import connection
22from django.db.models import Q, Count
23
24import csv
25import datetime
26import StringIO
27
28#################
29# GENERIC VIEWS #
30#################
31
32class SelectGroupForm(Form):
33    group = ModelChoiceField(queryset=groups.models.Group.objects.all())
34    def __init__(self, *args, **kwargs):
35        queryset = None
36        if 'queryset' in kwargs:
37            queryset = kwargs['queryset']
38            del kwargs['queryset']
39        super(SelectGroupForm, self).__init__(*args, **kwargs)
40        if queryset is not None:
41            self.fields["group"].queryset = queryset
42
43def select_group(request, url_name_after, url_args=[], pagename='homepage', queryset=None, title="", msg=""):
44    if request.method == 'POST': # If the form has been submitted...
45        # A form bound to the POST data
46        form = SelectGroupForm(request.POST, queryset=queryset, )
47        if form.is_valid(): # All validation rules pass
48            group = form.cleaned_data['group'].id
49            return HttpResponseRedirect(reverse(url_name_after, args=url_args+[group],)) # Redirect after POST
50    else:
51        form = SelectGroupForm(queryset=queryset, ) # An unbound form
52
53    if not title: title = "Select group"
54    context = {
55        'form':form,
56        'title':title,
57        'msg':msg,
58        'pagename':pagename,
59    }
60    return render_to_response('forms/select.html', context, context_instance=RequestContext(request), )
61
62#############################
63# FIRST-YEAR SUMMER MAILING #
64#############################
65
66@login_required
67def fysm_by_years(request, year, category, ):
68    if year is None: year = datetime.date.today().year
69    queryset = forms.models.FYSM.objects.filter(year=year).order_by('group__name')
70    category_obj = None
71    category_name = 'main'
72    if category != None:
73        category_obj = get_object_or_404(forms.models.FYSMCategory, slug=category)
74        category_name = category_obj.name
75        queryset = queryset.filter(categories=category_obj)
76    forms.models.FYSMView.record_metric(request=request, fysm=None, year=year, page=category_name, )
77    categories = forms.models.FYSMCategory.objects.all()
78    return list_detail.object_list(
79        request,
80        queryset=queryset,
81        template_name="fysm/fysm_listing.html",
82        template_object_name="fysm",
83        extra_context={
84            "year": year,
85            "pagename": "fysm",
86            "category": category_obj,
87            "categories": categories,
88        }
89    )
90
91@login_required
92def fysm_view(request, year, submission, ):
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
103    forms.models.FYSMView.record_metric(request=request, fysm=submit_obj, year=year, page="detail", )
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",
113            "prev": prev,
114            "next": next,
115        },
116    )
117
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")
128    forms.models.FYSMView.record_metric(request=request, fysm=submit_obj, year=year, page=link_type, )
129    return HttpResponseRedirect(url)
130
131def select_group_fysm(request, ):
132    qobj = Q(activity_category__isnull = True) | ~(Q(activity_category__name='Dorm') | Q(activity_category__name='FSILG'))
133    queryset = groups.models.Group.active_groups.filter(qobj)
134    return select_group(
135        request,
136        url_name_after='fysm-manage',
137        pagename='fysm',
138        queryset=queryset,
139    )
140
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',
151            'slide',
152            'tags',
153            'categories',
154        )
155
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
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
169@login_required
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
193            view_uri = request.build_absolute_uri(reverse('fysm-view', args=[year, request_obj.pk, ]))
194
195            # Send email
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                }),
206                subject='FYSM entry for "%s" updated by "%s"' % (
207                    group_obj.name,
208                    request.user,
209                ),
210                to=[group_obj.officer_email, request.user.email, ],
211                from_email='asa-fysm@mit.edu',
212            )
213            email.bcc = ['asa-fysm-submissions@mit.edu']
214            email.send()
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,
224        'categories':forms.models.FYSMCategory.objects.all(),
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), )
240
241#####################
242# Membership update #
243#####################
244
245membership_update_qs = groups.models.Group.objects.filter(group_status__slug__in=['active', 'suspended', ])
246
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)
253
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):
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',
274            'email_preface',
275            'no_hazing',
276            'no_discrimination',
277            'membership_definition',
278            'num_undergrads',
279            'num_grads',
280            'num_alum',
281            'num_other_affiliate',
282            'num_other',
283            'membership_list',
284        ]
285
286@login_required
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
297
298    confirm_uri = request.build_absolute_uri(reverse('membership-confirm'))
299    submitted_uri = request.build_absolute_uri(reverse('membership-submitted'))
300
301    if request.method == 'POST':
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
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
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(
321                subject='Anti-Hazing and Non-Discrimination Acknowledgement for %s' % (
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, ],
328                bcc=['asa-db-outgoing@mit.edu', ],
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,
339                'submitted_uri': submitted_uri,
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, ],
349                bcc=['asa-db-outgoing@mit.edu', ],
350            )
351            email.send()
352
353            return HttpResponseRedirect(reverse('membership-thanks', )) # Redirect after POST
354
355    else:
356        form = Form_GroupMembershipUpdate(instance=update_obj)
357
358    context = {
359        'form':form,
360        'group':group_obj,
361        'cycle':cycle,
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):
368    groups = ModelMultipleChoiceField(queryset=membership_update_qs)
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    }
379    cycle = forms.models.GroupConfirmationCycle.latest()
380
381    # Initialize/find the PersonMembershipUpdate for this user
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
396    # Determine whether the submitter is a student or not
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
408    update_obj.save()
409
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
419    filterset = groups.views.GroupFilter(request.GET, membership_update_qs)
420    filtered_groups = filterset.qs.all()
421    show_filtered_groups = ('search' in request.GET)
422
423    message = ""
424    message_type = "info"
425
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
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
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)
439                message = "You have successfully unconfirmed membership in %s." % (group, )
440            else:
441                message = "Removal failed because you had not confirmed membership in %s." % (group, )
442                message_type = "warn"
443        elif request.POST['action'] == 'add':
444            if group in update_obj.groups.all():
445                message = "Membership in %s already confirmed." % (group, )
446                message_type = "warn"
447            else:
448                update_obj.groups.add(group)
449                message = "You have successfully confirmed membership in %s." % (group, )
450        else:
451            message = "Uh, somehow you tried to do something besides adding and removing..."
452            message_type = "alert"
453
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():
458            request_obj = form.save()
459            message = "Update saved"
460    else:
461        form = Form_PersonMembershipUpdate(initial=initial, instance=update_obj, )
462    form.fields['groups'].widget.attrs['size'] = 20
463
464    # Render the page
465    context = {
466        'role_groups':role_groups,
467        'form':form,
468        'filter':filterset,
469        'show_filtered_groups':show_filtered_groups,
470        'filtered_groups':filtered_groups,
471        'member_groups':selected_groups,
472        'message': message,
473        'message_type': message_type,
474        'pagename':'groups',
475    }
476    return render_to_response('membership/confirm.html', context, context_instance=RequestContext(request), )
477
478
479class View_GroupMembershipList(ListView):
480    context_object_name = "group_list"
481    template_name = "membership/submitted.html"
482
483    def get_queryset(self):
484        cycle = forms.models.GroupConfirmationCycle.latest()
485        group_updates = forms.models.GroupMembershipUpdate.objects.all()
486        group_updates = group_updates.filter(
487            cycle=cycle,
488            group__personmembershipupdate__cycle=cycle,
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
495        return group_updates
496
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))
527        problems = []
528
529        if num_confirms < 5:
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:
538            output.writerow([
539                group.id,
540                group.name,
541                problem,
542                num_confirms,
543                group.officer_email,
544            ])
545
546
547    return HttpResponse(buf.getvalue(), mimetype='text/csv', )
Note: See TracBrowser for help on using the repository browser.