source: asadb/space/models.py

stablestage
Last change on this file was ef118cf, checked in by Alex Dehnert <adehnert@…>, 12 years ago

Merge branch 'master' into filters

  • master: (107 commits) People lookup: Handle more name formats (ASA-#253) People lookup: specify intended format (ASA-#253) People lookup: handle round numbers (ASA-#253) People lookup: force names to lowercase (ASA-#252) Add hazing and non-discrimination links (ASA-#255) Correct description of submitted link Add missing column heading (ASA-#258) Re-wrap anti-hazing email (ASA-#256) Add recognition date to issues.csv People lookup: fix *small* groups... Add docs for setting up chosen.js Link people lookup tool from membership update page People lookup: mention the data source People lookup: Support large numbers of people People lookup: make the UI more reasonable Basic people lookup support (ASA-#186) Add pre-group-approval warnings Reword membership definition text More thoroughly purge mention of membership lists Standarize on UK-style ndashes ...

Conflicts:

asadb/groups/models.py

  • Property mode set to 100755
File size: 8.7 KB
RevLine 
[bec7760]1import collections
[a86a924]2import datetime
3
4from django.db import models
[bec7760]5from django.db.models import Q
[a86a924]6
7import reversion
8
9import groups.models
10
[9dfb3d5]11EXPIRE_OFFSET   = datetime.timedelta(seconds=1)
[a86a924]12
[e2ceffa]13LOCK_DB_UPDATE_NONE = 'none'
[1eee8d1]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
[d0bfc27]20class LockType(models.Model):
21    name = models.CharField(max_length=50)
22    slug = models.SlugField(unique=True, )
[1eee8d1]23    description = models.TextField()
[00017c8]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.')
[8aea837]25    info_url = models.URLField(blank=True, help_text='URL that groups can visit to get more information about this lock type.')
[e2ceffa]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
[d0bfc27]31
[a86a924]32class Space(models.Model):
33    number = models.CharField(max_length=20, unique=True, )
34    asa_owned = models.BooleanField(default=True, )
[1eee8d1]35    lock_type = models.ForeignKey(LockType)
[5680065]36    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.")
[a86a924]37    notes = models.TextField(blank=True, )
38
39    def __unicode__(self, ):
40        if self.asa_owned:
41            asa_str = "ASA"
42        else:
43            asa_str = "Non-ASA"
44        return u"%s (%s)" % (self.number, asa_str)
[bec7760]45
46    def build_access(self, time=None, group=None, ):
47        """Assemble a list of who had access to this Space.
48
49        time:
50            optional; indicate that you want access as of a particular time.
51            If omitted, uses the present.
52        group:
53            optional; indicates that you want access via a particular group.
54            If omitted, finds access via any group.
55
56        Return value:
57            tuple (access, assignments, aces, errors)
58            access is the main field that matters, but the others are potentially useful supplementary information
59
60            access:
61                Group.pk -> (ID -> Set name)
62                Indicates who has access. Grouped by group and ID number.
63                Usually, the sets will each have one member, but ID 999999999 is decently likely to have several.
64                The SpaceAccessListEntrys will be filtered to reflect assignments as of that time.
[94f3a39]65            access_by_id:
66                ID -> (Name -> (Set Group.pk))
67                Indicates who has access. Grouped by ID number and name.
68                Usually, each ID dict will have one member, but ID 999999999 is, again, likely to have several.
69                This is intended for rooms that have one access list (e.g., W20-437 and W20-441)
[bec7760]70            assignments:
71                [SpaceAssignment]
72                QuerySet of all SpaceAssignments involving the space and group at the time
73            aces:
74                [SpaceAccessListEntry]
75                QuerySet of all SpaceAccessListEntrys involving the space and group at the time.
76                This is not filtered for the ace's group having a relevant SpaceAssignment.
77            errors:
78                [String]
79                errors/warnings that occurred.
80                Includes messages about groups no longer having access.
81        """
82
83        if time is None:
84            time = datetime.datetime.now()
85        errors = []
86        time_q = Q(end__gte=time, start__lte=time)
87        assignments = SpaceAssignment.objects.filter(time_q, space=self)
88        aces = SpaceAccessListEntry.objects.filter(time_q, space=self)
89        if group:
90            assignments = assignments.filter(group=group)
91            aces = aces.filter(group=group)
92        access = {}    # Group.pk -> (ID -> Set name)
[5680065]93        # ID -> (Name -> (Set Group.pk))
94        access_by_id = collections.defaultdict(lambda: collections.defaultdict(set))
[bec7760]95        for assignment in assignments:
96            if assignment.group.pk not in access:
97                access[assignment.group.pk] = collections.defaultdict(set)
98        for ace in aces:
99            if ace.group.pk in access:
100                access[ace.group.pk][ace.card_number].add(ace.name)
[94f3a39]101                access_by_id[ace.card_number][ace.name].add(ace.group.pk)
[bec7760]102            else:
103                # This group appears to no longer have access...
104                errors.append("Group %s no longer has access to %s, but has live ACEs." % (ace.group, self, ))
[94f3a39]105        return access, access_by_id, assignments, aces, errors
[bec7760]106
[a86a924]107reversion.register(Space)
108
[6ff04b1]109
110class CurrentAssignmentManager(models.Manager):
111    def get_query_set(self, ):
112        return super(CurrentAssignmentManager, self).get_query_set().filter(
113            start__lte=datetime.date.today,
114            end__gte=datetime.date.today,
115        )
116
[a86a924]117class SpaceAssignment(models.Model):
118    END_NEVER       = datetime.datetime.max
119
[a03cb61]120    group = models.ForeignKey(groups.models.Group, db_index=True, )
121    space = models.ForeignKey(Space, db_index=True, )
122    start = models.DateField(default=datetime.datetime.now, db_index=True, )
123    end = models.DateField(default=END_NEVER, db_index=True, )
[a86a924]124
125    notes = models.TextField(blank=True, )
126    locker_num = models.CharField(max_length=10, blank=True, help_text='Locker number. If set, will use the "locker-access" OfficerRole to maintain access. If unset/blank, uses "office-access" and SpaceAccessListEntry for access.')
127
[6ff04b1]128    objects = models.Manager()
129    current = CurrentAssignmentManager()
130
[a86a924]131    def expire(self, ):
132        self.end_time = datetime.datetime.now()-self.EXPIRE_OFFSET
133        self.save()
134
[6ff04b1]135    def is_locker(self, ):
136        return bool(self.locker_num)
137
138    def __unicode__(self, ):
139        return u"<SpaceAssignment group=%s space=%s locker=%s start=%s end=%s>" % (
140            self.group,
141            self.space,
142            self.locker_num,
143            self.start,
144            self.end,
145        )
146
147
[d7557b8]148groups.models.filter_registry.register(
[62f73df]149    category='space',
[d7557b8]150    slug='space:owners',
151    name='Space owners',
152    desc='Groups with space',
153    qs_thunk=lambda: SpaceAssignment.current.values('group'),
154)
155
[2563230]156def assignment_filter(building=None, locker=None):
157    assign = SpaceAssignment.current.all()
158    if building:
159        assign = assign.filter(
160            space__number__startswith="%s-" % (building, ),
161        )
162    if locker == True:
163        assign = assign.exclude(locker_num="")
164    elif locker == False:
165        assign = assign.filter(locker_num="")
166    owners = groups.models.Group.objects.filter(pk__in=assign.values('group'))
[d7557b8]167    return owners
168
169groups.models.filter_registry.register(
[62f73df]170    category='space',
[2563230]171    slug='space:locker',
172    name='Locker owners',
173    desc='Owners of lockers',
174    qs_thunk=lambda: assignment_filter(locker=True),
175)
176groups.models.filter_registry.register(
177    category='space',
178    slug='space:office',
179    name='Office owners',
180    desc='Owners of offices',
181    qs_thunk=lambda: assignment_filter(locker=False),
182)
183groups.models.filter_registry.register(
184    category='space',
[d7557b8]185    slug='space:w20',
186    name='W20 owners',
187    desc='Owners of W20 space',
[2563230]188    qs_thunk=lambda: assignment_filter(building='W20'),
[d7557b8]189)
190groups.models.filter_registry.register(
[62f73df]191    category='space',
[d7557b8]192    slug='space:walker',
193    name='Walker owners',
194    desc='Owners of Walker space',
[2563230]195    qs_thunk=lambda: assignment_filter(building='50'),
[d7557b8]196)
197
[2563230]198
[6ff04b1]199class CurrentACLEntryManager(models.Manager):
200    def get_query_set(self, ):
201        return super(CurrentACLEntryManager, self).get_query_set().filter(
202            start__lte=datetime.datetime.now,
203            end__gte=datetime.datetime.now,
204        )
205
[9dfb3d5]206def now_offset():
207    return datetime.datetime.now()-EXPIRE_OFFSET
208
[a86a924]209class SpaceAccessListEntry(models.Model):
210    END_NEVER       = datetime.datetime.max
211
[a03cb61]212    group = models.ForeignKey(groups.models.Group, db_index=True, )
213    space = models.ForeignKey(Space, db_index=True, )
214    start = models.DateTimeField(default=now_offset, db_index=True, )
215    end = models.DateTimeField(default=END_NEVER, db_index=True, )
[a86a924]216
217    name = models.CharField(max_length=50)
[7eea15c]218    card_number = models.CharField(max_length=20, verbose_name="MIT ID", help_text="MIT ID number (as printed on, eg, the relevant ID card)")
[a86a924]219
[6ff04b1]220    objects = models.Manager()
221    current = CurrentACLEntryManager()
222
[a86a924]223    def expire(self, ):
[9dfb3d5]224        self.end = now_offset()
[a86a924]225        self.save()
[6ff04b1]226
227    def format_name(self, ):
228        return u"%s (%s)" % (self.name, self.card_number, )
229
230    def __unicode__(self, ):
231        return u"<SpaceAccessListEntry group=%s space=%s name=%s start=%s end=%s>" % (
232            self.group,
233            self.space,
234            self.name,
235            self.start,
236            self.end,
237        )
Note: See TracBrowser for help on using the repository browser.