Changeset aed3e6d for asadb


Ignore:
Timestamp:
Dec 3, 2013, 12:31:49 AM (12 years ago)
Author:
Alex Dehnert <adehnert@…>
Branches:
master, stable, stage
Children:
eab727a
Parents:
7dde669 (diff), d9624e8 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
git-author:
Alex Dehnert <adehnert@…> (12/03/13 00:31:49)
git-committer:
Alex Dehnert <adehnert@…> (12/03/13 00:31:49)
Message:

Merge branch 'membership-confirmations'

  • membership-confirmations: Link people lookup tool from membership update page People lookup: mention the data source People lookup: Support large numbers of people People lookup: make the UI more reasonable Basic people lookup support (ASA-#186) Reword membership definition text More thoroughly purge mention of membership lists Standarize on UK-style ndashes Remove the membership list Do my counting in the DB... Save some unnecessary queries with select_related Finish removing membership confirmation search box Include suspended groups in issues.csv (ASA-#195) Memberships: refactor issues.csv slightly Make issues.csv cycle-aware (ASA-#191) JSify the giant list of groups (ASA-#185) Show the group name on the update page List only adminned groups in anti-hazing select Add Group.admin_groups method (ASA-#181)
Location:
asadb
Files:
2 added
13 edited

Legend:

Unmodified
Added
Removed
  • asadb/forms/models.py

    re0ed952 red7ddec  
    11import datetime
    2 import os, errno
     2import errno
     3import json
     4import os
     5
     6import ldap
    37
    48from django.conf import settings
     9from django.contrib.auth.models import User
    510from django.db import models
    611
     
    168173    num_other = models.IntegerField(verbose_name="Num non-MIT")
    169174
    170     membership_list = models.TextField(help_text="Member emails on separate lines (Athena usernames where applicable)")
     175    membership_list = models.TextField(blank=True, help_text="Member emails on separate lines (Athena usernames where applicable)")
    171176
    172177    email_preface = models.TextField(blank=True, help_text="If you would like, you may add text here that will preface the text of the policies when it is sent out to the group membership list provided above.")
     
    207212
    208213
     214class PeopleStatusLookup(models.Model):
     215    people = models.TextField(help_text="Enter some usernames or email addresses to look up here.")
     216    requestor = models.ForeignKey(User, null=True, blank=True, )
     217    referer = models.URLField(blank=True)
     218    time = models.DateTimeField(default=datetime.datetime.now)
     219    classified_people_json = models.TextField()
     220    _classified_people = None
     221
     222    def ldap_classify(self, usernames, ):
     223        con = ldap.open('ldap-too.mit.edu')
     224        con.simple_bind_s("", "")
     225        dn = "ou=users,ou=moira,dc=mit,dc=edu"
     226        fields = ['uid', 'eduPersonAffiliation', 'mitDirStudentYear']
     227
     228        chunk_size = 100
     229        username_chunks = []
     230        ends = range(chunk_size, len(usernames), chunk_size)
     231        start = 0
     232        for end in ends:
     233            username_chunks.append(usernames[start:end])
     234            start = end
     235        username_chunks.append(usernames[end:])
     236        print username_chunks
     237
     238        results = []
     239        for chunk in username_chunks:
     240            filters = [ldap.filter.filter_format('(uid=%s)', [u]) for u in chunk]
     241            userfilter = "(|%s)" % (''.join(filters), )
     242            batch_results = con.search_s(dn, ldap.SCOPE_SUBTREE, userfilter, fields)
     243            results.extend(batch_results)
     244
     245        left = set(usernames)
     246        undergrads = []
     247        grads = []
     248        staff = []
     249        secret = []
     250        other = []
     251        info = {
     252            'undergrads': undergrads,
     253            'grads': grads,
     254            'staff': staff,
     255            'secret': secret,
     256            'affiliate': other,
     257        }
     258        for result in results:
     259            username = result[1]['uid'][0]
     260            left.remove(username)
     261            affiliation = result[1].get('eduPersonAffiliation', ['secret'])[0]
     262            if affiliation == 'student':
     263                year = result[1].get('mitDirStudentYear', [None])[0]
     264                if year == 'G':
     265                    grads.append((username, None))
     266                elif year.isdigit():
     267                    undergrads.append((username, year))
     268                else:
     269                    other.append((username, year))
     270            else:
     271                info[affiliation].append((username, None, ))
     272        info['unknown'] = [(u, None) for u in left]
     273        return info
     274
     275    def classify_people(self, people):
     276        mit_usernames = []
     277        alum_addresses = []
     278        other_mit_addresses = []
     279        nonmit_addresses = []
     280
     281        for name in people:
     282            local, at, domain = name.partition('@')
     283            if domain.lower() == 'mit.edu' or domain == '':
     284                mit_usernames.append(local)
     285            elif domain.lower() == 'alum.mit.edu':
     286                alum_addresses.append((name, None))
     287            elif domain.endswith('.mit.edu'):
     288                other_mit_addresses.append((name, None))
     289            else:
     290                nonmit_addresses.append((name, None))
     291
     292        results = self.ldap_classify(mit_usernames)
     293        results['alum'] = alum_addresses
     294        results['other-mit'] = other_mit_addresses
     295        results['non-mit'] = nonmit_addresses
     296        return results
     297
     298    def update_classified_people(self):
     299        people = [p for p in [p.strip() for p in self.people.split('\n')] if p]
     300        self._classified_people = self.classify_people(people)
     301        self.classified_people_json = json.dumps(self._classified_people)
     302        return self._classified_people
     303
     304    @property
     305    def classified_people(self):
     306        if self._classified_people is None:
     307            self._classified_people = json.loads(self.classified_people_json)
     308        return self._classified_people
     309
     310    def classifications_with_descriptions(self):
     311        descriptions = {
     312            'undergrads':   'Undergraduate students (class year in parentheses)',
     313            'grads':        'Graduate students',
     314            'alum':         "Alumni Association addresses",
     315            'staff':        'MIT Staff (including faculty)',
     316            'affiliate':    'This includes some alumni, group members with Athena accounts sponsored through SAO, and many others.',
     317            'secret':       'People with directory information suppressed. These people have Athena accounts, but they could have any MIT affiliation, including just being a student group member.',
     318            'unknown':      "While this looks like an Athena account, we couldn't find it. This could be a deactivated account, or it might never have existed.",
     319            'other-mit':    ".mit.edu addresses that aren't @mit.edu or @alum.mit.edu.",
     320            'non-mit':      "Non-MIT addresses, including outside addresses of MIT students.",
     321        }
     322
     323        names = (
     324            ('undergrads', 'Undergrads', ),
     325            ('grads', 'Grad students', ),
     326            ('alum', 'Alumni', ),
     327            ('staff', 'Staff', ),
     328            ('affiliate', 'Affiliates', ),
     329            ('secret', 'Secret', ),
     330            ('unknown', 'Unknown', ),
     331            ('other-mit', 'Other MIT addresses', ),
     332            ('non-mit', 'Non-MIT addresses', ),
     333        )
     334
     335        classifications = self.classified_people
     336        sorted_results = []
     337        for k, label in names:
     338            sorted_results.append({
     339                'label': label,
     340                'description': descriptions[k],
     341                'people': sorted(classifications[k]),
     342            })
     343        return sorted_results
     344
    209345
    210346##########
  • asadb/forms/views.py

    re0ed952 rd9624e8  
    252252    cycle = forms.models.GroupConfirmationCycle.latest()
    253253
    254     users_groups = groups.models.Group.involved_groups(request.user.username)
     254    users_groups = groups.models.Group.admin_groups(request.user.username)
    255255    qs = membership_update_qs.filter(pk__in=users_groups)
    256256
     
    261261        queryset=qs,
    262262        title="Submit membership update for...",
    263         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.",
    264263    )
    265264
     
    268267        super(Form_GroupMembershipUpdate, self).__init__(*args, **kwargs)
    269268        self.fields['no_hazing'].required = True
     269        help_text = "If you have a membership list, you can get help turning it into membership breakdown using our <a href='%s'>people lookup</a> tool. You'll need to copy the numbers back over, though." % (reverse('membership-people-lookup'), )
     270        self.fields['num_undergrads'].help_text = help_text
    270271
    271272    class Meta:
     
    284285            'num_other_affiliate',
    285286            'num_other',
    286             'membership_list',
     287            #'membership_list',
    287288        ]
    288289
     
    418419            role_groups[office_holder.group.pk] = (office_holder.group, set())
    419420        role_groups[office_holder.group.pk][1].add(office_holder.role.display_name)
    420 
    421     # Find groups the user searched for
    422     filterset = groups.views.GroupFilter(request.GET, membership_update_qs)
    423     filtered_groups = filterset.qs.all()
    424     show_filtered_groups = ('search' in request.GET)
    425421
    426422    message = ""
     
    469465        'role_groups':role_groups,
    470466        'form':form,
    471         'filter':filterset,
    472         'show_filtered_groups':show_filtered_groups,
    473         'filtered_groups':filtered_groups,
    474467        'member_groups':selected_groups,
    475468        'message': message,
     
    499492
    500493
     494class View_GroupConfirmationCyclesList(ListView):
     495    context_object_name = "cycle_list"
     496    template_name = "membership/admin.html"
     497    model = forms.models.GroupConfirmationCycle
     498
     499    def get_context_data(self, **kwargs):
     500        context = super(View_GroupConfirmationCyclesList, self).get_context_data(**kwargs)
     501        context['pagename'] = 'groups'
     502        return context
     503
     504
    501505@permission_required('groups.view_group_private_info')
    502 def group_confirmation_issues(request, ):
     506def group_confirmation_issues(request, slug, ):
    503507    account_numbers = ("accounts" in request.GET) and request.GET['accounts'] == "1"
    504508
    505     active_groups = groups.models.Group.active_groups
    506     group_updates = forms.models.GroupMembershipUpdate.objects.all()
     509    check_groups = groups.models.Group.objects.filter(group_status__slug__in=('active', 'suspended', ))
     510    check_groups = check_groups.select_related('group_status')
     511    group_updates = forms.models.GroupMembershipUpdate.objects.filter(cycle__slug=slug, )
     512    group_updates = group_updates.select_related('group', 'group__group_status')
    507513    people_confirmations = forms.models.PersonMembershipUpdate.objects.filter(
    508514        deleted__isnull=True,
    509515        valid__gt=0,
     516        cycle__slug=slug,
    510517    )
    511518
    512519    buf = StringIO.StringIO()
    513520    output = csv.writer(buf)
    514     fields = ['group_id', 'group_name', 'issue', 'num_confirm', 'officer_email', ]
     521    fields = ['group_id', 'group_name', 'group_status', 'issue', 'num_confirm', 'officer_email', ]
    515522    if account_numbers: fields.append("main_account")
    516523    output.writerow(fields)
    517524
    518     q_present = Q(id__in=group_updates.values('group'))
    519     missing_groups = active_groups.filter(~q_present)
    520     #print len(list(group_updates))
    521     for group in missing_groups:
    522         num_confirms = len(people_confirmations.filter(groups=group))
     525    def output_issue(group, issue, num_confirms):
    523526        fields = [
    524527            group.id,
    525528            group.name,
    526             'unsubmitted',
     529            group.group_status.slug,
     530            issue,
    527531            num_confirms,
    528532            group.officer_email,
     
    531535        output.writerow(fields)
    532536
     537    q_present = Q(id__in=group_updates.values('group'))
     538    missing_groups = check_groups.filter(~q_present)
     539    #print len(list(group_updates))
     540    for group in missing_groups:
     541        #num_confirms = len(people_confirmations.filter(groups=group))
     542        output_issue(group, 'unsubmitted', '')
     543
    533544    for group_update in group_updates:
    534545        group = group_update.group
    535         num_confirms = len(people_confirmations.filter(groups=group))
     546        num_confirms = people_confirmations.filter(groups=group).count()
    536547        problems = []
    537548
     
    545556
    546557        for problem in problems:
    547             fields = [
    548                 group.id,
    549                 group.name,
    550                 problem,
    551                 num_confirms,
    552                 group.officer_email,
    553             ]
    554             if account_numbers: fields.append(group.main_account_id)
    555             output.writerow(fields)
    556 
     558            output_issue(group, problem, num_confirms)
    557559
    558560    return HttpResponse(buf.getvalue(), mimetype='text/csv', )
    559561
    560562
     563class PeopleStatusLookupForm(ModelForm):
     564    class Meta:
     565        model = forms.models.PeopleStatusLookup
     566        fields = ('people', )
     567
     568def people_status_lookup(request, pk=None, ):
     569    if pk is None:
     570        if request.method == 'POST':
     571            form = PeopleStatusLookupForm(request.POST, request.FILES, )
     572            if form.is_valid(): # All validation rules pass
     573                lookup = form.save(commit=False)
     574                lookup.requestor = request.user
     575                lookup.referer = request.META['HTTP_REFERER']
     576                lookup.update_classified_people()
     577                results = lookup.classifications_with_descriptions()
     578                lookup.save()
     579        else:
     580            form = PeopleStatusLookupForm()
     581            results = None
     582    else:
     583        if request.user.has_perm('forms.view_peoplestatusupdate'):
     584            lookup = get_object_or_404(forms.models.PeopleStatusLookup, pk=int(pk))
     585            results = lookup.classifications_with_descriptions()
     586            form = None
     587        else:
     588            raise PermissionDenied("You don't have permission to view old lookup requests.")
     589
     590    context = {
     591        'form': form,
     592        'results': results,
     593    }
     594
     595    return render_to_response('membership/people-lookup.html', context, context_instance=RequestContext(request), )
    561596
    562597##########
  • asadb/groups/models.py

    r00ec3e4 r7431d13  
     1import collections
    12import datetime
    23import filecmp
     
    1415from django.db.models import Q
    1516from django.core.validators import RegexValidator
    16 from django.contrib.auth.models import User
     17from django.contrib.auth.models import User, Permission
     18from django.contrib.contenttypes.models import ContentType
    1719from django.template.defaultfilters import slugify
    1820
     
    112114        current_officers = OfficeHolder.current_holders.filter(person=username)
    113115        users_groups = Group.objects.filter(officeholder__in=current_officers).distinct()
     116
     117    @staticmethod
     118    def admin_groups(username, codename='admin_group'):
     119        holders = OfficeHolder.current_holders.filter_perm(codename=codename).filter(person=username)
     120        users_groups = Group.objects.filter(officeholder__in=holders).distinct()
    114121        return users_groups
    115122
     
    395402
    396403    @classmethod
     404    def getRolesGrantingPerm(cls, perm=None, model=Group, codename=None, ):
     405        """Get all OfficerRole objects granting a permission
     406
     407        Either `perm` or `codename` must be supplied, but not both. If
     408        `codename` is provided (and `perm` is None), then `perm` the
     409        permission corresponding to `model` (default: `Group`) and `codename`
     410        will be found and used."""
     411
     412        if perm is None:
     413            ct = ContentType.objects.get_for_model(model)
     414            print ct
     415            print Permission.objects.filter(content_type=ct)
     416            perm = Permission.objects.get(content_type=ct, codename=codename)
     417
     418        Q_user = Q(user_permissions=perm)
     419        Q_group = Q(groups__permissions=perm)
     420        users = User.objects.filter(Q_user|Q_group)
     421        roles = cls.objects.filter(grant_user__in=users)
     422        return roles
     423
     424    @classmethod
    397425    def retrieve(cls, slug, ):
    398426        return cls.objects.get(slug=slug)
     427
    399428reversion.register(OfficerRole)
    400429
     
    407436        )
    408437
     438    def filter_perm(self, perm=None, model=Group, codename=None, ):
     439        roles = OfficerRole.getRolesGrantingPerm(perm=perm, model=model, codename=codename)
     440        return self.get_query_set().filter(role__in=roles)
     441
    409442class OfficeHolder(models.Model):
    410443    EXPIRE_OFFSET   = datetime.timedelta(seconds=1)
     
    433466    def __repr__(self, ):
    434467        return str(self)
     468
    435469reversion.register(OfficeHolder)
    436470
  • asadb/template/base.html

    r35280b4 r5560f6d  
    77    <link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}style/style.css" />
    88    <link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}style/page-frame.css" />
     9    {% block extrahead %}
     10    {% endblock %}
    911
    1012  </head>
  • asadb/template/fysm/fysm_listing.html

    r8948cef r21ab6b3  
    1212<p>Hello incoming MIT students!</p>
    1313
    14 <p>Student activities play an important role in student life at MIT &mdash; they're a
     14<p>Student activities play an important role in student life at MIT &ndash; they're a
    1515great place to meet people, practice old skills and learn new ones, have fun,
    1616and give back to the community.  Undergraduate and graduate students, along
  • asadb/template/groups/group_change_officers.html

    r53e624e r21ab6b3  
    5656<p>Please note:</p>
    5757<ul>
    58     <li>We don't track "group membership" &mdash; people will show up in the list below only if they have one of the roles listed</li>
    59     <li>Type only the <em>Athena username</em> to add people &mdash; do not type full names or names plus username</li>
     58    <li>We don't track "group membership" &ndash; people will show up in the list below only if they have one of the roles listed</li>
     59    <li>Type only the <em>Athena username</em> to add people &ndash; do not type full names or names plus username</li>
    6060    <li>To add <em>more than four</em> new people, add four at a time and <em>submit multiple times</em></li>
    6161</ul>
  • asadb/template/membership/admin.html

    rd6f8984 r21ab6b3  
    11{% extends "base.html" %}
    22
    3 {% block title %}Membership confirmations &mdash; ASA Exec pages{% endblock %}
     3{% block title %}Membership confirmations &ndash; ASA Exec pages{% endblock %}
    44{% block content %}
    55
    6 <h2>Membership confirmations &mdash; ASA Exec pages</h2>
     6<h2>Membership confirmations &ndash; ASA Exec pages</h2>
    77
    8 <p>Exec members can check the <a href='{% url membership-issues %}'>list of groups with issues</a> (or a version <a href='{% url membership-issues %}?accounts=1'>with account numbers</a>)</p>
     8<p>Exec members can check the issues:</p>
     9
     10<table class='pretty-table'>
     11<tr>
     12    <th>Name</th>
     13    <th>Create date</th>
     14    <th>Issues</th>
     15    <th>(with accounts)</th>
     16</tr>
     17{% for cycle in cycle_list %}
     18<tr>
     19    <th>{{cycle.name}}</th>
     20    <td>{{cycle.create_date}}</td>
     21    <td><a href='{% url membership-issues cycle.slug %}'>Issues</a></td>
     22    <td><a href='{% url membership-issues cycle.slug %}?accounts=1'>(with accounts)</a></td>
     23</tr>
     24{% endfor %}
     25</table>
    926
    1027<p>The list includes the following types of issues:</p>
    1128<dl>
    12     <dt>unsubmitted</dt><dd>active groups ("group status" is "Active") who have not submitted an update (ever &mdash; see ASA-#191)</dd>
    13     <dt>confirmations</dt><dd>groups that have submitted an update (ever) but had less than five members confirm membership (again, ever &mdash; ASA-#191)</dd>
    14     <dt>50%</dt><dd>groups that (ever) submitted a confirmation with more non-students (alum, other affiliates, or other) than students (grad or undergrad)</dd>
     29    <dt>unsubmitted</dt><dd>active or suspended groups who have not submitted an update
     30    <dt>confirmations</dt><dd>groups that have submitted an update but had less than five members confirm membership</dd>
     31    <dt>50%</dt><dd>groups that submitted a confirmation with more non-students (alum, other affiliates, or other) than students (grad or undergrad)</dd>
    1532</dl>
    1633
  • asadb/template/membership/confirm.html

    r11941e1 r5560f6d  
    11{% extends "base.html" %}
     2
     3{% block extrahead %}
     4<link rel="stylesheet" href="{{MEDIA_URL}}js/libs/chosen/chosen.css">
     5{% endblock %}
    26
    37{% block title %}Membership update{% endblock %}
     
    7781</table>
    7882
    79 <h3>Option 2: Add or remove groups by searching</h3>
     83<h3>Option 2: Select all your groups at once</h3>
    8084
    81 <form action="" method="get">
    82     <table class='pretty-table'>
    83     {{ filter.form.as_table }}
    84     </table>
    85     <input type="submit" name='search' value="Search" />
    86     <input type="submit" name='stop-search' value="Stop searching" />
    87 </form>
    88 
    89 {% if show_filtered_groups %}
    90 <table class='pretty-table'>
    91 <thead>
    92     <tr>
    93         <th>Name</th>
    94         <th>Website</th>
    95         <th>ASA DB</th>
    96         <th>Description</th>
    97         <th>Add/Remove</th>
    98     </tr>
    99 </thead>
    100 <tbody>
    101 {% for group in filtered_groups %}
    102     <tr>
    103         <th>{{group.name}}</th>
    104         <td>{% if group.website_url %}<a href='{{group.website_url}}'>Website</a>{%endif%}</td>
    105         <td><a href='{% url groups:group-detail group.pk %}'>DB Entry</a></td>
    106         <td>{{group.description}}</td>
    107         <td>
    108             <form action="" method="post">
    109             {% csrf_token %}
    110             <input type="hidden" name="group" value="{{group.pk}}">
    111             {% if group in member_groups %}
    112             <input type="hidden" name="action" value="remove">
    113             <input type="submit" name="add-remove" value="Remove">
    114             {% else %}
    115             <input type="hidden" name="action" value="add">
    116             <input type="submit" name="add-remove" value="Add">
    117             {% endif %}
    118             </form>
    119         </td>
    120     </tr>
    121 {% endfor %}
    122 </tbody>
    123 </table>
    124 {% else %}
    125 <p>Hit "search" to see matching groups.</p>
    126 {% endif %}
    127 
    128 
    129 <h3>Option 3: Select all your groups at once</h3>
    130 
    131 <p>Below is a list of all recognized groups. You can go through picking out the groups you are a member of. However, you should be careful not to deselect any groups (unless you aren't a member of them, of course).</p>
     85<p>Below, you can add or remove any recognized group from the list of groups you are a member of.</p>
    13286
    13387<form enctype="multipart/form-data" method="post" action="">
     
    13993</form>
    14094
     95
     96<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js" type="text/javascript"></script>
     97<script src="{{MEDIA_URL}}js/libs/chosen/chosen.jquery.js" type="text/javascript"></script>
     98<script type="text/javascript">
     99    $("#id_groups").chosen();
     100</script>
     101
    141102{% endblock %}
  • asadb/template/membership/thanks.html

    r1ed7e51 r21ab6b3  
    11{% extends "base.html" %}
    22
    3 {% block title %}Membership update &mdash; thanks{% endblock %}
     3{% block title %}Membership update &ndash; thanks{% endblock %}
    44{% block content %}
    55
    6 <h2>Membership update &mdash; thanks</h2>
     6<h2>Membership update &ndash; thanks</h2>
    77
    88<p>Thanks for confirming your group's activity. You should receive a confirmation email to your officer's list, and a copy of the anti-hazing law to your group list.</p>
  • asadb/template/membership/update.html

    r6e247d3 r3d2db0b  
    55
    66
    7 <h1>ASA Group Registration &amp; Anti-Hazing Form</h1>
     7<h1>ASA Group Registration &amp; Anti-Hazing Form for <a href='{%url groups:group-detail group.pk%}'>{{group.name}}</a></h1>
    88
    99<p>The purpose of this electronic registration is for the ASA to determine which groups are still active and which are not. There are four components to this process:</p>
     
    3535<h3><a name='components-membership'></a>3. Providing Membership Information</h3>
    3636
    37 <p>We are requiring that groups provide membership information &mdash; both membership numbers and a membership list. This information is important for our understanding of your group and ensuring that groups are meeting our membership standards. We will also share the membership numbers with the funding boards (GSC, UA, and LEF/ARCADE) for groups that apply to those sources. <strong>It is very important you are honest when answering these questions</strong>, even if you are concerned your membership does not meet our standards. We will provide time and support for groups to improve their student participation as needed &mdash; both in terms of number and proportion of students. Groups that are shown to be dishonest in this process, particularly with regards to meeting our standards, <strong>may have funding and space eligibility revoked or be suspended or derecognized</strong>.</p>
     37<p>We are requiring that groups provide membership information &ndash; both membership definition and the corresponding numbers. This information is important for our understanding of your group and ensuring that groups are meeting our membership standards. We will also share the membership numbers with the funding boards (GSC, UA, and LEF/ARCADE) for groups that apply to those sources. <strong>It is very important you are honest when answering these questions</strong>, even if you are concerned your membership does not meet our standards. We will provide time and support for groups to improve their student participation as needed &ndash; both in terms of number and proportion of students. Groups that are shown to be dishonest in this process, particularly with regards to meeting our standards, <strong>may have funding and space eligibility revoked or be suspended or derecognized</strong>.</p>
    3838
    3939<dl>
    40 <dt>Minimum definition of member</dt><dd><p>For the purposes of this form, the minimum definition of membership you should use is people you would expect to confirm membership in your group (not that they all have to) and people that you consider to be regular or active members of your group. (You may be more stringent, based on how your group views or defines membership internally.) You must state the specific definition of membership you are using below. This does not need to be a membership definition that is in your constitution.</p></dd>
     40<dt>Minimum definition of member</dt><dd><p>For the purposes of this form, the minimum definition of membership you use should include only people who (a) you would expect to confirm membership in your group (not that they all have to) if asked and (b) you consider to be regular or active members of your group. Members should satisfy both (a) and (b); you should not count members who satisfy only one or the other. (You may be more stringent, based on how your group views or defines membership internally.) You must state the specific definition of membership you are using below. This does not need to be a membership definition that is in your constitution.</p></dd>
    4141
    42 <dt>Privacy</dt><dd><p>The list of members you provide will not be shared. If we need to contact them we will contact your officer/exec list first &mdash; this would only be if we have concerns about the information provided or to spot-check the information provided. If feasible, we will ask your officer/exec list to contact your members directly.</p></dd>
    43 
    44 <dt>Recruitment</dt><dd><p>If you are still in the midst of or leading up to your major recruitment period, we encourage you to submit current numbers and a list soon to ensure you have something submitted before the deadline. Then you can submit an updated form later, either before or after the deadline. You don't need to do this if your membership changes slightly, but we recommend you do so if your numbers change significantly.</p></dd>
     42<dt>Recruitment</dt><dd><p>If you are still in the midst of or leading up to your major recruitment period, we encourage you to submit current numbers soon to ensure you have something submitted before the deadline. Then you can submit an updated form later, either before or after the deadline. You don't need to do this if your membership changes slightly, but we recommend you do so if your numbers change significantly.</p></dd>
    4543
    4644</dl>
  • asadb/urls.py

    r3c1b20b r88a856b  
    6565    ),
    6666    url(r'^membership/submitted/$', forms.views.View_GroupMembershipList.as_view(), name='membership-submitted', ),
    67     url(
    68         r'^membership/admin/$',
    69         'django.views.generic.simple.direct_to_template',
    70         {'template': 'membership/admin.html', 'extra_context': { 'pagename':'groups' }, },
    71         name='membership-admin',
    72     ),
    73     url(r'^membership/admin/issues.csv$', forms.views.group_confirmation_issues, name='membership-issues', ),
     67    url(r'^membership/admin/$', forms.views.View_GroupConfirmationCyclesList.as_view(), name='membership-admin', ),
     68    url(r'^membership/admin/issues/(?P<slug>[\w-]+).csv$', forms.views.group_confirmation_issues, name='membership-issues', ),
     69    url(r'^membership/people-lookup/((?P<pk>\d+)/)?$', forms.views.people_status_lookup, name='membership-people-lookup', ),
    7470
    7571    # Midway
  • asadb/groups/views.py

    r89165c1 r7dde669  
    696696            from_email='asa-admin@mit.edu',
    697697        )
    698         # XXX: Handle this better
    699         if officer_domain != 'mit.edu' or (create_group_list and group_domain != 'mit.edu'):
    700             accounts_mail.to = ['asa-groups@mit.edu']
    701             accounts_mail.cc = ['asa-db@mit.edu']
    702             accounts_mail.subject = "ERROR: " + accounts_mail.subject
    703             accounts_mail.body = "Bad domain on officer or group list\n\n" + accounts_mail.body
    704698
    705699    else:
     
    828822    return render_to_response('groups/create/startup.html', context, context_instance=RequestContext(request), )
    829823
     824def review_group_check_warnings(group_startup, group, ):
     825    warnings = []
     826
     827    if group.name.startswith("MIT "):
     828        warnings.append('Group name starts with "MIT". Generally, we prefer "Foo, MIT" instead.')
     829    if "mit" in group.athena_locker.lower():
     830        warnings.append('Athena locker name contains "mit", which may be redundant with paths like "http://web.mit.edu/mitfoo" or "/mit/foo/".')
     831
     832    if group_startup.president_kerberos == group_startup.treasurer_kerberos:
     833        warnings.append('President matches Treasurer.')
     834    if "%s@mit.edu" % (group_startup.president_kerberos, ) in (group.officer_email, group.group_email):
     835        warnings.append('President email matches officer and/or group email.')
     836    if group.officer_email == group.group_email:
     837        warnings.append('Officer email matches group email.')
     838
     839    if '@mit.edu' not in group.officer_email or '@mit.edu' not in group.group_email:
     840        warnings.append('Officer and/or group email are non-MIT. Ensure that they are not requesting the addresses be created, and consider suggesting they use an MIT list instead.')
     841
     842    if '.' in group.athena_locker:
     843        warnings.append('Athena locker contains a ".". This is not compatible with scripts.mit.edu\'s wildcard certificate, and may cause other problems.')
     844    if '_' in group.athena_locker:
     845        warnings.append('Athena locker contains a "_". If this locker name gets used in a URL (for example, locker.scripts.mit.edu), it will technically violate the hostname specification and may not work in some clients.')
     846    if len(group.athena_locker) > 12:
     847        warnings.append('Athena locker is more than twelve characters long. In general, twelve characters is the longest Athena locker an ASA-recognized group can get.')
     848
     849    return warnings
     850
    830851@permission_required('groups.recognize_group')
    831852def recognize_normal_group(request, pk, ):
     
    843864    if group_startup.stage != groups.models.GROUP_STARTUP_STAGE_SUBMITTED:
    844865        return render_to_response('groups/create/err.not-applying.html', context, context_instance=RequestContext(request), )
     866
     867    context['warnings'] = review_group_check_warnings(group_startup, group)
    845868
    846869    context['msg'] = ""
  • asadb/template/groups/create/startup_review.html

    r172255b r7dde669  
    5151{% endif %}
    5252
     53{% if warnings %}
     54<div class='messagebox warnbox'>
     55<h3>Potential issues</h3>
     56
     57<p>The following potential issues were identified with this group's startup application:</p>
     58
     59<ul>
     60{% for warning in warnings %}
     61    <li>{{warning}}</li>
     62{%endfor%}
     63</ul>
     64
     65<p>Please look over these issues. Usually, you should <strong>reach out to the group</strong> and ask them to fix (or consider fixing) each issue before you approve the group. In some cases, it may be appropriate to ignore an issue. (For example, "MIT" is a central part of some group's acronyms (like "HTGAMIT"). In these cases, confirming with the group may not be necessary.)</p>
     66</div>
     67{%endif%}
     68
    5369{% if disp_form %}
    5470<form enctype="multipart/form-data" method="post" action="">
    5571{% csrf_token %}
     72{% if warnings %}
     73<input type='submit' name='approve' value='Approve DESPITE WARNINGS' />
     74{% else %}
    5675<input type='submit' name='approve' value='Approve' />
     76{% endif %}
    5777<input type='submit' name='reject' value='Reject' />
    5878</form>
Note: See TracChangeset for help on using the changeset viewer.