Changeset bd72d5c


Ignore:
Timestamp:
Sep 15, 2011, 3:34:54 PM (15 years ago)
Author:
Alex Dehnert <adehnert@…>
Branches:
master, space-access, stable, stage, test-hooks
Children:
5c68184
Parents:
ceaf3bd (diff), fbb362a (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@…> (09/15/11 15:34:54)
git-committer:
Alex Dehnert <adehnert@…> (09/15/11 15:34:54)
Message:

Merge remote branch 'remotes/origin/new-db'

Files:
33 added
16 edited

Legend:

Unmodified
Added
Removed
  • .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/forms/views.py

    rceaf3bd rbd72d5c  
    121121def select_group_fysm(request, ):
    122122    qobj = Q(activity_category__isnull = True) | ~(Q(activity_category__name='Dorm') | Q(activity_category__name='FSILG'))
    123     queryset = groups.models.Group.objects.filter(qobj)
     123    queryset = groups.models.Group.active_groups.filter(qobj)
    124124    return select_group(
    125125        request,
  • asadb/groups/admin.py

    rbb674c2 r3107c52  
    11import groups.models
    22from django.contrib import admin
     3from reversion.admin import VersionAdmin
    34
    4 class GroupAdmin(admin.ModelAdmin):
     5class GroupAdmin(VersionAdmin):
    56    list_display = (
    67        'id',
     
    1617    )
    1718    list_display_links = ('id', 'name', )
    18     list_filter = [ 'activity_category', ]
     19    list_filter = [
     20        'activity_category',
     21        'group_class',
     22        'group_status',
     23        'group_funding',
     24    ]
    1925    date_hierarchy = 'update_date'
    2026    search_fields = [ 'id', 'name', 'abbreviation', 'officer_email', 'athena_locker', ]
     27admin.site.register(groups.models.Group, GroupAdmin)
     28
     29
     30class Admin_GroupNote(VersionAdmin):
     31    list_display = (
     32        'pk',
     33        'author',
     34        'timestamp',
     35        'acl_read_group',
     36        'acl_read_offices',
     37        'group',
     38    )
     39    list_display_links = ('pk', 'timestamp', )
     40    list_filter = [
     41        'acl_read_group',
     42        'acl_read_offices',
     43    ]
     44    date_hierarchy = 'timestamp'
     45    search_fields = [
     46        'author',
     47        'group__name',
     48        'group__abbreviation',
     49        'group__officer_email',
     50        'group__athena_locker',
     51    ]
     52admin.site.register(groups.models.GroupNote, Admin_GroupNote)
     53
     54
     55class OfficerRoleAdmin(VersionAdmin):
     56    list_display = (
     57        'id',
     58        'display_name',
     59        'slug',
     60        'max_count',
     61        'require_student',
     62        'publicly_visible',
     63        'grant_user',
     64    )
     65    list_display_links = ('id', 'display_name', 'slug', )
     66    prepopulated_fields = {"slug": ("display_name",)}
     67admin.site.register(groups.models.OfficerRole, OfficerRoleAdmin)
     68
     69
     70class OfficeHolderAdmin(VersionAdmin):
     71    list_display = (
     72        'id',
     73        'person',
     74        'role',
     75        'group',
     76        'start_time', 'end_time',
     77    )
     78    list_display_links = (
     79        'id',
     80        'person',
     81        'role',
     82        'group',
     83        'start_time', 'end_time',
     84    )
     85    search_fields = (
     86        'id',
     87        'person',
     88        'role__display_name', 'role__slug',
     89        'group__name', 'group__abbreviation',
     90        'start_time', 'end_time',
     91    )
     92admin.site.register(groups.models.OfficeHolder, OfficeHolderAdmin)
     93
    2194
    2295class ActivityCategoryAdmin(admin.ModelAdmin):
     
    2699    )
    27100    list_display_links = ('id', 'name', )
     101admin.site.register(groups.models.ActivityCategory, ActivityCategoryAdmin)
    28102
    29 admin.site.register(groups.models.Group, GroupAdmin)
    30 admin.site.register(groups.models.ActivityCategory, ActivityCategoryAdmin)
     103
     104class Admin_GroupClass(admin.ModelAdmin):
     105    list_display = (
     106        'id',
     107        'name',
     108        'slug',
     109        'gets_publicity',
     110    )
     111    list_display_links = ('id', 'name', 'slug', )
     112    list_filter = [ 'gets_publicity', ]
     113    prepopulated_fields = {'slug': ('name', )}
     114admin.site.register(groups.models.GroupClass, Admin_GroupClass)
     115
     116
     117class Admin_GroupStatus(admin.ModelAdmin):
     118    list_display = (
     119        'id',
     120        'name',
     121        'slug',
     122        'is_active',
     123    )
     124    list_display_links = ('id', 'name', 'slug', )
     125    list_filter = [ 'is_active', ]
     126    prepopulated_fields = {'slug': ('name', )}
     127admin.site.register(groups.models.GroupStatus, Admin_GroupStatus)
     128
     129
     130class Admin_GroupFunding(admin.ModelAdmin):
     131    list_display = (
     132        'id',
     133        'name',
     134        'slug',
     135        'contact_email',
     136        'funding_list',
     137    )
     138    list_display_links = ('id', 'name', 'slug', )
     139    prepopulated_fields = {'slug': ('name', )}
     140admin.site.register(groups.models.GroupFunding, Admin_GroupFunding)
     141
     142
     143class Admin_AthenaMoiraAccount(admin.ModelAdmin):
     144    list_display = (
     145        'id',
     146        'username',
     147        'mit_id',
     148        'first_name',
     149        'last_name',
     150        'account_class',
     151        'mutable',
     152        'add_date',
     153        'del_date',
     154        'mod_date',
     155    )
     156    list_display_links = ( 'id', 'username', )
     157    search_fields = ( 'username', 'mit_id', 'first_name', 'last_name', 'account_class', )
     158admin.site.register(groups.models.AthenaMoiraAccount, Admin_AthenaMoiraAccount)
  • asadb/groups/models.py

    r2a6907a r3275ab1  
    11from django.db import models
     2from django.contrib.auth.models import User
     3
     4import datetime
     5
     6import settings
    27
    38# Create your models here.
     9
     10class ActiveGroupManager(models.Manager):
     11    def get_query_set(self, ):
     12        return super(ActiveGroupManager, self).get_query_set().filter(
     13            group_status__slug='active',
     14        )
     15
    416class Group(models.Model):
    517    name = models.CharField(max_length=100)
     
    719    description = models.TextField()
    820    activity_category = models.ForeignKey('ActivityCategory', null=True, blank=True, )
     21    group_class = models.ForeignKey('GroupClass')
     22    group_status = models.ForeignKey('GroupStatus')
     23    group_funding = models.ForeignKey('GroupFunding', null=True, blank=True, )
    924    website_url = models.URLField()
    1025    constitution_url = models.CharField(max_length=200, blank=True)
     
    2136    athena_locker = models.CharField(max_length=20, blank=True)
    2237    recognition_date = models.DateField()
    23     update_date = models.DateTimeField()
    24     updater = models.CharField(max_length=30) # match Django username field
     38    update_date = models.DateTimeField(editable=False, )
     39    updater = models.CharField(max_length=30, editable=False, null=True, ) # match Django username field
     40    _updater_set = False
     41
     42    objects = models.Manager()
     43    active_groups = ActiveGroupManager()
     44
     45    def update_string(self, ):
     46        updater = self.updater or "unknown"
     47        return "%s by %s" % (self.update_date.strftime(settings.DATETIME_FORMAT_PYTHON), updater, )
     48
     49    def set_updater(self, who):
     50        if hasattr(who, 'username'):
     51            self.updater = who.username
     52        else:
     53            self.updater = who
     54        self._updater_set = True
     55
     56    def save(self, ):
     57        if not self._updater_set:
     58            self.updater = None
     59        self.update_date = datetime.datetime.now()
     60        super(Group, self).save()
     61
     62    def viewable_notes(self, user):
     63        return GroupNote.viewable_notes(self, user)
     64
     65    def officers(self, role=None, person=None, as_of="now",):
     66        """Get the set of people holding some office.
     67
     68        If None is passed for role, person, or as_of, that field will not
     69        be constrained. If as_of is "now" (default) the status will be
     70        required to be current. If any of the three parameters are set
     71        to another value, the corresponding filter will be applied.
     72        """
     73        office_holders = OfficeHolder.objects.filter(group=self,)
     74        if role:
     75            if isinstance(role, str):
     76                office_holders = office_holders.filter(role__slug=role)
     77            else:
     78                office_holders = office_holders.filter(role=role)
     79        if person:
     80            office_holders = office_holders.filter(person=person)
     81        if as_of:
     82            if as_of == "now": as_of = datetime.datetime.now()
     83            office_holders = office_holders.filter(start_time__lte=as_of, end_time__gte=as_of)
     84        return office_holders
    2585
    2686    def __str__(self, ):
     
    2989    class Meta:
    3090        ordering = ('name', )
     91        permissions = (
     92            ('view_group_private_info', 'View private group information'),
     93            # ability to update normal group info or people
     94            # this is weaker than change_group, which is the built-in
     95            # permission that controls the admin interface
     96            ('admin_group', 'Administer basic group information'),
     97            ('view_signatories', 'View signatory information for all groups'),
     98        )
     99
     100
     101class GroupNote(models.Model):
     102    author = models.CharField(max_length=30, ) # match Django username field
     103    timestamp = models.DateTimeField(default=datetime.datetime.now, editable=False, )
     104    body = models.TextField()
     105    acl_read_group = models.BooleanField(default=True, help_text='Can the group read this note')
     106    acl_read_offices = models.BooleanField(default=True, help_text='Can "offices" that interact with groups (SAO, CAC, and funding boards) read this note')
     107    group = models.ForeignKey(Group)
     108
     109    def __str__(self, ):
     110        return "Note by %s on %s" % (self.author, self.timestamp, )
     111
     112    @classmethod
     113    def viewable_notes(cls, group, user):
     114        notes = cls.objects.filter(group=group)
     115        if not user.has_perm('groups.view_note_all'):
     116            q = models.Q(pk=0)
     117            if user.has_perm('groups.view_note_group', group):
     118                q |= models.Q(acl_read_group=True)
     119            if user.has_perm('groups.view_note_office'):
     120                q |= models.Q(acl_read_offices=True)
     121            notes = notes.filter(q)
     122        return notes
     123
     124    class Meta:
     125        permissions = (
     126            ('view_note_group',     'View notes intended for the group to see', ),
     127            ('view_note_office',    'View notes intended for "offices" to see', ),
     128            ('view_note_all',       'View all notes', ),
     129        )
     130
     131
     132class OfficerRole(models.Model):
     133    UNLIMITED = 10000
     134
     135    display_name = models.CharField(max_length=50)
     136    slug = models.SlugField()
     137    description = models.TextField()
     138    max_count = models.IntegerField(default=UNLIMITED, help_text='Maximum number of holders of this role. Use %d for no limit.' % UNLIMITED)
     139    require_student = models.BooleanField(default=False)
     140    grant_user = models.ForeignKey(User, null=True, blank=True,
     141        limit_choices_to={ 'username__endswith': '@SYSTEM'})
     142    publicly_visible = models.BooleanField(default=True, help_text='Can everyone see the holders of this office.')
     143
     144    def max_count_str(self, ):
     145        if self.max_count == self.UNLIMITED:
     146            return "unlimited"
     147        else:
     148            return str(self.max_count)
     149
     150    def __str__(self, ):
     151        return self.display_name
     152
     153    @classmethod
     154    def getGrantUsers(cls, roles):
     155        ret = set([role.grant_user for role in roles])
     156        if None in ret: ret.remove(None)
     157        return ret
     158
     159    @classmethod
     160    def retrieve(cls, slug, ):
     161        return cls.objects.get(slug=slug)
     162
     163
     164class OfficeHolder_CurrentManager(models.Manager):
     165    def get_query_set(self, ):
     166        return super(OfficeHolder_CurrentManager, self).get_query_set().filter(
     167            start_time__lte=datetime.datetime.now,
     168            end_time__gte=datetime.datetime.now,
     169        )
     170
     171class OfficeHolder(models.Model):
     172    EXPIRE_OFFSET   = datetime.timedelta(seconds=1)
     173    END_NEVER       = datetime.datetime.max
     174
     175    person = models.CharField(max_length=30)
     176    role = models.ForeignKey('OfficerRole')
     177    group = models.ForeignKey('Group')
     178    start_time = models.DateTimeField(default=datetime.datetime.now)
     179    end_time = models.DateTimeField(default=datetime.datetime.max)
     180
     181    objects = models.Manager()
     182    current_holders = OfficeHolder_CurrentManager()
     183
     184    def expire(self, ):
     185        self.end_time = datetime.datetime.now()-self.EXPIRE_OFFSET
     186        self.save()
     187
     188    def __str__(self, ):
     189        return "<OfficeHolder: person=%s, role=%s, group=%s, start_time=%s, end_time=%s>" % (
     190            self.person, self.role, self.group, self.start_time, self.end_time, )
     191
     192    def __repr__(self, ):
     193        return str(self)
     194
     195
     196class PerGroupAuthz:
     197    supports_anonymous_user = True
     198    supports_inactive_user = True
     199    supports_object_permissions = True
     200
     201    def authenticate(self, username=None, password=None, ):
     202        return None # we don't do authn
     203    def get_user(user_id, ):
     204        return None # we don't do authn
     205
     206    def has_perm(self, user_obj, perm, obj=None, ):
     207        print "Checking user %s for perm %s on obj %s" % (user_obj, perm, obj)
     208        if not user_obj.is_active:
     209            return False
     210        if not user_obj.is_authenticated():
     211            return False
     212        if obj is None:
     213            return False
     214        # Great, we're active, authenticated, and not in a recursive call
     215        # Check that we've got a reasonable object
     216        if getattr(user_obj, 'is_system', False):
     217            return False
     218        if isinstance(obj, Group):
     219            # Now we can do the real work
     220            holders = obj.officers(person=user_obj.username).select_related('role__grant_user')
     221            sys_users = OfficerRole.getGrantUsers([holder.role for holder in holders])
     222            for sys_user in sys_users:
     223                sys_user.is_system = True
     224                if sys_user.has_perm(perm):
     225                    print "While checking user %s for perm %s on obj %s: implicit user %s has perms" % (user_obj, perm, obj, sys_user, )
     226                    return True
     227        print "While checking user %s for perm %s on obj %s: no perms found (implicit: %s)" % (user_obj, perm, obj, sys_users)
     228        return False
     229
     230
    31231
    32232class ActivityCategory(models.Model):
     
    38238    class Meta:
    39239        verbose_name_plural = "activity categories"
     240
     241
     242class GroupClass(models.Model):
     243    name = models.CharField(max_length=50)
     244    slug = models.SlugField(unique=True, )
     245    description = models.TextField()
     246    gets_publicity = models.BooleanField(help_text="Gets publicity resources such as FYSM or Activities Midway")
     247
     248    def __str__(self, ):
     249        return self.name
     250
     251    class Meta:
     252        verbose_name_plural = "group classes"
     253
     254
     255class GroupStatus(models.Model):
     256    name = models.CharField(max_length=50)
     257    slug = models.SlugField(unique=True, )
     258    description = models.TextField()
     259    is_active = models.BooleanField(default=True, help_text="This status represents an active group")
     260
     261    def __str__(self, ):
     262        active = ""
     263        if not self.is_active:
     264            active = " (inactive)"
     265        return "%s%s" % (self.name, active, )
     266
     267    class Meta:
     268        verbose_name_plural= "group statuses"
     269
     270
     271class GroupFunding(models.Model):
     272    name = models.CharField(max_length=50)
     273    slug = models.SlugField(unique=True, )
     274    contact_email = models.EmailField()
     275    funding_list = models.CharField(max_length=32, blank=True, help_text="List that groups receiving funding emails should be on. The database will attempt to make sure that ONLY those groups are on it.")
     276
     277    def __str__(self, ):
     278        return "%s (%s)" % (self.name, self.contact_email, )
     279
     280
     281class AthenaMoiraAccount_ActiveManager(models.Manager):
     282    def get_query_set(self, ):
     283        return super(AthenaMoiraAccount_ActiveManager, self).get_query_set().filter(del_date=None)
     284
     285class AthenaMoiraAccount(models.Model):
     286    username = models.CharField(max_length=8)
     287    mit_id = models.CharField(max_length=15)
     288    first_name      = models.CharField(max_length=45)
     289    last_name       = models.CharField(max_length=45)
     290    account_class   = models.CharField(max_length=10)
     291    mutable         = models.BooleanField(default=True)
     292    add_date        = models.DateField(help_text="Date when this person was added to the dump.", )
     293    del_date        = models.DateField(help_text="Date when this person was removed from the dump.", blank=True, null=True, )
     294    mod_date        = models.DateField(help_text="Date when this person's record was last changed.", blank=True, null=True, )
     295
     296    objects = models.Manager()
     297    active_accounts = AthenaMoiraAccount_ActiveManager()
     298
     299    def is_student(self, ):
     300        # XXX: Is this... right?
     301        return self.account_class == 'G' or self.account_class.isdigit()
     302
     303    def __str__(self, ):
     304        if self.mutable:
     305            mutable_str = ""
     306        else:
     307            mutable_str = " (immutable)"
     308        return "<AthenaMoiraAccount: username=%s name='%s, %s' account_class=%s%s>" % (
     309            self.username, self.last_name, self.first_name,
     310            self.account_class, mutable_str,
     311        )
     312
     313    def __repr__(self, ):
     314        return str(self)
     315
     316    class Meta:
     317        verbose_name = "Athena (Moira) account"
  • asadb/groups/views.py

    r5be39cc rfbb362a  
    11# Create your views here.
     2
     3import collections
     4import datetime
     5
     6import groups.models
     7
     8from django.contrib.auth.decorators import user_passes_test, login_required, permission_required
     9from django.contrib.contenttypes.models import ContentType
     10from django.core.exceptions import PermissionDenied
     11from django.views.generic import ListView, DetailView
     12from django.shortcuts import render_to_response, get_object_or_404, redirect
     13from django.template import RequestContext
     14from django.template import Context, Template
     15from django.template.loader import get_template
     16from django.http import Http404, HttpResponseRedirect
     17from django.core.urlresolvers import reverse
     18from django.core.mail import EmailMessage, mail_admins
     19from django.forms import Form
     20from django.forms import ModelForm
     21from django.forms import ModelChoiceField
     22from django.db import connection
     23from django.db.models import Q
     24
     25import form_utils.forms
     26import reversion.models
     27import django_filters
     28
     29from util.db_form_utils import StaticWidget
     30
     31
     32def view_homepage(request, ):
     33    users_groups = []
     34    groupmsg = ""
     35    has_perms = []
     36    if request.user.is_authenticated():
     37        username = request.user.username
     38        current_officers = groups.models.OfficeHolder.current_holders.filter(person=username)
     39        users_groups = groups.models.Group.objects.filter(officeholder__in=current_officers).distinct()
     40
     41        perms = []
     42        perms.extend(groups.models.Group._meta.permissions)
     43        perms.extend(groups.models.GroupNote._meta.permissions)
     44        perms += (
     45            ('change_group', 'Change arbitrary group information', ),
     46        )
     47        for perm_name, perm_desc in perms:
     48            if request.user.has_perm('groups.%s' % (perm_name, )):
     49                has_perms.append((perm_name, perm_desc, ))
     50
     51    context = {
     52        'groups': users_groups,
     53        'groupmsg': groupmsg,
     54        'has_perms': has_perms,
     55        'pagename': 'homepage',
     56    }
     57    return render_to_response('index.html', context, context_instance=RequestContext(request), )
     58
     59
     60class GroupChangeMainForm(form_utils.forms.BetterModelForm):
     61    def __init__(self, *args, **kwargs):
     62        change_restricted = False
     63        if 'change_restricted' in kwargs:
     64            change_restricted = kwargs['change_restricted']
     65            del kwargs['change_restricted']
     66        super(GroupChangeMainForm, self).__init__(*args, **kwargs)
     67        restricted_fields = list(self.nobody_fields)
     68        if change_restricted:
     69            restricted_fields.extend(self.exec_only_fields)
     70        for field_name in restricted_fields:
     71            formfield = self.fields[field_name]
     72            value = getattr(self.instance, field_name)
     73            StaticWidget.replace_widget(formfield, value)
     74
     75    exec_only_fields = [
     76        'name', 'abbreviation',
     77        'group_status', 'group_class',
     78        'group_funding', 'main_account_id', 'funding_account_id',
     79    ]
     80    nobody_fields = [
     81        'recognition_date',
     82    ]
     83
     84    class Meta:
     85        fieldsets = [
     86            ('basic', {
     87                'legend': 'Basic Information',
     88                'fields': ['name', 'abbreviation', 'activity_category', 'description', ],
     89            }),
     90            ('size', {
     91                'legend':'Membership Numbers',
     92                'fields': ['num_undergrads', 'num_grads', 'num_community', 'num_other',],
     93            }),
     94            ('contact', {
     95                'legend': 'Contact Information',
     96                'fields': ['website_url', 'meeting_times', 'officer_email', 'group_email', ],
     97            }),
     98            ('recognition', {
     99                'legend': 'Recognition',
     100                'fields': ['group_status', 'group_class', 'recognition_date', ],
     101            }),
     102            ('financial', {
     103                'legend': 'Financial Information',
     104                'fields': ['group_funding', 'main_account_id', 'funding_account_id', ],
     105            }),
     106            ('more-info', {
     107                'legend': 'Additional Information',
     108                'fields': ['constitution_url', 'advisor_name', 'athena_locker', ],
     109            }),
     110        ]
     111        model = groups.models.Group
     112
     113def manage_main(request, pk, ):
     114    group = get_object_or_404(groups.models.Group, pk=pk)
     115
     116    if not request.user.has_perm('groups.admin_group', group):
     117        raise PermissionDenied
     118    change_restricted = True
     119    if request.user.has_perm('groups.change_group', group):
     120        change_restricted = False
     121
     122    msg = None
     123
     124    initial = {}
     125    if request.method == 'POST': # If the form has been submitted...
     126        # A form bound to the POST data
     127        form = GroupChangeMainForm(
     128            request.POST, request.FILES,
     129            change_restricted=change_restricted,
     130            instance=group,
     131        )
     132
     133        if form.is_valid(): # All validation rules pass
     134            request_obj = form.save(commit=False)
     135            request_obj.set_updater(request.user)
     136            request_obj.save()
     137            form.save_m2m()
     138
     139            # Send email
     140            #tmpl = get_template('fysm/update_email.txt')
     141            #ctx = Context({
     142            #    'group': group_obj,
     143            #    'fysm': fysm_obj,
     144            #    'view_uri': view_uri,
     145            #    'submitter': request.user,
     146            #    'request': request,
     147            #    'sender': "ASA FYSM team",
     148            #})
     149            #body = tmpl.render(ctx)
     150            #email = EmailMessage(
     151            #    subject='FYSM entry for "%s" updated by "%s"' % (
     152            #        group_obj.name,
     153            #        request.user,
     154            #    ),
     155            #    body=body,
     156            #    from_email='asa-fysm@mit.edu',
     157            #    to=[group_obj.officer_email, request.user.email, ],
     158            #    bcc=['asa-fysm-submissions@mit.edu', ]
     159            #)
     160            #email.send()
     161            msg = "Thanks for editing!"
     162        else:
     163            msg = "Validation failed. See below for details."
     164
     165    else:
     166        form = GroupChangeMainForm(change_restricted=change_restricted, instance=group, initial=initial, ) # An unbound form
     167
     168    context = {
     169        'group': group,
     170        'form':  form,
     171        'msg':   msg,
     172    }
     173    return render_to_response('groups/group_change_main.html', context, context_instance=RequestContext(request), )
     174
     175
     176class GroupFilter(django_filters.FilterSet):
     177    name = django_filters.CharFilter(lookup_type='icontains', label="Name contains")
     178    abbreviation = django_filters.CharFilter(lookup_type='iexact', label="Abbreviation is")
     179
     180    class Meta:
     181        model = groups.models.Group
     182        fields = [
     183            'name',
     184            'abbreviation',
     185            'activity_category',
     186            'group_class',
     187            'group_status',
     188            'group_funding',
     189        ]
     190
     191
     192class GroupListView(ListView):
     193    model = groups.models.Group
     194    template_object_name = 'group'
     195
     196    def get(self, *args, **kwargs):
     197        qs = super(GroupListView, self).get_queryset()
     198        self.filterset = GroupFilter(self.request.GET, qs)
     199        return super(GroupListView, self).get(*args, **kwargs)
     200
     201    def get_queryset(self, ):
     202        qs = self.filterset.qs
     203        return qs
     204
     205    def get_context_data(self, **kwargs):
     206        context = super(GroupListView, self).get_context_data(**kwargs)
     207        # Add in the publisher
     208        context['pagename'] = 'groups'
     209        context['filter'] = self.filterset
     210        return context
     211
     212
     213class GroupDetailView(DetailView):
     214    context_object_name = "group"
     215    model = groups.models.Group
     216    def get_context_data(self, **kwargs):
     217        # Call the base implementation first to get a context
     218        context = super(GroupDetailView, self).get_context_data(**kwargs)
     219        group = context['group']
     220
     221        # Indicate whether this person should be able to see "private" info
     222        context['viewpriv'] = self.request.user.has_perm('groups.view_group_private_info', group)
     223        context['adminpriv'] = self.request.user.has_perm('groups.admin_group', group)
     224        context['notes'] = group.viewable_notes(self.request.user)
     225
     226        # People involved in the group
     227        just_roles = groups.models.OfficerRole.objects.all()
     228        if context['viewpriv'] or self.request.user.has_perm('groups.view_signatories'):
     229            # Can see the non-public stuff
     230            pass
     231        else:
     232            just_roles = just_roles.filter(publicly_visible=True)
     233        roles = []
     234        for role in just_roles:
     235            roles.append((role.display_name, role, group.officers(role=role), ))
     236        context['roles'] = roles
     237
     238        return context
     239
     240
     241class GroupHistoryView(ListView):
     242    context_object_name = "version_list"
     243    template_name = "groups/group_version.html"
     244
     245    def get_queryset(self):
     246        history_entries = None
     247        if 'pk' in self.kwargs:
     248            group = get_object_or_404(groups.models.Group, pk=self.kwargs['pk'])
     249            history_entries = reversion.models.Version.objects.get_for_object(group)
     250        else:
     251            history_entries = reversion.models.Version.objects.all()
     252            group_content_type = ContentType.objects.get_for_model(groups.models.Group)
     253            history_entries = history_entries.filter(content_type=group_content_type)
     254        length = len(history_entries)
     255        if length > 150:
     256            history_entries = history_entries[length-100:]
     257        return history_entries
     258
     259    def get_context_data(self, **kwargs):
     260        context = super(GroupHistoryView, self).get_context_data(**kwargs)
     261        if 'pk' in self.kwargs:
     262            group = get_object_or_404(groups.models.Group, pk=self.kwargs['pk'])
     263            context['title'] = "History for %s" % (group.name, )
     264        else:
     265            context['title'] = "Recent Changes"
     266        return context
     267
     268
     269def load_officers(group, ):
     270    officers = group.officers()
     271    people = list(set([ officer.person for officer in officers ]))
     272    roles  = groups.models.OfficerRole.objects.all()
     273
     274    officers_map = {}
     275    for officer in officers:
     276        officers_map[(officer.person, officer.role)] = officer
     277
     278    return people, roles, officers_map
     279
     280def manage_officers(request, pk, ):
     281    group = get_object_or_404(groups.models.Group, pk=pk)
     282
     283    if not request.user.has_perm('groups.admin_group', group):
     284        raise PermissionDenied
     285
     286    max_new = 4
     287
     288    people, roles, officers_map = load_officers(group)
     289
     290    msgs = []
     291    changes = []
     292    edited = False
     293    kept = 0
     294    kept_not = 0
     295    if request.method == 'POST': # If the form has been submitted
     296        edited = True
     297
     298        new_people = {}
     299        moira_accounts = {}
     300        for i in range(max_new):
     301            key = "extra.%d" % (i, )
     302            if key in request.POST and request.POST[key] != "":
     303                username = request.POST[key]
     304                try:
     305                    moira_accounts[username] = groups.models.AthenaMoiraAccount.active_accounts.get(username=username)
     306                    new_people[i] = username
     307                except groups.models.AthenaMoiraAccount.DoesNotExist:
     308                    msgs.append('Athena account "%s" appears not to exist. Changes involving them have been ignored.' % (username, ))
     309        for person in people:
     310            try:
     311                moira_accounts[person] = groups.models.AthenaMoiraAccount.active_accounts.get(username=person)
     312            except groups.models.AthenaMoiraAccount.DoesNotExist:
     313                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, ))
     314        for role in roles:
     315            key = "holders.%s" % (role.slug, )
     316            new_holders = set()
     317            if key in request.POST:
     318                new_holders = set(request.POST.getlist(key, ))
     319            if len(new_holders) > role.max_count:
     320                msgs.append("You selected %d people for %s; only %d are allowed. No changes to %s have been carried out in this update." %
     321                    (len(new_holders), role.display_name, role.max_count, role.display_name, )
     322                )
     323            else:
     324                for person in people:
     325                    if person in new_holders:
     326                        if (person, role) in officers_map:
     327                            if role.require_student and not moira_accounts[person].is_student():
     328                                msgs.append('Only students can have the %s role, and %s does not appear to be a student. (If this is not the case, please contact us.) You should replace this person ASAP.' % (role, person, ))
     329                            #changes.append(("Kept", "yellow", person, role))
     330                            kept += 1
     331                        else:
     332                            if person not in moira_accounts:
     333                                msgs.append('Could not add nonexistent Athena account "%s" as %s.' % (person, role, ))
     334                            elif role.require_student and not moira_accounts[person].is_student():
     335                                msgs.append('Only students can have the %s role, and %s does not appear to be a student. (If this is not the case, please contact us.)' % (role, person, ))
     336                            else:
     337                                holder = groups.models.OfficeHolder(person=person, role=role, group=group,)
     338                                holder.save()
     339                                changes.append(("Added", "green", person, role))
     340                    else:
     341                        if (person, role) in officers_map:
     342                            officers_map[(person, role)].expire()
     343                            changes.append(("Removed", "red", person, role))
     344                        else:
     345                            kept_not += 1
     346                            pass
     347                for i in range(max_new):
     348                    if "extra.%d" % (i, ) in new_holders:
     349                        if i in new_people:
     350                            person = new_people[i]
     351                            if role.require_student and not moira_accounts[person].is_student():
     352                                msgs.append('Only students can have the %s role, and %s does not appear to be a student.' % (role, person, ))
     353                            else:
     354                                holder = groups.models.OfficeHolder(person=person, role=role, group=group,)
     355                                holder.save()
     356                                changes.append(("Added", "green", person, role))
     357
     358        # mark as changed and reload the data
     359        if changes:
     360            group.set_updater(request.user)
     361            group.save()
     362        people, roles, officers_map = load_officers(group)
     363
     364    officers_data = []
     365    for person in people:
     366        role_list = []
     367        for role in roles:
     368            if (person, role) in officers_map:
     369                role_list.append((role, True))
     370            else:
     371                role_list.append((role, False))
     372        officers_data.append((False, person, role_list))
     373    null_role_list = [(role, False) for role in roles]
     374    for i in range(max_new):
     375        officers_data.append((True, "extra.%d" % (i, ), null_role_list))
     376
     377    context = {
     378        'group': group,
     379        'roles': roles,
     380        'people': people,
     381        'officers': officers_data,
     382        'edited': edited,
     383        'changes':   changes,
     384        'kept': kept,
     385        'kept_not': kept_not,
     386        'msgs': msgs,
     387    }
     388    return render_to_response('groups/group_change_officers.html', context, context_instance=RequestContext(request), )
     389
     390@permission_required('groups.view_signatories')
     391def view_signatories(request, ):
     392    # TODO:
     393    # * limit which columns (roles) get displayed
     394    # This might want to wait for the generic reporting infrastructure, since
     395    # I'd imagine some of it can be reused.
     396
     397    the_groups = groups.models.Group.objects.all()
     398    groups_filterset = GroupFilter(request.GET, the_groups)
     399    the_groups = groups_filterset.qs
     400    officers = groups.models.OfficeHolder.objects.filter(start_time__lte=datetime.datetime.now(), end_time__gte=datetime.datetime.now())
     401    officers = officers.filter(group__in=the_groups)
     402    officers = officers.select_related(depth=1)
     403    roles = groups.models.OfficerRole.objects.all()
     404    officers_map = collections.defaultdict(lambda: collections.defaultdict(set))
     405    for officer in officers:
     406        officers_map[officer.group][officer.role].add(officer.person)
     407    officers_data = []
     408    for group in the_groups:
     409        role_list = []
     410        for role in roles:
     411            role_list.append(officers_map[group][role])
     412        officers_data.append((group, role_list))
     413
     414    context = {
     415        'roles': roles,
     416        'officers': officers_data,
     417        'filter': groups_filterset,
     418        'pagename': 'groups',
     419    }
     420    return render_to_response('groups/groups_signatories.html', context, context_instance=RequestContext(request), )
     421
     422def search_groups(request, ):
     423    the_groups = groups.models.Group.objects.all()
     424    groups_filterset = GroupFilter(request.GET, the_groups)
     425
     426    dest = None
     427    if 'signatories' in request.GET:
     428        dest = reverse('groups:signatories')
     429        print dest
     430    elif 'group-info' in request.GET:
     431        dest = reverse('groups:list')
     432
     433    if dest:
     434        return redirect(dest + "?" + request.META['QUERY_STRING'])
     435    else:
     436        context = {
     437            'filter': groups_filterset,
     438            'pagename': 'groups',
     439        }
     440        return render_to_response('groups/group_search.html', context, context_instance=RequestContext(request), )
  • asadb/media/style/style.css

    r3400018 rf0a4fc4  
    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;
     24}
     25table.pretty-table th a, table.pretty-table caption a
     26{
     27    color: #008;
    1828}
    1929ul.errorlist
     
    202212
    203213
     214/*****************
     215 * GROUP DISPLAY *
     216 *****************/
     217tr.private-info th:before
     218{
     219    content: "* ";
     220}
     221
     222
    204223/* Reset some stuff */
    205224h1, h2, h3, th
     
    212231    margin-left: 0;
    213232}
     233
     234table.group-change-officers-change td
     235{
     236    text-align: center;
     237}
  • asadb/settings.py

    rc9d8369 r1de230f  
    55SITE_ROOT = os.path.normpath(os.path.dirname(__file__))
    66SITE_WEB_PATH = ''
    7 
    87
    98DEBUG = False
     
    2827# If running in a Windows environment this must be set to the same as your
    2928# system time zone.
    30 TIME_ZONE = 'America/Chicago'
     29TIME_ZONE = 'America/New_York'
    3130
    3231# Language code for this installation. All choices can be found here:
     
    3938# to load the internationalization machinery.
    4039USE_I18N = True
     40
     41DATETIME_FORMAT_PYTHON = "%c"
    4142
    4243from local_settings import *
     
    7172    'django.contrib.sessions.middleware.SessionMiddleware',
    7273    'django.contrib.auth.middleware.AuthenticationMiddleware',
     74    'django.middleware.transaction.TransactionMiddleware',
    7375    'django.middleware.csrf.CsrfViewMiddleware',
     76    'reversion.middleware.RevisionMiddleware',
    7477]
    7578
    7679AUTHENTICATION_BACKENDS = [
     80    'groups.models.PerGroupAuthz',
    7781    'django.contrib.auth.backends.ModelBackend',
    7882]
     
    99103    'django.contrib.sessions',
    100104    'django.contrib.sites',
     105    'form_utils',
     106    'django_filters',
     107    'reversion',
    101108    'south',
    102109    'groups',
  • asadb/template/base.html

    r37b65d6 rfbb362a  
    1212    <ul class='tab-navigation'>
    1313        <li{% ifequal pagename "homepage" %} class='selected'{% endifequal %}><a href="{% url homepage   %}">Home</a></li>
    14         <li{% ifequal pagename "groups"   %} class='selected'{% endifequal %}><a href="{% url group-list %}">Groups</a></li>
     14        <li{% ifequal pagename "groups"   %} class='selected'{% endifequal %}><a href="{% url groups:list %}">Groups</a></li>
    1515        <li{% ifequal pagename "fysm"     %} class='selected'{% endifequal %}><a href="{% url fysm       %}">FYSM</a></li>
     16        {% if user.is_staff %}<li><a href='{% url admin:index %}'>Admin</a></li>{% endif %}
    1617    </ul>
    1718    <div id='content'>
  • asadb/template/groups/group_list.html

    r140fc9e rfbb362a  
    66<h1>MIT Student Groups</h1>
    77
     8<h2>Search</h2>
     9
     10<form action="" method="get">
     11    <table class='pretty-table'>
     12    {{ filter.form.as_table }}
     13    </table>
     14    <input type="submit" value="Search" />
     15</form>
     16
     17<h2>The Groups</h2>
     18
    819<table class='pretty-table'>
    920<thead>
    1021    <tr>
    1122        <th>Name</th>
     23        <th>Website</th>
     24        <th>ASA DB</th>
     25        <th>Description</th>
    1226        <th>Meeting Time</th>
    1327    </tr>
     
    1630{% for group in group_list %}
    1731    <tr>
    18         <td>{% if group.website_url %}<a href='{{group.website_url}}'>{% endif %}{{group.name}}{%if group.website_url%}</a>{%endif%}</td>
     32        <th>{{group.name}}</th>
     33        <td>{% if group.website_url %}<a href='{{group.website_url}}'>Website</a>{%endif%}</td>
     34        <td><a href='{% url groups:group-detail group.pk %}'>DB Entry</a></td>
     35        <td>{{group.description}}</td>
    1936        <td>{{group.meeting_times}}</td>
    2037    </tr>
  • asadb/template/index.html

    r653a0e8 rfbb362a  
    77
    88<ul>
    9     <li><a href='{%url group-list%}'>List of groups</a></li>
     9    <li><a href='{%url groups:search%}'>Search groups</a></li>
     10    <li><a href='{%url groups:list%}'>List of groups</a></li>
    1011    <li>First Year Summer Mailing<ul>
    1112        <li><a href='{%url fysm%}'>View the entries</a></li>
    1213        <li><a href='{%url fysm-select%}'>Submit an entry</a></li>
    1314    </ul></li>
     15    {%if perms.groups.view_signatories %}<li><a href='{% url groups:signatories %}'>View Signatories</a></li>{%endif%}
    1416</ul>
    1517
     18<h2>My Groups</h2>
     19
     20{% if user.is_anonymous %}
     21<p><a href='{% url login %}'>Login</a> to see your groups.</p>
     22{% else %}
     23{%if not groups %}
     24<p>You do not currently appear to be listed with any groups.</p>
     25{% endif %}
     26{% endif %}
     27
     28{% if groups %}
     29<ul>
     30{% for group in groups %}
     31    <li><a href='{% url groups:group-detail group.pk %}'>{{group}}</a></li>
     32{%endfor%}
     33</ul>
     34{% endif %}
     35
     36{% if has_perms %}
     37<h2>Permissions</h2>
     38
     39<p>You have the following permissions:</p>
     40
     41<table class='pretty-table'>
     42<tr>
     43    <th>Name</th>
     44    <th>Description</th>
     45</tr>
     46{% for perm_name, perm_desc in has_perms %}
     47<tr>
     48    <td>{{perm_name}}</td>
     49    <td>{{perm_desc}}</td>
     50</tr>
     51{% endfor %}
     52</table>
     53{% endif %}
     54
    1655{% endblock %}
  • asadb/template/registration/login.html

    rad48c42 r9879d18  
    1515<p>In the unlikely event that you have a username and password, you can log in below.</p>
    1616
    17 <form method="post" action="{% url login %}">
     17<form method="post" action="{% url login-password %}">
    1818{% csrf_token %}
    1919<table>
  • asadb/urls.py

    r5b834ab rbd72d5c  
    99import settings
    1010
     11import groups.urls
    1112import forms.views
    12 
    13 import groups.models
    1413
    1514urlpatterns = patterns('',
    1615    # Example:
    1716    # (r'^asadb/', include('asadb.foo.urls')),
    18     url(
    19         r'^$',
    20         'django.views.generic.simple.direct_to_template',
    21         {'template': 'index.html', 'extra_context': { 'pagename':'homepage' }, },
    22         name='homepage',
    23     ),
     17    url(r'^$', 'groups.views.view_homepage', name='homepage', ),
    2418
    2519    # FYSM
     
    4539
    4640    # Group list
    47     url(
    48         r'^groups/$',
    49         list_detail.object_list,
    50         {
    51             'queryset': groups.models.Group.objects.all(),
    52             'template_object_name': 'group',
    53             'extra_context': {'pagename': 'groups', },
    54         },
    55         name='group-list',
    56     ),
     41    (r'^groups/', include(groups.urls.urls(), ), ),
    5742
    5843    # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
     
    6247    # Uncomment the next line to enable the admin:
    6348    (r'^admin/', include(admin.site.urls)),
     49    url(r'^accounts/login/password/', 'django.contrib.auth.views.login', name='login-password', ),
    6450    url(r'^accounts/login/',  'mit.scripts_login',  name='login', ),
    6551    url(r'^accounts/logout/', logout, name='logout', ),
  • asadb/util/import_db.py

    r504778e r53ca693  
    2121def canonicalize_email(email):
    2222    if '@' in email: return email
     23    elif email == '': return ''
    2324    else: return email + "@mit.edu"
    2425
     
    5051            print ">> Unknown category '%s' on group '%s'" % (cat_name, g.name, )
    5152            pass
     53        class_name = d['GROUP_CLASS']
     54        status_name = d['GROUP_STATUS']
     55        funding_name = d['GROUP_FUNDING_TYPE']
     56        if class_name == 'Standard':
     57            if status_name == 'Provisional':
     58                class_name = 'Unfunded'
     59                status_name = 'Active'
     60            elif status_name == 'Active':
     61                class_name = 'MIT-funded'
     62            elif status_name == 'Suspended' or status_name == 'Derecognized':
     63                class_name = 'MIT-funded'
     64        g.group_class = groups.models.GroupClass.objects.get(name=class_name)
     65        g.group_status = groups.models.GroupStatus.objects.get(name=status_name)
     66        if funding_name == 'none':
     67            g.group_funding = None
     68        else:
     69            g.group_funding = groups.models.GroupFunding.objects.get(name=funding_name)
    5270        g.website_url       = d['WEBSITE_URL']
    5371        g.constitution_url  = d['CONSTITUTION_WEB_URL']
  • 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
  • asadb/forms/admin.py

    rbb674c2 rceaf3bd  
    2222admin.site.register(forms.models.FYSM, FYSMAdmin)
    2323admin.site.register(forms.models.FYSMCategory, FYSMCategoryAdmin)
     24
     25class Admin_GroupMembershipUpdate(admin.ModelAdmin):
     26    list_display = (
     27        'pk',
     28        'group',
     29        'update_time',
     30        'updater_name',
     31        'updater_title',
     32        'num_undergrads',
     33        'num_grads',
     34        'num_alum',
     35        'num_other_affiliate',
     36        'num_other',
     37    )
     38    list_display_links = ('pk', 'group', )
     39admin.site.register(forms.models.GroupMembershipUpdate, Admin_GroupMembershipUpdate)
     40
     41class Admin_PersonMembershipUpdate(admin.ModelAdmin):
     42    list_display = (
     43        'pk',
     44        'username',
     45        'update_time',
     46    )
     47    list_display_links = ('pk', 'username', )
     48admin.site.register(forms.models.PersonMembershipUpdate, Admin_PersonMembershipUpdate)
  • asadb/forms/models.py

    rbb674c2 rceaf3bd  
    135135            preview.update_time = cls.never_updated
    136136            preview.save()
     137
     138
     139class GroupMembershipUpdate(models.Model):
     140    update_time = models.DateTimeField(default=datetime.datetime.utcfromtimestamp(0))
     141    updater_name = models.CharField(max_length=30)
     142    updater_title = models.CharField(max_length=30, help_text="You need not hold any particular title in the group, but we like to know who is completing the form.")
     143   
     144    group = models.ForeignKey(groups.models.Group, help_text="If your group does not appear in the list above, then please email asa-exec@mit.edu.")
     145    group_email = models.EmailField(help_text="The text of the law will be automatically distributed to your members via this list, in order to comply with the law.")
     146    officer_email = models.EmailField()
     147
     148    membership_definition = models.TextField()
     149    num_undergrads = models.IntegerField()
     150    num_grads = models.IntegerField()
     151    num_alum = models.IntegerField()
     152    num_other_affiliate = models.IntegerField()
     153    num_other = models.IntegerField()
     154
     155    membership_list = models.TextField(help_text="Member emails on separate lines (Athena usernames where applicable)")
     156
     157    compliance_statement = "By checking this, I hereby affirm that I have read and understand Chapter 269: Sections 17, 18, and 19 of Massachusetts Law. I furthermore attest that I will distribute to group members, pledges, and/or applicants, copies of Massachusetts Law 269: 17, 18, 19 and that our organization, group, or team agrees to comply with the provisions of that law. (See below for text.)"
     158    no_hazing = models.BooleanField(help_text=compliance_statement)
     159
     160
     161class PersonMembershipUpdate(models.Model):
     162    update_time = models.DateTimeField(default=datetime.datetime.utcfromtimestamp(0))
     163    username = models.CharField(max_length=30)
     164    groups = models.ManyToManyField(groups.models.Group, help_text="By selecting a group here, you indicate that you are an active member of the group in question.<br>If your group does not appear in the list above, then please email asa-exec@mit.edu.<br>")
Note: See TracChangeset for help on using the changeset viewer.