Changeset ef118cf for asadb/groups
- Timestamp:
- Feb 11, 2014, 2:22:25 AM (12 years ago)
- Branches:
- master, stable, stage
- Children:
- d4f571c
- Parents:
- 2563230 (diff), e5c5180 (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@…> (02/11/14 02:22:25)
- git-committer:
- Alex Dehnert <adehnert@…> (02/11/14 02:22:25)
- Location:
- asadb/groups
- Files:
-
- 3 added
- 6 edited
-
admin.py (modified) (5 diffs)
-
diffs.py (modified) (1 diff)
-
listdiff.py (added)
-
load_people.py (modified) (1 diff)
-
migrations/0028_officerrole_admins.py (added)
-
migrations/0029_athena_affiliation.py (added)
-
models.py (modified) (12 diffs)
-
urls.py (modified) (1 diff)
-
views.py (modified) (17 diffs)
Legend:
- Unmodified
- Added
- Removed
-
asadb/groups/admin.py
rcbffe98 r89165c1 1 import datetime 2 3 from django.contrib import admin 4 from django.utils.translation import ugettext_lazy 5 6 from reversion.admin import VersionAdmin 7 1 8 import groups.models 2 from django.contrib import admin 3 from reversion.admin import VersionAdmin 9 import util.admin 4 10 5 11 class GroupAdmin(VersionAdmin): … … 98 104 99 105 class OfficeHolderAdmin(VersionAdmin): 106 class OfficeHolderPeriodFilter(util.admin.TimePeriodFilter): 107 start_field = 'start_time' 108 end_field = 'end_time' 109 110 def expire_holders(self, request, queryset): 111 rows_updated = queryset.update(end_time=datetime.datetime.now()) 112 if rows_updated == 1: 113 message_bit = "1 entry was" 114 else: 115 message_bit = "%s entries were" % rows_updated 116 self.message_user(request, "%s successfully expired." % message_bit) 117 expire_holders.short_description = ugettext_lazy("Expire selected %(verbose_name_plural)s") 118 119 actions = ['expire_holders'] 120 100 121 list_display = ( 101 122 'id', … … 121 142 list_filter = [ 122 143 'role', 144 OfficeHolderPeriodFilter, 123 145 ] 124 146 admin.site.register(groups.models.OfficeHolder, OfficeHolderAdmin) … … 181 203 'last_name', 182 204 'account_class', 205 'affiliation_basic', 206 'loose_student', 183 207 'mutable', 184 208 'add_date', … … 188 212 list_display_links = ( 'id', 'username', ) 189 213 search_fields = ( 'username', 'mit_id', 'first_name', 'last_name', 'account_class', ) 214 list_filter = ( 215 'account_class', 216 'affiliation_basic', 'affiliation_detailed', 217 'loose_student', 218 'mutable', 219 ) 190 220 admin.site.register(groups.models.AthenaMoiraAccount, Admin_AthenaMoiraAccount) -
asadb/groups/diffs.py
rafc5348 r9af1bb4 244 244 245 245 def default_active_pred(): 246 status_objs = groups.models.GroupStatus.objects.filter(slug__in=['active', 'suspended', 'nge'])246 status_objs = groups.models.GroupStatus.objects.filter(slug__in=['active', 'suspended', ]) 247 247 status_pks = [status.pk for status in status_objs] 248 248 def pred(version, fields): -
asadb/groups/load_people.py
r161ce5f r89165c1 26 26 'last_name', 27 27 'account_class', 28 'affiliation_basic', 29 'affiliation_detailed', 28 30 ] 29 31 -
asadb/groups/models.py
r62f73df ref118cf 1 1 # -*- coding: utf8 -*- 2 2 3 from django.conf import settings 4 from django.db import models 5 from django.core.exceptions import ValidationError 6 from django.core.validators import RegexValidator 7 from django.contrib.auth.models import User 8 from django.template.defaultfilters import slugify 9 import reversion 10 3 import collections 11 4 import datetime 12 5 import filecmp … … 20 13 import urllib2 21 14 15 from django.conf import settings 16 from django.db import models 17 from django.db.models import Q 18 from django.core.exceptions import ValidationError 19 from django.core.validators import RegexValidator 20 from django.contrib.auth.models import User, Permission 21 from django.contrib.contenttypes.models import ContentType 22 from django.template.defaultfilters import slugify 23 24 import reversion 25 22 26 import mit 23 27 … … 30 34 ) 31 35 32 locker_validator = RegexValidator(regex=r'^[-A-Za-z0-9_.]+$', message='Enter a valid Athena locker. ')36 locker_validator = RegexValidator(regex=r'^[-A-Za-z0-9_.]+$', message='Enter a valid Athena locker. This should be the single "word" that appears in "/mit/word/" or "web.mit.edu/word/", with no slashes, spaces, etc..') 33 37 34 38 class Group(models.Model): … … 100 104 if as_of == "now": as_of = datetime.datetime.now() 101 105 office_holders = office_holders.filter(start_time__lte=as_of, end_time__gte=as_of) 106 office_holders = office_holders.order_by('role', 'person') 102 107 return office_holders 103 108 … … 112 117 current_officers = OfficeHolder.current_holders.filter(person=username) 113 118 users_groups = Group.objects.filter(officeholder__in=current_officers).distinct() 119 120 @staticmethod 121 def admin_groups(username, codename='admin_group'): 122 holders = OfficeHolder.current_holders.filter_perm(codename=codename).filter(person=username) 123 users_groups = Group.objects.filter(officeholder__in=holders).distinct() 114 124 return users_groups 115 125 … … 395 405 396 406 @classmethod 407 def getRolesGrantingPerm(cls, perm=None, model=Group, codename=None, ): 408 """Get all OfficerRole objects granting a permission 409 410 Either `perm` or `codename` must be supplied, but not both. If 411 `codename` is provided (and `perm` is None), then `perm` the 412 permission corresponding to `model` (default: `Group`) and `codename` 413 will be found and used.""" 414 415 if perm is None: 416 ct = ContentType.objects.get_for_model(model) 417 print ct 418 print Permission.objects.filter(content_type=ct) 419 perm = Permission.objects.get(content_type=ct, codename=codename) 420 421 Q_user = Q(user_permissions=perm) 422 Q_group = Q(groups__permissions=perm) 423 users = User.objects.filter(Q_user|Q_group) 424 roles = cls.objects.filter(grant_user__in=users) 425 return roles 426 427 @classmethod 397 428 def retrieve(cls, slug, ): 398 429 return cls.objects.get(slug=slug) 430 399 431 reversion.register(OfficerRole) 400 432 … … 407 439 ) 408 440 441 def filter_perm(self, perm=None, model=Group, codename=None, ): 442 roles = OfficerRole.getRolesGrantingPerm(perm=perm, model=model, codename=codename) 443 return self.get_query_set().filter(role__in=roles) 444 409 445 class OfficeHolder(models.Model): 410 446 EXPIRE_OFFSET = datetime.timedelta(seconds=1) 411 447 END_NEVER = datetime.datetime.max 412 448 413 person = models.CharField(max_length=30, db_index=True, )449 person = models.CharField(max_length=30, db_index=True, help_text='Athena username') 414 450 role = models.ForeignKey('OfficerRole', db_index=True, ) 415 451 group = models.ForeignKey('Group', db_index=True, ) … … 433 469 def __repr__(self, ): 434 470 return str(self) 471 435 472 reversion.register(OfficeHolder) 436 473 … … 506 543 507 544 def __str__(self, ): 508 active = "" 509 if not self.is_active: 510 active = " (inactive)" 511 return "%s%s" % (self.name, active, ) 545 return self.name 512 546 513 547 class Meta: … … 528 562 def get_query_set(self, ): 529 563 return super(AthenaMoiraAccount_ActiveManager, self).get_query_set().filter(del_date=None) 564 565 def student_account_classes(): 566 year = datetime.datetime.now().year 567 return ["G"] + [str(yr) for yr in range(year-5, year+10)] 530 568 531 569 class AthenaMoiraAccount(models.Model): … … 535 573 last_name = models.CharField(max_length=45) 536 574 account_class = models.CharField(max_length=10) 575 affiliation_basic = models.CharField(max_length=10) 576 affiliation_detailed = models.CharField(max_length=40) 577 loose_student = models.BooleanField(default=False, help_text='Whether to use loose or strict determination of student status. Loose means that either the account class or the affiliation should indicate student status; strict means that the affiliation must be student. In general, we use strict; for some people ("secret people") directory information is suppressed and the affiliation will be misleading.') 537 578 mutable = models.BooleanField(default=True) 538 579 add_date = models.DateField(help_text="Date when this person was added to the dump.", ) … … 544 585 545 586 def is_student(self, ): 546 # XXX: Is this... right? 547 return self.account_class == 'G' or self.account_class.isdigit() 587 student_affiliation = (self.affiliation_basic == 'student') 588 student_class = (self.account_class in student_account_classes()) 589 return student_affiliation or (student_class and self.loose_student) 590 591 @staticmethod 592 def student_q(): 593 q_affiliation = Q(affiliation_basic='student') 594 q_class = Q(account_class__in=student_account_classes()) 595 return q_affiliation | (q_class & Q(loose_student=True)) 548 596 549 597 def format(self, ): -
asadb/groups/urls.py
r532a8e9 ra7c08e4 1 1 from django.conf.urls.defaults import * 2 3 import django.shortcuts 2 4 3 5 import groups.views 4 6 import space.views 5 7 8 def redirect_view(to): 9 def _redirect_view(request, **kwargs): 10 return django.shortcuts.redirect(to, **kwargs) 11 return _redirect_view 12 6 13 group_patterns = patterns('', 7 14 url(r'^$', groups.views.GroupDetailView.as_view(), name='group-detail', ), 8 15 url(r'^edit/main$', groups.views.manage_main, name='group-manage-main', ), 9 url(r'^edit/officers$', groups.views.manage_officers, name='group-manage-officers', ), 16 url(r'^edit/people$', groups.views.manage_officers, name='group-manage-officers', ), 17 url(r'^edit/officers$', redirect_view('groups:group-manage-officers'), ), 10 18 url(r'^history/$', groups.views.GroupHistoryView.as_view(), name='group-manage-history', ), 11 19 url(r'^space/$', space.views.manage_access, name='group-space-access', ), -
asadb/groups/views.py
rd7557b8 ref118cf 4 4 import csv 5 5 import datetime 6 7 import groups.models8 6 9 7 from django.contrib.auth.decorators import user_passes_test, login_required, permission_required … … 30 28 import django_filters 31 29 30 import groups.models 32 31 from util.db_form_utils import StaticWidget 32 import util.db_filters 33 33 from util.emails import email_from_template 34 34 … … 129 129 for field in self.force_required: 130 130 self.fields[field].required = True 131 self.fields['constitution_url'].help_text = mark_safe(" ""Please put your current constitution URL or AFS path.<br>If you don't currently know where your constitution is, put "http://mit.edu/asa/start/constitution-req.html" and draft a constitution soon.""")131 self.fields['constitution_url'].help_text = mark_safe("Please put your current constitution URL or AFS path.") 132 132 133 133 exec_only_fields = [ … … 221 221 def manage_officers_load_officers(group, ): 222 222 officers = group.officers() 223 people = list(set([ officer.person for officer in officers ]))223 people = sorted(set([ officer.person for officer in officers ])) 224 224 roles = groups.models.OfficerRole.objects.all() 225 225 … … 573 573 treasurer_name = forms.CharField(max_length=50) 574 574 treasurer_kerberos = forms.CharField(min_length=3, max_length=8, ) 575 def clean_president (self, ):575 def clean_president_kerberos(self, ): 576 576 username = self.cleaned_data['president_kerberos'] 577 577 validate_athena(username, True, ) 578 578 return username 579 579 580 def clean_treasurer (self, ):580 def clean_treasurer_kerberos(self, ): 581 581 username = self.cleaned_data['treasurer_kerberos'] 582 582 validate_athena(username, True, ) … … 630 630 self.fields['constitution_url'].required = True 631 631 self.fields['constitution_url'].help_text = "Please put a copy of your finalized constitution on a publicly-accessible website (e.g. your group's, or your own, Public folder), and link to it in the box above." 632 self.fields['group_email'].required = True 632 633 self.fields['athena_locker'].required = True 634 self.fields['athena_locker'].help_text = "In general, this is limited to twelve characters. You should stick to letters, numbers, and hyphens. (Underscores and dots are also acceptable, but may cause problems in some situations.)" 635 636 # Specifically, if the group ends up wanting to use scripts.mit.edu, 637 # they will currently be assigned locker.scripts.mit.edu. If they try 638 # to use foo.bar, then https://foo.bar.scripts.mit.edu/ will produce a 639 # certificate name mismatch. Officially, underscores are not allowed in 640 # hostnames, so foo_.scripts.mit.edu may fail with some software. 633 641 634 642 class Meta(GroupCreateForm.Meta): … … 688 696 from_email='asa-admin@mit.edu', 689 697 ) 690 # XXX: Handle this better691 if officer_domain != 'mit.edu' or (create_group_list and group_domain != 'mit.edu'):692 accounts_mail.to = ['asa-groups@mit.edu']693 accounts_mail.cc = ['asa-db@mit.edu']694 accounts_mail.subject = "ERROR: " + accounts_mail.subject695 accounts_mail.body = "Bad domain on officer or group list\n\n" + accounts_mail.body696 698 697 699 else: … … 820 822 return render_to_response('groups/create/startup.html', context, context_instance=RequestContext(request), ) 821 823 824 def review_group_check_warnings(group_startup, group, ): 825 warnings = [] 826 827 if group.name.startswith("MIT "): 828 warnings.append('Group name starts with "MIT". Generally, we prefer "Foo, MIT" instead.') 829 if "mit" in group.athena_locker.lower(): 830 warnings.append('Athena locker name contains "mit", which may be redundant with paths like "http://web.mit.edu/mitfoo" or "/mit/foo/".') 831 832 if group_startup.president_kerberos == group_startup.treasurer_kerberos: 833 warnings.append('President matches Treasurer.') 834 if "%s@mit.edu" % (group_startup.president_kerberos, ) in (group.officer_email, group.group_email): 835 warnings.append('President email matches officer and/or group email.') 836 if group.officer_email == group.group_email: 837 warnings.append('Officer email matches group email.') 838 839 if '@mit.edu' not in group.officer_email or '@mit.edu' not in group.group_email: 840 warnings.append('Officer and/or group email are non-MIT. Ensure that they are not requesting the addresses be created, and consider suggesting they use an MIT list instead.') 841 842 if '.' in group.athena_locker: 843 warnings.append('Athena locker contains a ".". This is not compatible with scripts.mit.edu\'s wildcard certificate, and may cause other problems.') 844 if '_' in group.athena_locker: 845 warnings.append('Athena locker contains a "_". If this locker name gets used in a URL (for example, locker.scripts.mit.edu), it will technically violate the hostname specification and may not work in some clients.') 846 if len(group.athena_locker) > 12: 847 warnings.append('Athena locker is more than twelve characters long. In general, twelve characters is the longest Athena locker an ASA-recognized group can get.') 848 849 return warnings 850 822 851 @permission_required('groups.recognize_group') 823 852 def recognize_normal_group(request, pk, ): … … 836 865 return render_to_response('groups/create/err.not-applying.html', context, context_instance=RequestContext(request), ) 837 866 867 context['warnings'] = review_group_check_warnings(group_startup, group) 868 838 869 context['msg'] = "" 839 870 if request.method == 'POST': … … 842 873 group_startup.save() 843 874 844 group.group_status = groups.models.GroupStatus.objects.get(slug=' active')875 group.group_status = groups.models.GroupStatus.objects.get(slug='suspended') 845 876 group.constitution_url = "" 846 877 group.recognition_date = datetime.datetime.now() 847 878 group.set_updater(request.user) 879 880 note = groups.models.GroupNote( 881 author=request.user.username, 882 body="Approved group for recognition.", 883 acl_read_group=True, 884 acl_read_offices=True, 885 group=group, 886 ).save() 848 887 849 888 group.save() … … 900 939 name = django_filters.CharFilter(lookup_type='icontains', label="Name contains") 901 940 abbreviation = django_filters.CharFilter(lookup_type='iexact', label="Abbreviation is") 941 officer_email = django_filters.CharFilter(lookup_type='icontains', label="Officers' list contains") 942 943 account_filter = util.db_filters.MultiNumberFilter( 944 lookup_type='exact', label="Account number", 945 names=('main_account_id', 'funding_account_id', ), 946 ) 902 947 903 948 class Meta: … … 906 951 'name', 907 952 'abbreviation', 953 'officer_email', 908 954 'activity_category', 909 955 'group_class', 910 956 'group_status', 911 957 'group_funding', 958 'account_filter', 912 959 ] 913 960 … … 945 992 groups_filterset = GroupFilter(request.GET, the_groups) 946 993 the_groups = groups_filterset.qs 994 947 995 officers = groups.models.OfficeHolder.objects.filter(start_time__lte=datetime.datetime.now(), end_time__gte=datetime.datetime.now()) 948 996 officers = officers.filter(group__in=the_groups) 949 997 officers = officers.select_related(depth=1) 998 950 999 role_slugs = ['president', 'treasurer', 'financial', 'reservation'] 951 1000 roles = groups.models.OfficerRole.objects.filter(slug__in=role_slugs) 952 1001 roles = sorted(roles, key=lambda r: role_slugs.index(r.slug)) 1002 953 1003 officers_map = collections.defaultdict(lambda: collections.defaultdict(set)) 954 1004 for officer in officers: … … 958 1008 role_list = [] 959 1009 for role in roles: 960 role_list.append( officers_map[group][role])1010 role_list.append(sorted(officers_map[group][role])) 961 1011 officers_data.append((group, role_list)) 962 1012 … … 1004 1054 if 'pk' in self.kwargs: 1005 1055 group = get_object_or_404(groups.models.Group, pk=self.kwargs['pk']) 1006 history_entries = reversion. models.Version.objects.get_for_object(group)1056 history_entries = reversion.get_for_object(group) 1007 1057 else: 1008 1058 history_entries = reversion.models.Version.objects.all() … … 1288 1338 def show_nonstudent_officers(request, ): 1289 1339 student_roles = groups.models.OfficerRole.objects.filter(require_student=True, ) 1290 year = datetime.datetime.now().year 1291 account_classes = ["G"] + [str(yr) for yr in range(year-5, year+10)] 1292 students = groups.models.AthenaMoiraAccount.active_accounts.filter(account_class__in=account_classes) 1340 student_q = groups.models.AthenaMoiraAccount.student_q() 1341 students = groups.models.AthenaMoiraAccount.active_accounts.filter(student_q) 1293 1342 office_holders = groups.models.OfficeHolder.current_holders.order_by('group__name', 'role', ) 1294 1343 office_holders = office_holders.filter(role__in=student_roles) 1295 1344 office_holders = office_holders.exclude(person__in=students.values('username')) 1296 office_holders = office_holders.select_related('group', ' role')1345 office_holders = office_holders.select_related('group', 'group__group_status', 'role') 1297 1346 1298 1347 msg = None … … 1300 1349 if 'sort' in request.GET: 1301 1350 if request.GET['sort'] == 'group': 1302 office_holders = office_holders.order_by('group__name', 'role', 'person', ) 1351 office_holders = office_holders.order_by('group__name', 'group__group_status', 'role', 'person', ) 1352 elif request.GET['sort'] == 'status': 1353 office_holders = office_holders.order_by('group__group_status', 'group__name', 'role', 'person', ) 1303 1354 elif request.GET['sort'] == 'role': 1304 office_holders = office_holders.order_by('role', 'group__ name', 'person', )1355 office_holders = office_holders.order_by('role', 'group__group_status', 'group__name', 'person', ) 1305 1356 elif request.GET['sort'] == 'person': 1306 office_holders = office_holders.order_by('person', 'group__ name', 'role', )1357 office_holders = office_holders.order_by('person', 'group__group_status', 'group__name', 'role', ) 1307 1358 else: 1308 1359 msg = 'Unknown sort key "%s".' % (request.GET['sort'], )
Note: See TracChangeset
for help on using the changeset viewer.