source: asadb/forms/views.py @ 360b3d4

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

Serve membership/admin/issues.csv as text/csv

Firefox can be told to open a text/csv in the browser using the "Open in
browser" extension, so serving CSV files as text/plain no longer has
significant advantages (for me) over serving as text/csv, and has the
disadvantage of making it hard to open as a spreadsheet. Thus, I'm switching
back to text/csv.

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