Changeset 3c1b20b


Ignore:
Timestamp:
Jun 4, 2013, 3:40:41 AM (13 years ago)
Author:
Alex Dehnert <adehnert@…>
Branches:
master, space-access, stable, stage
Children:
6aa3517
Parents:
1927d79 (diff), 1e08e97 (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@…> (06/04/13 03:40:41)
git-committer:
Alex Dehnert <adehnert@…> (06/04/13 03:40:41)
Message:

Merge branch 'master' into space-access

  • master: (43 commits) Color NGEs differently from inactive groups Don't add NGEs to asa-official (ASA-#231) Take is_active out of str(GroupStatus?) (ASA-#230) Add group status to account lookup (ASA-#203) Add help text about Athena lockername selection Sort the "Update people" page (partial ASA-#232) Sort the signatories page (partial ASA-#232) Sort the officers of a group (partial ASA-#232) Validate P/T usernames in group creation form Remove spurious slash in new group email (ASA-#217) Use absolute path to templates (ASA-#211) Show group status on the non-student officers page Prevent 500'ing while uploading midway assignments "Upload table assignments" link (ASA-#225) Add midway tab Add midway list page (ASA-#226) Handle duplicate groups Fix spelling of midway permission Add the midway permissions to the asa-ebm group Allow running diff_static_data from any directory ...

Conflicts:

asadb/space/admin.py

Files:
14 added
28 edited

Legend:

Unmodified
Added
Removed
  • .gitignore

    r7ef7143 r7d7801f  
    77asadb/media/fysm/slides/
    88asadb/media/page-previews/fysm/
     9asadb/media/midway/maps/
    910asadb/util/saved-data/
    1011asadb/util/static-data/
  • asadb/forms/admin.py

    rbda4d86 r26826c9  
     1from django.contrib import admin
     2
    13import forms.models
    2 from django.contrib import admin
    34
    45class FYSMAdmin(admin.ModelAdmin):
     
    6667    search_fields = ('username', 'groups__officer_email', 'groups__name', 'groups__abbreviation', )
    6768admin.site.register(forms.models.PersonMembershipUpdate, Admin_PersonMembershipUpdate)
     69
     70class Admin_Midway(admin.ModelAdmin):
     71    list_display = (
     72        'pk',
     73        'name',
     74        'slug',
     75        'date',
     76    )
     77    list_display_links = list_display
     78    search_fields = ('name', )
     79    prepopulated_fields = {"slug": ("name",)}
     80admin.site.register(forms.models.Midway, Admin_Midway)
     81
     82class Admin_MidwayAssignment(admin.ModelAdmin):
     83    list_display = (
     84        'pk',
     85        'midway',
     86        'location',
     87        'group',
     88    )
     89    list_display_links = list_display
     90    list_filter = ('midway', )
     91    search_fields = ('location', 'group__name', )
     92admin.site.register(forms.models.MidwayAssignment, Admin_MidwayAssignment)
  • asadb/forms/models.py

    rc7b6a3a r26826c9  
    1 from django.db import models
    2 
    31import datetime
    42import os, errno
    53
    6 import settings
     4from django.conf import settings
     5from django.db import models
     6
    77import groups.models
    88from util.misc import log_and_ignore_failures, mkdir_p
     
    205205    def __unicode__(self, ):
    206206        return "PersonMembershipUpdate for %s" % (self.username, )
     207
     208
     209
     210##########
     211# MIDWAY #
     212##########
     213
     214
     215class Midway(models.Model):
     216    name = models.CharField(max_length=50)
     217    slug = models.SlugField()
     218    date = models.DateTimeField()
     219    table_map = models.ImageField(upload_to='midway/maps')
     220
     221    def __str__(self, ):
     222        return "%s" % (self.name, )
     223
     224class MidwayAssignment(models.Model):
     225    midway = models.ForeignKey(Midway)
     226    location = models.CharField(max_length=20)
     227    group = models.ForeignKey(groups.models.Group)
     228
     229    def __str__(self, ):
     230        return "<MidwayAssignment: %s at %s at %s>" % (self.group, self.location, self.midway, )
  • asadb/forms/views.py

    rd6f8984 r83759bf  
    1 import forms.models
    2 import groups.models
    3 import groups.views
    4 import settings
    5 import util.emails
    6 
     1import collections
     2import csv
     3import datetime
     4import StringIO
     5
     6from django.conf import settings
    77from django.contrib.auth.decorators import user_passes_test, login_required, permission_required
    88from django.core.exceptions import PermissionDenied
     
    1515from django.core.urlresolvers import reverse
    1616from django.core.mail import EmailMessage, mail_admins
     17from django.forms import FileField
    1718from django.forms import Form
    1819from django.forms import ModelForm
     
    2223from django.db.models import Q, Count
    2324
    24 import csv
    25 import datetime
    26 import StringIO
     25import django_filters
     26
     27import forms.models
     28import groups.models
     29import groups.views
     30import util.emails
    2731
    2832#################
     
    554558
    555559    return HttpResponse(buf.getvalue(), mimetype='text/csv', )
     560
     561
     562
     563##########
     564# Midway #
     565##########
     566
     567
     568class View_Midways(ListView):
     569    context_object_name = "midway_list"
     570    template_name = "midway/midway_list.html"
     571
     572    def get_queryset(self):
     573        midways = forms.models.Midway.objects.order_by('date')
     574        return midways
     575
     576    def get_context_data(self, **kwargs):
     577        context = super(View_Midways, self).get_context_data(**kwargs)
     578        context['pagename'] = 'midway'
     579        return context
     580
     581def midway_map_latest(request, ):
     582    midways = forms.models.Midway.objects.order_by('-date')[:1]
     583    if len(midways) == 0:
     584        raise Http404("No midways found.")
     585    else:
     586        url = reverse('midway-map', args=(midways[0].slug, ))
     587        return HttpResponseRedirect(url)
     588
     589
     590class MidwayAssignmentFilter(django_filters.FilterSet):
     591    name = django_filters.CharFilter(name='group__name', lookup_type='icontains', label="Name contains")
     592    abbreviation = django_filters.CharFilter(name='group__abbreviation', lookup_type='iexact', label="Abbreviation is")
     593    activity_category = django_filters.ModelChoiceFilter(
     594        label='Activity category',
     595        name='group__activity_category',
     596        queryset=groups.models.ActivityCategory.objects,
     597    )
     598
     599    class Meta:
     600        model = forms.models.MidwayAssignment
     601        fields = [
     602            'name',
     603            'abbreviation',
     604            'activity_category',
     605        ]
     606        order_by = (
     607            ('group__name', 'Name', ),
     608            ('group__abbreviation', 'Abbreviation', ),
     609            ('group__activity_category__name', 'Activity category', ),
     610            ('location', 'Location', ),
     611        )
     612
     613
     614class MidwayMapView(DetailView):
     615    context_object_name = "midway"
     616    model = forms.models.Midway
     617    template_name = 'midway/map.html'
     618
     619    def get_context_data(self, **kwargs):
     620        # Call the base implementation first to get a context
     621        context = super(MidwayMapView, self).get_context_data(**kwargs)
     622       
     623        filterset = MidwayAssignmentFilter(self.request.GET)
     624        context['assignments'] = filterset.qs
     625        context['filter'] = filterset
     626        context['pagename'] = 'midway'
     627
     628        return context
     629
     630
     631class MidwayAssignmentsUploadForm(Form):
     632    def validate_csv_fields(upload_file):
     633        reader = csv.reader(upload_file)
     634        row = reader.next()
     635        for col in ('Group', 'officers', 'Table', ):
     636            if col not in row:
     637                raise ValidationError('Please upload a CSV file with (at least) columns "Group", "officers", and "Table". (Missing at least "%s".)' % (col, ))
     638
     639    assignments = FileField(validators=[validate_csv_fields])
     640
     641@permission_required('forms.add_midwayassignment')
     642def midway_assignment_upload(request, slug, ):
     643    midway = get_object_or_404(forms.models.Midway, slug=slug, )
     644
     645    uploaded = False
     646    found = []
     647    issues = collections.defaultdict(list)
     648
     649    if request.method == 'POST': # If the form has been submitted...
     650        form = MidwayAssignmentsUploadForm(request.POST, request.FILES, ) # A form bound to the POST data
     651
     652        if form.is_valid(): # All validation rules pass
     653            uploaded = True
     654            reader = csv.DictReader(request.FILES['assignments'])
     655            for row in reader:
     656                group_name = row['Group']
     657                group_officers = row['officers']
     658                table = row['Table']
     659                issue = False
     660                try:
     661                    group = groups.models.Group.objects.get(name=group_name)
     662                    assignment = forms.models.MidwayAssignment(
     663                        midway=midway,
     664                        location=table,
     665                        group=group,
     666                    )
     667                    assignment.save()
     668                    found.append(assignment)
     669                    status = group.group_status.slug
     670                    if status != 'active':
     671                        issue = 'status=%s (added anyway)' % (status, )
     672                except groups.models.Group.DoesNotExist:
     673                    issue = 'unknown group (ignored)'
     674                except groups.models.Group.MultipleObjectsReturned:
     675                    issue = 'multiple groups found (ignored)'
     676                if issue:
     677                    issues[issue].append((group_name, group_officers, table))
     678            for issue in issues:
     679                issues[issue] = sorted(issues[issue], key=lambda x: x[0])
     680
     681    else:
     682        form = MidwayAssignmentsUploadForm() # An unbound form
     683
     684    context = {
     685        'midway':midway,
     686        'form':form,
     687        'uploaded': uploaded,
     688        'found': found,
     689        'issues': dict(issues),
     690        'pagename':'midway',
     691    }
     692    return render_to_response('midway/upload.html', context, context_instance=RequestContext(request), )
  • asadb/groups/admin.py

    rcbffe98 rfa218b1  
     1import datetime
     2
     3from django.contrib import admin
     4from django.utils.translation import ugettext_lazy
     5
     6from reversion.admin import VersionAdmin
     7
    18import groups.models
    2 from django.contrib import admin
    3 from reversion.admin import VersionAdmin
     9import util.admin
    410
    511class GroupAdmin(VersionAdmin):
     
    98104
    99105class 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
    100121    list_display = (
    101122        'id',
     
    121142    list_filter = [
    122143        'role',
     144        OfficeHolderPeriodFilter,
    123145    ]
    124146admin.site.register(groups.models.OfficeHolder, OfficeHolderAdmin)
     
    188210    list_display_links = ( 'id', 'username', )
    189211    search_fields = ( 'username', 'mit_id', 'first_name', 'last_name', 'account_class', )
     212    list_filter = ( 'account_class', 'mutable', )
    190213admin.site.register(groups.models.AthenaMoiraAccount, Admin_AthenaMoiraAccount)
  • asadb/groups/diffs.py

    r14f594b r9af1bb4  
    1111    os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
    1212
     13from django.conf import settings
    1314from django.contrib.contenttypes.models import ContentType
    1415from django.core.mail import EmailMessage, mail_admins
     
    2122
    2223import groups.models
    23 import settings
    2424import util.emails
    2525import util.mailinglist
     
    244244
    245245def 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', ])
    247247    status_pks = [status.pk for status in status_objs]
    248248    def pred(version, fields):
  • asadb/groups/models.py

    r6559695 rc4282e2  
    1 from django.db import models
    2 from django.core.validators import RegexValidator
    3 from django.contrib.auth.models import User
    4 from django.template.defaultfilters import slugify
    5 import reversion
    6 
    71import datetime
    82import filecmp
     
    1610import urllib2
    1711
    18 import settings
     12from django.conf import settings
     13from django.db import models
     14from django.core.validators import RegexValidator
     15from django.contrib.auth.models import User
     16from django.template.defaultfilters import slugify
     17
     18import reversion
    1919
    2020import mit
     
    2828        )
    2929
    30 locker_validator = RegexValidator(regex=r'^[-A-Za-z0-9_.]+$', message='Enter a valid Athena locker.')
     30locker_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..')
    3131
    3232class Group(models.Model):
     
    9898            if as_of == "now": as_of = datetime.datetime.now()
    9999            office_holders = office_holders.filter(start_time__lte=as_of, end_time__gte=as_of)
     100        office_holders = office_holders.order_by('role', 'person')
    100101        return office_holders
    101102
     
    504505
    505506    def __str__(self, ):
    506         active = ""
    507         if not self.is_active:
    508             active = " (inactive)"
    509         return "%s%s" % (self.name, active, )
     507        return self.name
    510508
    511509    class Meta:
  • asadb/groups/views.py

    rbfa60e8 r8ccd88f  
    44import csv
    55import datetime
    6 
    7 import groups.models
    86
    97from django.contrib.auth.decorators import user_passes_test, login_required, permission_required
     
    3028import django_filters
    3129
     30import groups.models
    3231from util.db_form_utils import StaticWidget
     32import util.db_filters
    3333from util.emails import email_from_template
    3434
     
    129129        for field in self.force_required:
    130130            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.")
    132132
    133133    exec_only_fields = [
     
    221221def manage_officers_load_officers(group, ):
    222222    officers = group.officers()
    223     people = list(set([ officer.person for officer in officers ]))
     223    people = sorted(set([ officer.person for officer in officers ]))
    224224    roles  = groups.models.OfficerRole.objects.all()
    225225
     
    573573    treasurer_name = forms.CharField(max_length=50)
    574574    treasurer_kerberos = forms.CharField(min_length=3, max_length=8, )
    575     def clean_president(self, ):
     575    def clean_president_kerberos(self, ):
    576576        username = self.cleaned_data['president_kerberos']
    577577        validate_athena(username, True, )
    578578        return username
    579579
    580     def clean_treasurer(self, ):
     580    def clean_treasurer_kerberos(self, ):
    581581        username = self.cleaned_data['treasurer_kerberos']
    582582        validate_athena(username, True, )
     
    631631        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."
    632632        self.fields['athena_locker'].required = True
     633        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.)"
     634
     635        # Specifically, if the group ends up wanting to use scripts.mit.edu,
     636        # they will currently be assigned locker.scripts.mit.edu. If they try
     637        # to use foo.bar, then https://foo.bar.scripts.mit.edu/ will produce a
     638        # certificate name mismatch. Officially, underscores are not allowed in
     639        # hostnames, so foo_.scripts.mit.edu may fail with some software.
    633640
    634641    class Meta(GroupCreateForm.Meta):
     
    900907    name = django_filters.CharFilter(lookup_type='icontains', label="Name contains")
    901908    abbreviation = django_filters.CharFilter(lookup_type='iexact', label="Abbreviation is")
     909    officer_email = django_filters.CharFilter(lookup_type='icontains', label="Officers' list contains")
     910
     911    account_filter = util.db_filters.MultiNumberFilter(
     912        lookup_type='exact', label="Account number",
     913        names=('main_account_id', 'funding_account_id', ),
     914    )
    902915
    903916    class Meta:
     
    906919            'name',
    907920            'abbreviation',
     921            'officer_email',
    908922            'activity_category',
    909923            'group_class',
    910924            'group_status',
    911925            'group_funding',
     926            'account_filter',
    912927        ]
    913928
     
    942957@permission_required('groups.view_signatories')
    943958def view_signatories(request, ):
    944     # TODO:
    945     # * limit which columns (roles) get displayed
    946     # This might want to wait for the generic reporting infrastructure, since
    947     # I'd imagine some of it can be reused.
    948 
    949959    the_groups = groups.models.Group.objects.all()
    950960    groups_filterset = GroupFilter(request.GET, the_groups)
    951961    the_groups = groups_filterset.qs
     962
    952963    officers = groups.models.OfficeHolder.objects.filter(start_time__lte=datetime.datetime.now(), end_time__gte=datetime.datetime.now())
    953964    officers = officers.filter(group__in=the_groups)
    954965    officers = officers.select_related(depth=1)
    955     roles = groups.models.OfficerRole.objects.all()
     966
     967    role_slugs = ['president', 'treasurer', 'financial', 'reservation']
     968    roles = groups.models.OfficerRole.objects.filter(slug__in=role_slugs)
     969    roles = sorted(roles, key=lambda r: role_slugs.index(r.slug))
     970
    956971    officers_map = collections.defaultdict(lambda: collections.defaultdict(set))
    957972    for officer in officers:
     
    961976        role_list = []
    962977        for role in roles:
    963             role_list.append(officers_map[group][role])
     978            role_list.append(sorted(officers_map[group][role]))
    964979        officers_data.append((group, role_list))
    965980
     
    10071022        if 'pk' in self.kwargs:
    10081023            group = get_object_or_404(groups.models.Group, pk=self.kwargs['pk'])
    1009             history_entries = reversion.models.Version.objects.get_for_object(group)
     1024            history_entries = reversion.get_for_object(group)
    10101025        else:
    10111026            history_entries = reversion.models.Version.objects.all()
     
    11001115    )
    11011116
     1117    special_fields_choices = (
     1118        ('option_entry', '<option> entry', ),
     1119    )
     1120    special_fields = forms.fields.MultipleChoiceField(
     1121        choices=special_fields_choices,
     1122        widget=forms.CheckboxSelectMultiple,
     1123        required=False,
     1124    )
     1125
    11021126    _format_choices = [
    11031127        ('html/inline',     "Web (HTML)", ),
     
    11151139            ('fields', {
    11161140                'legend': 'Data to display',
    1117                 'fields': ['basic_fields', 'people_fields', 'show_as_emails', ],
     1141                'fields': ['basic_fields', 'people_fields', 'show_as_emails', 'special_fields', ],
    11181142            }),
    11191143            ('final', {
     
    11551179        escaped = html.escape(email)
    11561180        return mark_safe("<a href='mailto:%s'>%s</a>" % (escaped, escaped))
     1181
     1182def format_option_entry(group):
     1183    name = html.escape(group.name)
     1184    return '<option value="%s">%s</option>' % (name, name, )
    11571185
    11581186reporting_html_formatters = {
     
    11951223        for field in people_fields:
    11961224            col_labels.append(field.display_name)
     1225
     1226        # Set up special fields
     1227        special_formatters = []
     1228        if 'option_entry' in form.cleaned_data['special_fields']:
     1229            col_labels.append('option_entry')
     1230            special_formatters.append(format_option_entry)
    11971231
    11981232        # Assemble data
     
    12071241                val = formatters[field](val)
    12081242            return val
     1243
    12091244        for group in qs:
    12101245            group_data = [fetch_item(group, field) for field in basic_fields]
     
    12131248                if show_as_emails: people = ["%s@mit.edu" % p for p in people]
    12141249                group_data.append(", ".join(people))
     1250
     1251            for formatter in special_formatters:
     1252                group_data.append(formatter(group))
    12151253
    12161254            report_groups.append(group_data)
     
    12281266            writer = csv.writer(response)
    12291267            writer.writerow(col_labels)
    1230             for row in report_groups: writer.writerow(row)
     1268            for row in report_groups:
     1269                utf8_row = [unicode(cell).encode("utf-8") for cell in row]
     1270                writer.writerow(utf8_row)
    12311271            return response
    12321272
     
    12511291    office_holders = office_holders.filter(role__in=student_roles)
    12521292    office_holders = office_holders.exclude(person__in=students.values('username'))
    1253     office_holders = office_holders.select_related('group', 'role')
     1293    office_holders = office_holders.select_related('group', 'group__group_status', 'role')
    12541294
    12551295    msg = None
     
    12571297    if 'sort' in request.GET:
    12581298        if request.GET['sort'] == 'group':
    1259             office_holders = office_holders.order_by('group__name', 'role', 'person', )
     1299            office_holders = office_holders.order_by('group__name', 'group__group_status', 'role', 'person', )
     1300        elif request.GET['sort'] == 'status':
     1301            office_holders = office_holders.order_by('group__group_status', 'group__name', 'role', 'person', )
    12601302        elif request.GET['sort'] == 'role':
    1261             office_holders = office_holders.order_by('role', 'group__name', 'person', )
     1303            office_holders = office_holders.order_by('role', 'group__group_status', 'group__name', 'person', )
    12621304        elif request.GET['sort'] == 'person':
    1263             office_holders = office_holders.order_by('person', 'group__name', 'role', )
     1305            office_holders = office_holders.order_by('person', 'group__group_status', 'group__name', 'role', )
    12641306        else:
    12651307            msg = 'Unknown sort key "%s".' % (request.GET['sort'], )
  • asadb/media/style/style.css

    rd6f8984 r1e08e97  
    234234.group-detail-page.group-status-active h1          { color: black; }
    235235.group-detail-page.group-status-applying h1        { color: red; }
    236 .group-detail-page.group-status-nge h1             { color: yellow; }
     236.group-detail-page.group-status-nge h1             { color: #00ffff; }
    237237.group-detail-page.group-status-provisional h1     { color: yellow; }
    238238.group-detail-page.group-status-derecognized h1    { color: red; }
     
    254254    color: black;
    255255}
     256.group-list-page tr.group-status-nge td.group-status
     257{
     258    background-color: #00ffff;
     259    color: black;
     260}
    256261
    257262
  • asadb/settings.py

    red5797d rb4cc0cc  
    121121    # Always use forward slashes, even on Windows.
    122122    # Don't forget to use absolute paths, not relative paths.
    123     'template',
     123    os.path.join(SITE_ROOT, 'template'),
    124124)
    125125
  • asadb/space/admin.py

    r21f7242 r3c1b20b  
     1from django.contrib import admin
     2
     3from reversion.admin import VersionAdmin
     4
    15import space.models
    2 from django.contrib import admin
    3 from reversion.admin import VersionAdmin
     6import util.admin
    47
    58class Admin_LockType(VersionAdmin):
     
    3033
    3134class Admin_SpaceAssignment(admin.ModelAdmin):
     35    class AssignmentPeriodFilter(util.admin.TimePeriodFilter):
     36        start_field = 'start'
     37        end_field = 'end'
     38
    3239    list_max_show_all = 500
    3340    list_display = (
     
    3946    )
    4047    list_display_links = list_display
    41     list_filter = ('space', )
     48    list_filter = (AssignmentPeriodFilter, 'space', )
    4249    search_fields = ( 'group__name', 'group__officer_email', 'space__number', )
    4350admin.site.register(space.models.SpaceAssignment, Admin_SpaceAssignment)
    4451
    4552class Admin_SpaceAccessListEntry(admin.ModelAdmin):
     53    class AccessPeriodFilter(util.admin.TimePeriodFilter):
     54        start_field = 'start'
     55        end_field = 'end'
     56
    4657    list_display = (
    4758        'group',
     
    5263    )
    5364    list_display_links = list_display
     65    list_filter = (AccessPeriodFilter, 'space', )
    5466    search_fields = (
    5567        'group__name', 'group__officer_email',
  • asadb/template/base.html

    rcec082b r35280b4  
    1414        <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        <li{% ifequal pagename "midway"     %} class='selected'{% endifequal %}><a href="{% url midway-list %}">Midway</a></li>
    1617        <li{% ifequal pagename "about"    %} class='selected'{% endifequal %}><a href="{% url about %}">Help &amp; About</a></li>
    1718        {% if user.is_staff %}<li><a href='{% url admin:index %}'>Admin</a></li>{% endif %}
  • asadb/template/groups/account_lookup.html

    rb928edc rcfde3dc  
    1717    <th>Group</th>
    1818    <td>{{group}}</td>
     19</tr>
     20<tr>
     21    <th>Status</th>
     22    <td>{{group.group_status}}</td>
    1923</tr>
    2024<tr>
     
    4650</table>
    4751
     52<h2>Group Status</h2>
     53
     54<table class='pretty-table'>
     55<tr>
     56    <th>Status</th>
     57    <th>Description</th>
     58</tr>
     59<tr>
     60    <th>Active</th>
     61    <td>Active groups are normal ASA groups in good standing.</td>
     62</tr>
     63<tr>
     64    <th>Suspended and Derecognized</th>
     65    <td>Suspended and derecognized groups are <strong>not</strong> in good standing, and should generally not be permitted to spend money, reserve rooms, etc..</td>
     66</tr>
     67<tr>
     68    <th>Non-Group Entity</th>
     69    <td>Non-Group Entities do not necessarily have any ASA recognition, but are included in the ASA Database as a courtesy to other MIT offices. You may assume the roles listed are authoritative. However, the ASA does not grant them any privileges.</td>
     70</tr>
     71</table>
     72
     73
    4874<h2>Available Roles</h2>
    4975
  • asadb/template/groups/create/startup_review.html

    rac93a0c r172255b  
    1414<tr><th>Description</th><td>{{group.description}}</td></tr>
    1515
    16 <tr><th colspan='2'>Type</th></tr>
     16<tr><th colspan='2'>Officers</th></tr>
    1717<tr><th>President</th><td>{{startup.president_name}} ({{startup.president_kerberos}})</td></tr>
    1818<tr><th>Treasurer</th><td>{{startup.treasurer_name}} ({{startup.treasurer_kerberos}})</td></tr>
  • asadb/template/groups/diffs/new-group-announce.txt

    r264efa0 r798ec5b  
    1212Your group's entry is at:
    1313
    14       http://asa.mit.edu/{% url groups:group-detail group.pk %}
     14      https://asa.mit.edu{% url groups:group-detail group.pk %}
    1515
    1616You should make sure to do the following:
  • asadb/template/groups/group_change_main.html

    rcb9b105 r4752b74  
    99{% include "groups/group_tools.part.html" %}
    1010
    11 <p>We're using the transition from the old ASA Database to this new one as an opportunity to verify that old information is still accurate. Thus, we have intentionally limited the amount of information we copied from the old database. While filling this out, feel free to refer to the <a href='https://sisapp2.mit.edu/asa/student_group_detail.do?action=detail&amp;studentGroupId={{group.pk}}'>old database</a>. However, please verify that information is accurate as you copy it over. As always, if you have trouble, please <a href='mailto:asa-exec@mit.edu'>contact us</a>.</p>
     11<p>Please make sure to keep the information about your group up-to-date. As always, if you have trouble (or need to update a field that you don't have access to), please <a href='mailto:asa-exec@mit.edu'>contact us</a>.</p>
    1212
    1313{% if msg %}
  • asadb/template/groups/group_list.html

    r8f46374 r58fe19c  
    1717
    1818<h2>The Groups</h2>
     19
     20<p>Found {{group_list|length}} groups:</p>
    1921
    2022<table class='pretty-table group-list'>
  • asadb/template/groups/reporting.html

    r1b10de0 r58fe19c  
    77<h1>Reporting</h1>
    88
    9 <h2>Results</h2>
     9<div class='toolbox'>
     10<h2>Sections</h2>
     11<ul>
     12<li><a href='#results'>Results</a></li>
     13<li><a href='#config'>Configuration</a></li>
     14</ul>
     15</div>
     16
     17<h2><a name='results'></a>Results</h2>
    1018{% if run_report %}
     19
     20<p>Found {{report_groups|length}} groups:</p>
     21
    1122<table class='pretty-table'>
    1223<thead>
     
    2738{% endif %}
    2839
    29 <h2>Configuration</h2>
     40<h2><a name='config'></a>Configuration</h2>
    3041<form action="" method="get">
    3142    {{ form|render }}
  • asadb/template/groups/reporting/non-students.html

    r213dd57 r52fb1d5  
    3535<tr>
    3636    <th><a href='?sort=group'>Group</a></th>
     37    <th><a href='?sort=status'>Status</a></th>
    3738    <th><a href='?sort=role'>Role</a></th>
    3839    <th><a href='?sort=person'>Person</a></th>
     
    4142<tr>
    4243    <td><a href='{% url groups:group-detail holder.group.pk %}'>{{holder.group.name}}</a></td>
     44    <td>{{holder.group.group_status}}</td>
    4345    <td>{{holder.role.display_name}}</td>
    4446    <td>{{holder.person}}</td>
  • asadb/template/index.html

    rf6982d4 r3c1b20b  
    4444        <li><a href='{%url fysm-select%}'>Submit an entry</a></li>
    4545    </ul></li>
     46    <li><a href='{% url midway-list %}'>Midways</a>: <a href='{% url midway-map-latest %}'>latest map</a></li>
    4647    <li>Membership updates<ul>
    4748        <li><a href='{%url membership-update-cycle %}'>Group update</a></li>
  • asadb/urls.py

    rf6982d4 r3c1b20b  
     1from django.conf import settings
    12from django.conf.urls.defaults import *
    23from django.contrib.auth.views import login, logout
     
    67from django.contrib import admin
    78admin.autodiscover()
    8 
    9 import settings
    109
    1110import groups.urls
     
    7473    url(r'^membership/admin/issues.csv$', forms.views.group_confirmation_issues, name='membership-issues', ),
    7574
     75    # Midway
     76    url(r'^midway/$', forms.views.View_Midways.as_view(), name='midway-list', ),
     77    url(r'^midway/latest/$', forms.views.midway_map_latest, name='midway-map-latest', ),
     78    url(r'^midway/(?P<slug>[\w-]+)/$', forms.views.MidwayMapView.as_view(), name='midway-map', ),
     79    url(r'^midway/(?P<slug>[\w-]+)/assign/$', forms.views.midway_assignment_upload, name='midway-assign', ),
     80
    7681    # Group list
    7782    (r'^groups/', include(groups.urls.urls(), ), ),
  • asadb/util/diff_static_data.sh

    re2ceffa r3c1b20b  
    55date
    66
    7 cd static-data
     7cd "$(dirname "$0")/static-data"
    88
    99../dump_group_perms.py > group-perms.py
  • asadb/space/diffs.py

    rc8f4eea r59506d6  
    88    cur_file = os.path.abspath(__file__)
    99    django_dir = os.path.abspath(os.path.join(os.path.dirname(cur_file), '..'))
     10    django_dir_parent = os.path.abspath(os.path.join(os.path.dirname(cur_file), '../..'))
    1011    sys.path.append(django_dir)
     12    sys.path.append(django_dir_parent)
    1113    os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
    1214
    1315from django.core.mail import EmailMessage
     16from django.core.urlresolvers import reverse
    1417from django.db import connection
    1518from django.db.models import Q
     
    100103
    101104    def list_office_changes(self, ):
    102         cac_lines = []
     105        systems_lines = {
     106            'cac-card': [],
     107            'none': [],
     108        }
    103109        group_lines = []
    104         def append_change(mit_id, verb, name):
    105             cac_lines.append("%s:\t%s:\t%s" % (mit_id, verb, name))
    106             group_lines.append("%s:\t%s" % (verb, name))
    107110        for space_pk, space_data in self.offices.items():
     111            lock_type = all_spaces[space_pk].lock_type
     112            system_lines = systems_lines[lock_type.db_update]
     113            def append_change(mit_id, verb, name):
     114                system_lines.append("%s:\t%s:\t%s" % (mit_id, verb, name))
     115                group_lines.append("%s:\t%s" % (verb, name))
     116
    108117            line = "Changes in %s:" % (all_spaces[space_pk].number, )
    109             cac_lines.append(line)
     118            system_lines.append(line)
    110119            group_lines.append(line)
     120
     121            if lock_type.db_update == 'none':
     122                tmpl =  'Warning: You submitted changes effecting this space, but this space is ' + \
     123                        'a "%s" space, and is not managed through the ASA DB. See ' + \
     124                        'https://asa.mit.edu/%s for details on how to update spaces of this type.'
     125                line = tmpl % (lock_type.name, reverse('space-lock-type'), )
     126                group_lines.append(line)
     127
    111128            for mit_id, (old_names, new_names) in space_data.items():
    112129                if mit_id is None: mit_id = "ID unknown"
     
    125142                        else:
    126143                            append_change(mit_id, "Add", name)
    127             cac_lines.append("")
     144            system_lines.append("")
    128145            group_lines.append("")
    129146
    130         cac_msg = "\n".join(cac_lines)
     147        systems_msg = dict([
     148            (system, '\n'.join(lines), ) for (system, lines) in systems_lines.items()
     149        ])
    131150        group_msg = "\n".join(group_lines)
    132         return cac_msg, group_msg
     151        return systems_msg, group_msg
    133152
    134153    def add_locker_signatories(self, space_access, time):
     
    177196
    178197def safe_add_change_real(change_by_name, change):
     198    """Add a new change to our dict of pending changes.
     199
     200    If a different change has already been added for this person (eg, "Remove"
     201    instead of "Keep", or with a different list of groups), error.  This should
     202    always succeed; if it doesn't, the code is buggy. We worry about this
     203    because we want to be really sure that the email that goes to just CAC is
     204    compatible with the emails that go to each groups. Since we iterate over
     205    the changes once per group, we want to be sure that for each group
     206    iteration we're building compatible information.
     207    """
     208
    179209    name = change.name
    180210    if name in change_by_name:
     
    200230        print "ID=%s (%s):\n\t%s\t(%s)\n\t%s\t(%s)\n" % (mit_id, unchanged, old_by_names, old_by_group, new_by_names, new_by_group, ),
    201231        for group_pk in joint_keys(old_by_group, new_by_group):
     232            # TODO: Do we need to do an iteration for each group? This seems
     233            # slightly questionable. Can we just loop over all known names?
     234
    202235            old_names = old_by_group[group_pk]
    203236            new_names = new_by_group[group_pk]
     
    267300    cac_locker_msgs = []
    268301
    269     process_spaces =  space.models.Space.objects.all()
     302    process_spaces =  space.models.Space.objects.all().select_related('lock_type')
    270303    for the_space in process_spaces:
    271304        new_cac_msgs = space_specific_access(the_space, group_data, old_time, new_time)
     
    274307
    275308    changed_groups = []
     309    cac_chars = 0
    276310    for group_pk, group_info in group_data.items():
    277311        group_info.add_office_signatories(old_time, new_time)
    278         cac_changes, group_office_changes = group_info.list_office_changes()
     312        systems_changes, group_office_changes = group_info.list_office_changes()
    279313        if group_info.changes:
    280             changed_groups.append((group_info.group, cac_changes, group_office_changes, group_info.locker_messages, ))
     314            cac_chars += len(systems_changes['cac-card'])
     315            changed_groups.append((group_info.group, systems_changes['cac-card'], group_office_changes, group_info.locker_messages, ))
    281316
    282317    asa_rcpts = ['asa-space@mit.edu', 'asa-db@mit.edu', ]
    283     if changed_groups:
     318    if cac_chars > 0 or cac_locker_msgs:
    284319        util.emails.email_from_template(
    285320            tmpl='space/cac-change-email.txt',
  • asadb/space/models.py

    r7eea15c r8aea837  
    1111EXPIRE_OFFSET   = datetime.timedelta(seconds=1)
    1212
     13LOCK_DB_UPDATE_NONE = 'none'
     14LOCK_DB_UPDATE_CAC_CARD = 'cac-card'
     15lock_db_update_choices = (
     16    (LOCK_DB_UPDATE_NONE, "No database management"),
     17    (LOCK_DB_UPDATE_CAC_CARD, "CAC-managed card-based access"),
     18)
     19
     20class LockType(models.Model):
     21    name = models.CharField(max_length=50)
     22    slug = models.SlugField(unique=True, )
     23    description = models.TextField()
     24    info_addr = models.EmailField(default='asa-exec@mit.edu', help_text='Address groups should email to get more information about managing access through this lock type.')
     25    info_url = models.URLField(blank=True, help_text='URL that groups can visit to get more information about this lock type.')
     26    db_update = models.CharField(max_length=20, default='none', choices=lock_db_update_choices)
     27
     28    def __unicode__(self, ):
     29        return self.name
     30
     31
    1332class Space(models.Model):
    1433    number = models.CharField(max_length=20, unique=True, )
    1534    asa_owned = models.BooleanField(default=True, )
     35    lock_type = models.ForeignKey(LockType)
    1636    merged_acl = models.BooleanField(default=False, help_text="Does this room have a single merged ACL, that combines all groups together, or CAC maintain a separate ACL per-group? Generally, the shared storage offices get a merged ACL and everything else doesn't.")
    1737    notes = models.TextField(blank=True, )
  • asadb/space/views.py

    rde2f0ac rf6982d4  
    9090        'allow_edit': allow_edit,
    9191        'extras_indices': extras_indices,
    92         'pagename':'group',
     92        'pagename':'groups',
    9393    }
    9494    return render_to_response('space/manage-access.html', context, context_instance=RequestContext(request), )
     
    112112        'locker_num',
    113113        'group__name',
    114     ).select_related('space', 'group')
     114    ).select_related('space', 'space__lock_type', 'group')
    115115    office_assignments = assignments.filter(locker_num='')
    116116
     
    127127        'offices': office_assignments,
    128128        'lockers': locker_rooms,
    129         'pagename':'group',
     129        'pagename':'groups',
    130130    }
    131131    return render_to_response('space/summary.html', context, context_instance=RequestContext(request), )
     132
     133def lock_types(request, ):
     134    lock_types = space.models.LockType.objects.order_by('name')
     135    context = {
     136        'lock_types': lock_types,
     137        'pagename': 'groups',
     138    }
     139    return render_to_response('space/lock_types.html', context, context_instance=RequestContext(request), )
  • asadb/template/space/group-change-email.txt

    rd768e47 r210d042  
    11{% autoescape off %}Hi {{group.name}},
    2     Thank you for updating space access on the ASA database today. We've forwarded the following changes on to CAC:
     2
     3Thank you for updating space access on the ASA database today. We are forwarding these changes to CAC, which generally updates access within [interval]. They will reply when the update has been done; if you don't get a reply, the message may have been lost and you should reply-all to this email reminding them.
     4
     5We believe you made the following changes:
    36
    47{% if office_msg %}
  • asadb/template/space/manage-access.html

    r78de8cb r1927d79  
    11{% extends "base.html" %}
    22
    3 {% block title %}{{group.name}}: Office Access{% endblock %}
     3{% block title %}{{group.name}}: Space Access{% endblock %}
    44{% block content %}
    55
    6 <h1>{{group.name}}: Office Access</h1>
     6<h1>{{group.name}}: Space Access</h1>
    77
    88{% include "groups/group_tools.part.html" %}
     
    4848    <th>{{assignment.space}}</th>
    4949    <td>
     50        {% with assignment.space.lock_type as lock_type %}
     51        {% if lock_type.db_update == "none" %}
     52        <p><strong>Warning: Access to this office is not managed through the ASA DB.</strong></p>
     53        <p><em><a href='{% url space-lock-type %}'>{{lock_type.name}}</a></em>: {{lock_type.description}}{% if lock_type.info_url %} <a href='{{lock_type.info_url}}'>Details.</a>{%endif%}</p>
     54        <p>Contact <a href='mailto:{{lock_type.info_addr}}'>{{lock_type.info_addr}}</a> for more information.</p>
     55
     56        {% else %}
     57
     58        <p>We recommend managing access on the <a href='{% url groups:group-manage-officers group.id %}'>update people</a> page if possible. You should only need to use this page if:</p>
     59        <ul>
     60            <li>You need to grant access to somebody who does not have an Athena account, or</li>
     61            <li>Your group has several offices, and somebody needs access to one or more of the offices, but should not have access to all of them</li>
     62        </ul>
     63
    5064        <table class='pretty-table'>
    5165            <tr>
     
    6781            </tr>{% endfor %}
    6882        </table>
     83
     84        {% endif %}
     85        {% endwith %}
     86
    6987    </td>
    7088</tr>
  • asadb/template/space/summary.html

    rde2f0ac rf6982d4  
    2727    <th>Room</th>
    2828    <th>Group</th>
     29    <th><a href='{% url space-lock-type %}'>Lock Type</a></th>
    2930    <th>Access</th>
    3031</tr>
     
    3334    <td>{{office.space.number}}</td>
    3435    <td><a href='{% url groups:group-detail office.group.pk %}'>{{office.group}}</a></td>
     36    <td>{{office.space.lock_type.name}}</td>
    3537    <td><a href='{% url groups:group-space-access office.group.pk %}'>Access</a></td>
    3638</tr>
Note: See TracChangeset for help on using the changeset viewer.