Changeset bd72d5c
- Timestamp:
- Sep 15, 2011, 3:34:54 PM (15 years ago)
- 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)
- Files:
-
- 33 added
- 16 edited
-
.gitignore (modified) (1 diff)
-
asadb/forms/views.py (modified) (1 diff)
-
asadb/groups/admin.py (modified) (3 diffs)
-
asadb/groups/import_signatories.py (added)
-
asadb/groups/load_people.py (added)
-
asadb/groups/migrations/0002_add_officers.py (added)
-
asadb/groups/migrations/0003_add_people.py (added)
-
asadb/groups/migrations/0004_add_require_student.py (added)
-
asadb/groups/migrations/0005_add_role_user_grant.py (added)
-
asadb/groups/migrations/0006_add_group_perms.py (added)
-
asadb/groups/migrations/0007_add_statusy_stuff.py (added)
-
asadb/groups/migrations/0008_add_statusy_stuff_data.py (added)
-
asadb/groups/migrations/0009_add_notes.py (added)
-
asadb/groups/migrations/0010_allow_null_updater.py (added)
-
asadb/groups/migrations/0011_add_publicly_viewable.py (added)
-
asadb/groups/models.py (modified) (5 diffs)
-
asadb/groups/urls.py (added)
-
asadb/groups/views.py (modified) (1 diff)
-
asadb/media/style/style.css (modified) (3 diffs)
-
asadb/settings.py (modified) (5 diffs)
-
asadb/template/base.html (modified) (1 diff)
-
asadb/template/form_utils/better_form.html (added)
-
asadb/template/form_utils/fields_as_trs.html (added)
-
asadb/template/groups/group_change_main.html (added)
-
asadb/template/groups/group_change_officers.html (added)
-
asadb/template/groups/group_detail.html (added)
-
asadb/template/groups/group_list.html (modified) (2 diffs)
-
asadb/template/groups/group_search.html (added)
-
asadb/template/groups/group_version.html (added)
-
asadb/template/groups/groups_signatories.html (added)
-
asadb/template/groups/note/detail.head.html (added)
-
asadb/template/groups/note/detail.row.html (added)
-
asadb/template/index.html (modified) (1 diff)
-
asadb/template/registration/login.html (modified) (1 diff)
-
asadb/urls.py (modified) (3 diffs)
-
asadb/util/db_form_utils.py (added)
-
asadb/util/import_db.py (modified) (2 diffs)
-
design/tools/audit-trail.txt (modified) (1 diff)
-
asadb/forms/admin.py (modified) (1 diff)
-
asadb/forms/migrations/0005_group_membership_update.py (added)
-
asadb/forms/migrations/0006_person_membership_update.py (added)
-
asadb/forms/migrations/0007_fix_fields_community.py (added)
-
asadb/forms/models.py (modified) (1 diff)
-
asadb/settings/local_settings.dev-template.py (added)
-
asadb/template/membership/anti-hazing.txt (added)
-
asadb/template/membership/confirm.html (added)
-
asadb/template/membership/submit-confirm-email.txt (added)
-
asadb/template/membership/thanks.html (added)
-
asadb/template/membership/update.html (added)
Legend:
- Unmodified
- Added
- Removed
-
.gitignore
ra76118f r3ffb210 6 6 asadb/media/fysm/logos/ 7 7 asadb/media/fysm/slides/ 8 asadb/media/page-previews/fysm/ 8 9 asadb/util/saved-data/ 9 10 asadb/util/warehouse-dump.csv -
asadb/forms/views.py
rceaf3bd rbd72d5c 121 121 def select_group_fysm(request, ): 122 122 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) 124 124 return select_group( 125 125 request, -
asadb/groups/admin.py
rbb674c2 r3107c52 1 1 import groups.models 2 2 from django.contrib import admin 3 from reversion.admin import VersionAdmin 3 4 4 class GroupAdmin( admin.ModelAdmin):5 class GroupAdmin(VersionAdmin): 5 6 list_display = ( 6 7 'id', … … 16 17 ) 17 18 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 ] 19 25 date_hierarchy = 'update_date' 20 26 search_fields = [ 'id', 'name', 'abbreviation', 'officer_email', 'athena_locker', ] 27 admin.site.register(groups.models.Group, GroupAdmin) 28 29 30 class 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 ] 52 admin.site.register(groups.models.GroupNote, Admin_GroupNote) 53 54 55 class 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",)} 67 admin.site.register(groups.models.OfficerRole, OfficerRoleAdmin) 68 69 70 class 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 ) 92 admin.site.register(groups.models.OfficeHolder, OfficeHolderAdmin) 93 21 94 22 95 class ActivityCategoryAdmin(admin.ModelAdmin): … … 26 99 ) 27 100 list_display_links = ('id', 'name', ) 101 admin.site.register(groups.models.ActivityCategory, ActivityCategoryAdmin) 28 102 29 admin.site.register(groups.models.Group, GroupAdmin) 30 admin.site.register(groups.models.ActivityCategory, ActivityCategoryAdmin) 103 104 class 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', )} 114 admin.site.register(groups.models.GroupClass, Admin_GroupClass) 115 116 117 class 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', )} 127 admin.site.register(groups.models.GroupStatus, Admin_GroupStatus) 128 129 130 class 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', )} 140 admin.site.register(groups.models.GroupFunding, Admin_GroupFunding) 141 142 143 class 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', ) 158 admin.site.register(groups.models.AthenaMoiraAccount, Admin_AthenaMoiraAccount) -
asadb/groups/models.py
r2a6907a r3275ab1 1 1 from django.db import models 2 from django.contrib.auth.models import User 3 4 import datetime 5 6 import settings 2 7 3 8 # Create your models here. 9 10 class 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 4 16 class Group(models.Model): 5 17 name = models.CharField(max_length=100) … … 7 19 description = models.TextField() 8 20 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, ) 9 24 website_url = models.URLField() 10 25 constitution_url = models.CharField(max_length=200, blank=True) … … 21 36 athena_locker = models.CharField(max_length=20, blank=True) 22 37 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 25 85 26 86 def __str__(self, ): … … 29 89 class Meta: 30 90 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 101 class 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 132 class 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 164 class 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 171 class 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 196 class 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 31 231 32 232 class ActivityCategory(models.Model): … … 38 238 class Meta: 39 239 verbose_name_plural = "activity categories" 240 241 242 class 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 255 class 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 271 class 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 281 class AthenaMoiraAccount_ActiveManager(models.Manager): 282 def get_query_set(self, ): 283 return super(AthenaMoiraAccount_ActiveManager, self).get_query_set().filter(del_date=None) 284 285 class 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 1 1 # Create your views here. 2 3 import collections 4 import datetime 5 6 import groups.models 7 8 from django.contrib.auth.decorators import user_passes_test, login_required, permission_required 9 from django.contrib.contenttypes.models import ContentType 10 from django.core.exceptions import PermissionDenied 11 from django.views.generic import ListView, DetailView 12 from django.shortcuts import render_to_response, get_object_or_404, redirect 13 from django.template import RequestContext 14 from django.template import Context, Template 15 from django.template.loader import get_template 16 from django.http import Http404, HttpResponseRedirect 17 from django.core.urlresolvers import reverse 18 from django.core.mail import EmailMessage, mail_admins 19 from django.forms import Form 20 from django.forms import ModelForm 21 from django.forms import ModelChoiceField 22 from django.db import connection 23 from django.db.models import Q 24 25 import form_utils.forms 26 import reversion.models 27 import django_filters 28 29 from util.db_form_utils import StaticWidget 30 31 32 def 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 60 class 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 113 def 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 176 class 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 192 class 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 213 class 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 241 class 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 269 def 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 280 def 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') 391 def 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 422 def 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 7 7 { 8 8 border-collapse: collapse; 9 } 10 table.pretty-table th, table.pretty-table td 9 margin: 0.5em; 10 } 11 table.pretty-table th, table.pretty-table td, table.pretty-table caption 11 12 { 12 13 border: 1px solid black; 13 14 padding: 2px; 14 15 } 15 table.pretty-table th 16 { 16 table.pretty-table caption 17 { 18 border-bottom: none; 19 } 20 table.pretty-table th, table.pretty-table caption 21 { 22 font-weight: bold; 17 23 background: #BE2933; 24 } 25 table.pretty-table th a, table.pretty-table caption a 26 { 27 color: #008; 18 28 } 19 29 ul.errorlist … … 202 212 203 213 214 /***************** 215 * GROUP DISPLAY * 216 *****************/ 217 tr.private-info th:before 218 { 219 content: "* "; 220 } 221 222 204 223 /* Reset some stuff */ 205 224 h1, h2, h3, th … … 212 231 margin-left: 0; 213 232 } 233 234 table.group-change-officers-change td 235 { 236 text-align: center; 237 } -
asadb/settings.py
rc9d8369 r1de230f 5 5 SITE_ROOT = os.path.normpath(os.path.dirname(__file__)) 6 6 SITE_WEB_PATH = '' 7 8 7 9 8 DEBUG = False … … 28 27 # If running in a Windows environment this must be set to the same as your 29 28 # system time zone. 30 TIME_ZONE = 'America/ Chicago'29 TIME_ZONE = 'America/New_York' 31 30 32 31 # Language code for this installation. All choices can be found here: … … 39 38 # to load the internationalization machinery. 40 39 USE_I18N = True 40 41 DATETIME_FORMAT_PYTHON = "%c" 41 42 42 43 from local_settings import * … … 71 72 'django.contrib.sessions.middleware.SessionMiddleware', 72 73 'django.contrib.auth.middleware.AuthenticationMiddleware', 74 'django.middleware.transaction.TransactionMiddleware', 73 75 'django.middleware.csrf.CsrfViewMiddleware', 76 'reversion.middleware.RevisionMiddleware', 74 77 ] 75 78 76 79 AUTHENTICATION_BACKENDS = [ 80 'groups.models.PerGroupAuthz', 77 81 'django.contrib.auth.backends.ModelBackend', 78 82 ] … … 99 103 'django.contrib.sessions', 100 104 'django.contrib.sites', 105 'form_utils', 106 'django_filters', 107 'reversion', 101 108 'south', 102 109 'groups', -
asadb/template/base.html
r37b65d6 rfbb362a 12 12 <ul class='tab-navigation'> 13 13 <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> 15 15 <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 %} 16 17 </ul> 17 18 <div id='content'> -
asadb/template/groups/group_list.html
r140fc9e rfbb362a 6 6 <h1>MIT Student Groups</h1> 7 7 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 8 19 <table class='pretty-table'> 9 20 <thead> 10 21 <tr> 11 22 <th>Name</th> 23 <th>Website</th> 24 <th>ASA DB</th> 25 <th>Description</th> 12 26 <th>Meeting Time</th> 13 27 </tr> … … 16 30 {% for group in group_list %} 17 31 <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> 19 36 <td>{{group.meeting_times}}</td> 20 37 </tr> -
asadb/template/index.html
r653a0e8 rfbb362a 7 7 8 8 <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> 10 11 <li>First Year Summer Mailing<ul> 11 12 <li><a href='{%url fysm%}'>View the entries</a></li> 12 13 <li><a href='{%url fysm-select%}'>Submit an entry</a></li> 13 14 </ul></li> 15 {%if perms.groups.view_signatories %}<li><a href='{% url groups:signatories %}'>View Signatories</a></li>{%endif%} 14 16 </ul> 15 17 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 16 55 {% endblock %} -
asadb/template/registration/login.html
rad48c42 r9879d18 15 15 <p>In the unlikely event that you have a username and password, you can log in below.</p> 16 16 17 <form method="post" action="{% url login %}">17 <form method="post" action="{% url login-password %}"> 18 18 {% csrf_token %} 19 19 <table> -
asadb/urls.py
r5b834ab rbd72d5c 9 9 import settings 10 10 11 import groups.urls 11 12 import forms.views 12 13 import groups.models14 13 15 14 urlpatterns = patterns('', 16 15 # Example: 17 16 # (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', ), 24 18 25 19 # FYSM … … 45 39 46 40 # 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(), ), ), 57 42 58 43 # Uncomment the admin/doc line below and add 'django.contrib.admindocs' … … 62 47 # Uncomment the next line to enable the admin: 63 48 (r'^admin/', include(admin.site.urls)), 49 url(r'^accounts/login/password/', 'django.contrib.auth.views.login', name='login-password', ), 64 50 url(r'^accounts/login/', 'mit.scripts_login', name='login', ), 65 51 url(r'^accounts/logout/', logout, name='logout', ), -
asadb/util/import_db.py
r504778e r53ca693 21 21 def canonicalize_email(email): 22 22 if '@' in email: return email 23 elif email == '': return '' 23 24 else: return email + "@mit.edu" 24 25 … … 50 51 print ">> Unknown category '%s' on group '%s'" % (cat_name, g.name, ) 51 52 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) 52 70 g.website_url = d['WEBSITE_URL'] 53 71 g.constitution_url = d['CONSTITUTION_WEB_URL'] -
design/tools/audit-trail.txt
r35d8e68 r5bb1780 34 34 * Design 35 35 * Store all versions of all models in a single table, JSON'd 36 * Metadata stored unpickled 37 * Time 38 * Editor 39 * Some other stuff 36 40 * django-reversion 37 41 * https://github.com/etianen/django-reversion -
asadb/forms/admin.py
rbb674c2 rceaf3bd 22 22 admin.site.register(forms.models.FYSM, FYSMAdmin) 23 23 admin.site.register(forms.models.FYSMCategory, FYSMCategoryAdmin) 24 25 class 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', ) 39 admin.site.register(forms.models.GroupMembershipUpdate, Admin_GroupMembershipUpdate) 40 41 class Admin_PersonMembershipUpdate(admin.ModelAdmin): 42 list_display = ( 43 'pk', 44 'username', 45 'update_time', 46 ) 47 list_display_links = ('pk', 'username', ) 48 admin.site.register(forms.models.PersonMembershipUpdate, Admin_PersonMembershipUpdate) -
asadb/forms/models.py
rbb674c2 rceaf3bd 135 135 preview.update_time = cls.never_updated 136 136 preview.save() 137 138 139 class 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 161 class 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.