Changeset 7444bbe


Ignore:
Timestamp:
Aug 6, 2011, 5:43:39 AM (14 years ago)
Author:
Alex Dehnert <adehnert@…>
Branches:
master, space-access, stable, stage, test-hooks
Children:
f27faaf
Parents:
89be44c (diff), bb674c2 (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@…> (08/06/11 05:43:39)
git-committer:
Alex Dehnert <adehnert@…> (08/06/11 05:43:39)
Message:

Merge branch 'master' into new-db

Conflicts:

asadb/settings.py

Files:
10 added
13 edited

Legend:

Unmodified
Added
Removed
  • asadb/forms/admin.py

    rc27da9e rbb674c2  
    1111    )
    1212    list_display_links = ('group', 'display_name', 'year', )
     13    list_filter = ('year', 'categories', )
     14    search_fields = ('group__name', 'group__abbreviation', 'display_name', 'year', )
    1315
    1416class FYSMCategoryAdmin(admin.ModelAdmin):
  • asadb/forms/models.py

    rc71d5c7 rbb674c2  
    3030            )
    3131        super(FYSM, self).save(*args, **kwargs) # Call the "real" save() method.
     32
     33    def __str__(self, ):
     34        return "%s (%d)" % (self.display_name, self.year, )
    3235
    3336    class Meta:
  • asadb/groups/admin.py

    r89be44c r7444bbe  
    1717    )
    1818    list_display_links = ('id', 'name', )
     19    list_filter = [ 'activity_category', ]
     20    date_hierarchy = 'update_date'
    1921    search_fields = [ 'id', 'name', 'abbreviation', 'officer_email', 'athena_locker', ]
    2022
  • asadb/mit/__init__.py

    r1c68fbb r9c1f0b2  
     1import subprocess
     2import ldap
     3import ldap.filter
     4
    15from django.contrib.auth.middleware import RemoteUserMiddleware
    26from django.contrib.auth.backends import RemoteUserBackend
     
    812import settings
    913
    10 def zephyr(msg, clas='remit', instance='log', rcpt='adehnert',):
    11     import os
    12     os.system("zwrite -d -c '%s' -i '%s' '%s' -m '%s'" % (clas, instance, rcpt, msg, ))
     14def zephyr(msg, clas='message', instance='log', rcpt='nobody',):
     15    proc = subprocess.Popen(
     16        ['zwrite', '-d', '-n', '-c', clas, '-i', instance, rcpt, ],
     17        stdin=subprocess.PIPE, stdout=subprocess.PIPE
     18    )
     19    proc.communicate(msg)
    1320
    1421class ScriptsRemoteUserMiddleware(RemoteUserMiddleware):
     
    2532    def configure_user(self, user, ):
    2633        username = user.username
    27         import ldap
     34        user.password = "ScriptsSSLAuth"
    2835        con = ldap.open('ldap.mit.edu')
    2936        con.simple_bind_s("", "")
    3037        dn = "dc=mit,dc=edu"
    3138        fields = ['cn', 'sn', 'givenName', 'mail', ]
    32         result = con.search_s('dc=mit,dc=edu', ldap.SCOPE_SUBTREE, 'uid=%s'%username, fields)
     39        userfilter = ldap.filter.filter_format('uid=%s', [username])
     40        result = con.search_s('dc=mit,dc=edu', ldap.SCOPE_SUBTREE, userfilter, fields)
    3341        if len(result) == 1:
    3442            user.first_name = result[0][1]['givenName'][0]
     
    3947            except ObjectDoesNotExist:
    4048                print "Failed to retrieve mit group"
    41             user.save()
     49        else:
     50            raise ValueError, ("Could not find user with username '%s' (filter '%s')"%(username, userfilter))
    4251        try:
    4352            user.groups.add(auth.models.Group.objects.get(name='autocreated'))
    4453        except ObjectDoesNotExist:
    4554            print "Failed to retrieve autocreated group"
     55        user.save()
    4656        return user
    4757
  • asadb/settings.py

    r062a126 r7444bbe  
    7272    'django.contrib.auth.middleware.AuthenticationMiddleware',
    7373    'django.middleware.transaction.TransactionMiddleware',
     74    'django.middleware.csrf.CsrfViewMiddleware',
    7475    'reversion.middleware.RevisionMiddleware',
    7576]
  • asadb/template/forms/select.html

    r90afb00 rc9d8369  
    77
    88<form method="post" action="">
     9{% csrf_token %}
    910<table>
    1011{{ form.as_table }}
  • asadb/template/fysm/submit.html

    r3356151 rc9d8369  
    1010
    1111<form enctype="multipart/form-data" method="post" action="">
     12{% csrf_token %}
    1213<table class='pretty-table'>
    1314{{ form.as_table }}
  • .gitignore

    ra76118f r3ffb210  
    66asadb/media/fysm/logos/
    77asadb/media/fysm/slides/
     8asadb/media/page-previews/fysm/
    89asadb/util/saved-data/
    910asadb/util/warehouse-dump.csv
  • asadb/groups/models.py

    r2a6907a r89be44c  
    11from django.db import models
     2
     3import datetime
    24
    35# Create your models here.
     
    2426    updater = models.CharField(max_length=30) # match Django username field
    2527
     28    def officers(self, role=None, person=None, as_of="now",):
     29        """Get the set of people holding some office.
     30
     31        If None is passed for role, person, or as_of, that field will not
     32        be constrained. If as_of is "now" (default) the status will be
     33        required to be current. If any of the three parameters are set
     34        to another value, the corresponding filter will be applied.
     35        """
     36        office_holders = OfficeHolder.objects.filter(group=self,)
     37        if role:
     38            if isinstance(role, str):
     39                office_holders = office_holders.filter(role__slug=role)
     40            else:
     41                office_holders = office_holders.filter(role=role)
     42        if person:
     43            office_holders = office_holders.filter(person=person)
     44        if as_of:
     45            if as_of == "now": as_of = datetime.datetime.now()
     46            office_holders = office_holders.filter(start_time__lte=as_of, end_time__gte=as_of)
     47        return office_holders
     48
    2649    def __str__(self, ):
    2750        return self.name
     
    2952    class Meta:
    3053        ordering = ('name', )
     54
     55
     56class OfficerRole(models.Model):
     57    UNLIMITED = 10000
     58
     59    display_name = models.CharField(max_length=50)
     60    slug = models.SlugField()
     61    description = models.TextField()
     62    max_count = models.IntegerField(default=UNLIMITED, help_text='Maximum number of holders of this role. Use %d for no limit.' % UNLIMITED)
     63    require_student = models.BooleanField(default=False)
     64
     65    def __str__(self, ):
     66        return self.display_name
     67
     68    @classmethod
     69    def retrieve(cls, slug, ):
     70        return cls.objects.get(slug=slug)
     71
     72
     73class OfficeHolder(models.Model):
     74    EXPIRE_OFFSET = datetime.timedelta(seconds=1)
     75
     76    person = models.CharField(max_length=30)
     77    role = models.ForeignKey('OfficerRole')
     78    group = models.ForeignKey('Group')
     79    start_time = models.DateTimeField(default=datetime.datetime.now)
     80    end_time = models.DateTimeField(default=datetime.datetime.max)
     81
     82    def expire(self, ):
     83        self.end_time = datetime.datetime.now()-self.EXPIRE_OFFSET
     84        self.save()
     85
     86    def __str__(self, ):
     87        return "<OfficeHolder: person=%s, role=%s, group=%s, start_time=%s, end_time=%s>" % (
     88            self.person, self.role, self.group, self.start_time, self.end_time, )
     89
     90    def __repr__(self, ):
     91        return str(self)
     92
    3193
    3294class ActivityCategory(models.Model):
     
    38100    class Meta:
    39101        verbose_name_plural = "activity categories"
     102
     103
     104class AthenaMoiraAccount_ActiveManager(models.Manager):
     105    def get_query_set(self, ):
     106        return super(AthenaMoiraAccount_ActiveManager, self).get_query_set().filter(del_date=None)
     107
     108class AthenaMoiraAccount(models.Model):
     109    username = models.CharField(max_length=8)
     110    mit_id = models.CharField(max_length=15)
     111    first_name      = models.CharField(max_length=45)
     112    last_name       = models.CharField(max_length=45)
     113    account_class   = models.CharField(max_length=10)
     114    mutable         = models.BooleanField(default=True)
     115    add_date        = models.DateField(help_text="Date when this person was added to the dump.", )
     116    del_date        = models.DateField(help_text="Date when this person was removed from the dump.", blank=True, null=True, )
     117    mod_date        = models.DateField(help_text="Date when this person's record was last changed.", blank=True, null=True, )
     118
     119    objects = models.Manager()
     120    active_accounts = AthenaMoiraAccount_ActiveManager()
     121
     122    def is_student(self, ):
     123        # XXX: Is this... right?
     124        return self.account_class == 'G' or self.account_class.isdigit()
     125
     126    def __str__(self, ):
     127        if self.mutable:
     128            mutable_str = ""
     129        else:
     130            mutable_str = " (immutable)"
     131        return "<AthenaMoiraAccount: username=%s name='%s, %s' account_class=%s%s>" % (
     132            self.username, self.last_name, self.first_name,
     133            self.account_class, mutable_str,
     134        )
     135
     136    def __repr__(self, ):
     137        return str(self)
     138
     139    class Meta:
     140        verbose_name = "Athena (Moira) account"
  • asadb/groups/views.py

    r5be39cc r89be44c  
    11# Create your views here.
     2
     3import groups.models
     4
     5from django.contrib.auth.decorators import user_passes_test, login_required
     6from django.contrib.contenttypes.models import ContentType
     7from django.core.exceptions import PermissionDenied
     8from django.views.generic import 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
     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
     19from django.db.models import Q
     20
     21import form_utils.forms
     22import reversion.models
     23
     24class GroupChangeMainForm(form_utils.forms.BetterModelForm):
     25    class Meta:
     26        fieldsets = [
     27            ('basic', {
     28                'legend': 'Basic Information',
     29                'fields': ['name', 'abbreviation', 'description', 'activity_category', ],
     30            }),
     31            ('size', {
     32                'legend':'Membership Numbers',
     33                'fields': ['num_undergrads', 'num_grads', 'num_community', 'num_other',],
     34            }),
     35            ('contact', {
     36                'legend': 'Contact Information',
     37                'fields': ['website_url', 'meeting_times', 'group_email', 'officer_email', ],
     38            }),
     39            ('more-info', {
     40                'legend': 'Additional Information',
     41                'fields': ['constitution_url', 'advisor_name', 'main_account_id', 'funding_account_id', 'athena_locker', 'recognition_date', ],
     42            }),
     43        ]
     44        model = groups.models.Group
     45
     46def manage_main(request, group_id, ):
     47    group = get_object_or_404(groups.models.Group, pk=group_id)
     48
     49    if not request.user.has_perm('groups.change_group', group):
     50        raise PermissionDenied
     51
     52    msg = None
     53
     54    initial = {}
     55    if request.method == 'POST': # If the form has been submitted...
     56        form = GroupChangeMainForm(request.POST, request.FILES, instance=group, ) # A form bound to the POST data
     57
     58        if form.is_valid(): # All validation rules pass
     59            request_obj = form.save()
     60
     61            # Send email
     62            #tmpl = get_template('fysm/update_email.txt')
     63            #ctx = Context({
     64            #    'group': group_obj,
     65            #    'fysm': fysm_obj,
     66            #    'view_uri': view_uri,
     67            #    'submitter': request.user,
     68            #    'request': request,
     69            #    'sender': "ASA FYSM team",
     70            #})
     71            #body = tmpl.render(ctx)
     72            #email = EmailMessage(
     73            #    subject='FYSM entry for "%s" updated by "%s"' % (
     74            #        group_obj.name,
     75            #        request.user,
     76            #    ),
     77            #    body=body,
     78            #    from_email='asa-fysm@mit.edu',
     79            #    to=[group_obj.officer_email, request.user.email, ],
     80            #    bcc=['asa-fysm-submissions@mit.edu', ]
     81            #)
     82            #email.send()
     83            msg = "Thanks for editing!"
     84
     85    else:
     86        form = GroupChangeMainForm(instance=group, initial=initial, ) # An unbound form
     87
     88    context = {
     89        'group': group,
     90        'form':  form,
     91        'msg':   msg,
     92    }
     93    return render_to_response('groups/group_change_main.html', context, context_instance=RequestContext(request), )
     94
     95class GroupDetailView(DetailView):
     96    context_object_name = "group"
     97    model = groups.models.Group
     98    def get_context_data(self, **kwargs):
     99        # Call the base implementation first to get a context
     100        context = super(GroupDetailView, self).get_context_data(**kwargs)
     101        group = context['group']
     102
     103        # Indicate whether this person should be able to see "private" info
     104        context['viewpriv'] = self.request.user.has_perm('groups.view_group_private_info', group)
     105        return context
     106
     107class GroupHistoryView(ListView):
     108    context_object_name = "version_list"
     109    template_name = "groups/group_version.html"
     110
     111    def get_queryset(self):
     112        history_entries = None
     113        if 'group' in self.kwargs:
     114            group = get_object_or_404(groups.models.Group, pk=self.kwargs['group'])
     115            history_entries = reversion.models.Version.objects.get_for_object(group)
     116        else:
     117            history_entries = reversion.models.Version.objects.all()
     118            group_content_type = ContentType.objects.get_for_model(groups.models.Group)
     119            history_entries = history_entries.filter(content_type=group_content_type)
     120        length = len(history_entries)
     121        if length > 150:
     122            history_entries = history_entries[length-100:]
     123        return history_entries
     124
     125    def get_context_data(self, **kwargs):
     126        context = super(GroupHistoryView, self).get_context_data(**kwargs)
     127        if 'group' in self.kwargs:
     128            group = get_object_or_404(groups.models.Group, pk=self.kwargs['group'])
     129            context['title'] = "History for %s" % (group.name, )
     130        else:
     131            context['title'] = "Recent Changes"
     132        return context
     133
     134
     135def load_officers(group, ):
     136    officers = group.officers()
     137    people = list(set([ officer.person for officer in officers ]))
     138    roles  = groups.models.OfficerRole.objects.all()
     139
     140    officers_map = {}
     141    for officer in officers:
     142        officers_map[(officer.person, officer.role)] = officer
     143
     144    return people, roles, officers_map
     145
     146def manage_officers(request, group_id, ):
     147    group = get_object_or_404(groups.models.Group, pk=group_id)
     148
     149    if not request.user.has_perm('groups.change_group', group):
     150        raise PermissionDenied
     151
     152    max_new = 4
     153
     154    people, roles, officers_map = load_officers(group)
     155
     156    msgs = []
     157    changes = []
     158    edited = False
     159    kept = 0
     160    kept_not = 0
     161    if request.method == 'POST': # If the form has been submitted
     162        edited = True
     163
     164        new_people = {}
     165        moira_accounts = {}
     166        for i in range(max_new):
     167            key = "extra.%d" % (i, )
     168            if key in request.POST and request.POST[key] != "":
     169                username = request.POST[key]
     170                try:
     171                    moira_accounts[username] = groups.models.AthenaMoiraAccount.active_accounts.get(username=username)
     172                    new_people[i] = username
     173                except groups.models.AthenaMoiraAccount.DoesNotExist:
     174                    msgs.append('Athena account "%s" appears not to exist. Changes involving them have been ignored.' % (username, ))
     175        for person in people:
     176            try:
     177                moira_accounts[person] = groups.models.AthenaMoiraAccount.active_accounts.get(username=person)
     178            except groups.models.AthenaMoiraAccount.DoesNotExist:
     179                msgs.append('Athena account "%s" appears not to exist. They can not be added to new roles. You should remove them from any roles they hold, if you have not already.' % (person, ))
     180        for role in roles:
     181            key = "holders.%s" % (role.slug, )
     182            new_holders = set()
     183            if key in request.POST:
     184                new_holders = set(request.POST.getlist(key, ))
     185            if len(new_holders) > role.max_count:
     186                msgs.append("You selected %d people for %s; only %d are allowed. No changes to %s have been carried out in this update." %
     187                    (len(new_holders), role.display_name, role.max_count, role.display_name, )
     188                )
     189            else:
     190                for person in people:
     191                    if person in new_holders:
     192                        if (person, role) in officers_map:
     193                            if role.require_student and not moira_accounts[person].is_student():
     194                                msgs.append('Only students can have the %s role, and %s does not appear to be a student. You should replace this person ASAP.' % (role, person, ))
     195                            #changes.append(("Kept", "yellow", person, role))
     196                            kept += 1
     197                        else:
     198                            if person not in moira_accounts:
     199                                msgs.append('Could not add nonexistent Athena account "%s" as %s.' % (person, role, ))
     200                            elif role.require_student and not moira_accounts[person].is_student():
     201                                msgs.append('Only students can have the %s role, and %s does not appear to be a student.' % (role, person, ))
     202                            else:
     203                                holder = groups.models.OfficeHolder(person=person, role=role, group=group,)
     204                                holder.save()
     205                                changes.append(("Added", "green", person, role))
     206                    else:
     207                        if (person, role) in officers_map:
     208                            officers_map[(person, role)].expire()
     209                            changes.append(("Removed", "red", person, role))
     210                        else:
     211                            kept_not += 1
     212                            pass
     213                for i in range(max_new):
     214                    if "extra.%d" % (i, ) in new_holders:
     215                        if i in new_people:
     216                            person = new_people[i]
     217                            if role.require_student and not moira_accounts[person].is_student():
     218                                msgs.append('Only students can have the %s role, and %s does not appear to be a student.' % (role, person, ))
     219                            else:
     220                                holder = groups.models.OfficeHolder(person=person, role=role, group=group,)
     221                                holder.save()
     222                                changes.append(("Added", "green", person, role))
     223
     224        # reload the data
     225        people, roles, officers_map = load_officers(group)
     226
     227    officers_data = []
     228    for person in people:
     229        role_list = []
     230        for role in roles:
     231            if (person, role) in officers_map:
     232                role_list.append((role, True))
     233            else:
     234                role_list.append((role, False))
     235        officers_data.append((False, person, role_list))
     236    null_role_list = [(role, False) for role in roles]
     237    for i in range(max_new):
     238        officers_data.append((True, "extra.%d" % (i, ), null_role_list))
     239
     240    context = {
     241        'group': group,
     242        'roles': roles,
     243        'people': people,
     244        'officers': officers_data,
     245        'edited': edited,
     246        'changes':   changes,
     247        'kept': kept,
     248        'kept_not': kept_not,
     249        'msgs': msgs,
     250    }
     251    return render_to_response('groups/group_change_officers.html', context, context_instance=RequestContext(request), )
  • asadb/media/style/style.css

    r3400018 ree679c6  
    77{
    88    border-collapse: collapse;
    9 }
    10 table.pretty-table th, table.pretty-table td
     9    margin: 0.5em;
     10}
     11table.pretty-table th, table.pretty-table td, table.pretty-table caption
    1112{
    1213    border: 1px solid black;
    1314    padding: 2px;
    1415}
    15 table.pretty-table th
    16 {
     16table.pretty-table caption
     17{
     18    border-bottom: none;
     19}
     20table.pretty-table th, table.pretty-table caption
     21{
     22    font-weight: bold;
    1723    background: #BE2933;
    1824}
     
    202208
    203209
     210/*****************
     211 * GROUP DISPLAY *
     212 *****************/
     213tr.private-info th:before
     214{
     215    content: "* ";
     216}
     217
     218
    204219/* Reset some stuff */
    205220h1, h2, h3, th
     
    212227    margin-left: 0;
    213228}
     229
     230table.group-change-officers-change td
     231{
     232    text-align: center;
     233}
  • asadb/urls.py

    rb01b0ba ree679c6  
    1010
    1111import forms.views
     12import groups.models
     13import groups.views
    1214
    13 import groups.models
    1415
    1516urlpatterns = patterns('',
     
    3536    url(r'^fysm/(?:(\d+)/)?(?:([\w-]+)/)?$', forms.views.fysm_by_years, name='fysm', ),
    3637
     38    # Group editing
     39    url(r'^group/(\d+)/edit/main$', groups.views.manage_main, name='group-manage-main', ),
     40    url(r'^group/(\d+)/edit/officers$', groups.views.manage_officers, name='group-manage-officers', ),
     41
    3742    # Group list
    3843    url(
     
    4651        name='group-list',
    4752    ),
     53    url(r'^group/(?P<pk>\d+)/$', groups.views.GroupDetailView.as_view(), name='group-detail', ),
     54    url(r'^groups/recent_changes/$', groups.views.GroupHistoryView.as_view(), name='groups-manage-history', ),
     55    url(r'^group/(?P<group>\d+)/history/$', groups.views.GroupHistoryView.as_view(), name='group-manage-history', ),
    4856
    4957    # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
  • design/tools/audit-trail.txt

    r35d8e68 r5bb1780  
    3434    * Design
    3535        * Store all versions of all models in a single table, JSON'd
     36        * Metadata stored unpickled
     37            * Time
     38            * Editor
     39            * Some other stuff
    3640* django-reversion
    3741    * https://github.com/etianen/django-reversion
Note: See TracChangeset for help on using the changeset viewer.