source: asadb/space/models.py @ 59506d6

space-accessstablestage
Last change on this file since 59506d6 was e2ceffa, checked in by Alex Dehnert <adehnert@…>, 13 years ago

Add list of lock types and assign them to rooms

  • Property mode set to 100755
File size: 7.2 KB
Line 
1import collections
2import datetime
3
4from django.db import models
5from django.db.models import Q
6
7import reversion
8
9import groups.models
10
11EXPIRE_OFFSET   = datetime.timedelta(seconds=1)
12
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    db_update = models.CharField(max_length=20, default='none', choices=lock_db_update_choices)
26
27    def __unicode__(self, ):
28        return self.name
29
30
31class Space(models.Model):
32    number = models.CharField(max_length=20, unique=True, )
33    asa_owned = models.BooleanField(default=True, )
34    lock_type = models.ForeignKey(LockType)
35    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.")
36    notes = models.TextField(blank=True, )
37
38    def __unicode__(self, ):
39        if self.asa_owned:
40            asa_str = "ASA"
41        else:
42            asa_str = "Non-ASA"
43        return u"%s (%s)" % (self.number, asa_str)
44
45    def build_access(self, time=None, group=None, ):
46        """Assemble a list of who had access to this Space.
47
48        time:
49            optional; indicate that you want access as of a particular time.
50            If omitted, uses the present.
51        group:
52            optional; indicates that you want access via a particular group.
53            If omitted, finds access via any group.
54
55        Return value:
56            tuple (access, assignments, aces, errors)
57            access is the main field that matters, but the others are potentially useful supplementary information
58
59            access:
60                Group.pk -> (ID -> Set name)
61                Indicates who has access. Grouped by group and ID number.
62                Usually, the sets will each have one member, but ID 999999999 is decently likely to have several.
63                The SpaceAccessListEntrys will be filtered to reflect assignments as of that time.
64            access_by_id:
65                ID -> (Name -> (Set Group.pk))
66                Indicates who has access. Grouped by ID number and name.
67                Usually, each ID dict will have one member, but ID 999999999 is, again, likely to have several.
68                This is intended for rooms that have one access list (e.g., W20-437 and W20-441)
69            assignments:
70                [SpaceAssignment]
71                QuerySet of all SpaceAssignments involving the space and group at the time
72            aces:
73                [SpaceAccessListEntry]
74                QuerySet of all SpaceAccessListEntrys involving the space and group at the time.
75                This is not filtered for the ace's group having a relevant SpaceAssignment.
76            errors:
77                [String]
78                errors/warnings that occurred.
79                Includes messages about groups no longer having access.
80        """
81
82        if time is None:
83            time = datetime.datetime.now()
84        errors = []
85        time_q = Q(end__gte=time, start__lte=time)
86        assignments = SpaceAssignment.objects.filter(time_q, space=self)
87        aces = SpaceAccessListEntry.objects.filter(time_q, space=self)
88        if group:
89            assignments = assignments.filter(group=group)
90            aces = aces.filter(group=group)
91        access = {}    # Group.pk -> (ID -> Set name)
92        # ID -> (Name -> (Set Group.pk))
93        access_by_id = collections.defaultdict(lambda: collections.defaultdict(set))
94        for assignment in assignments:
95            if assignment.group.pk not in access:
96                access[assignment.group.pk] = collections.defaultdict(set)
97        for ace in aces:
98            if ace.group.pk in access:
99                access[ace.group.pk][ace.card_number].add(ace.name)
100                access_by_id[ace.card_number][ace.name].add(ace.group.pk)
101            else:
102                # This group appears to no longer have access...
103                errors.append("Group %s no longer has access to %s, but has live ACEs." % (ace.group, self, ))
104        return access, access_by_id, assignments, aces, errors
105
106reversion.register(Space)
107
108
109class CurrentAssignmentManager(models.Manager):
110    def get_query_set(self, ):
111        return super(CurrentAssignmentManager, self).get_query_set().filter(
112            start__lte=datetime.date.today,
113            end__gte=datetime.date.today,
114        )
115
116class SpaceAssignment(models.Model):
117    END_NEVER       = datetime.datetime.max
118
119    group = models.ForeignKey(groups.models.Group, db_index=True, )
120    space = models.ForeignKey(Space, db_index=True, )
121    start = models.DateField(default=datetime.datetime.now, db_index=True, )
122    end = models.DateField(default=END_NEVER, db_index=True, )
123
124    notes = models.TextField(blank=True, )
125    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.')
126
127    objects = models.Manager()
128    current = CurrentAssignmentManager()
129
130    def expire(self, ):
131        self.end_time = datetime.datetime.now()-self.EXPIRE_OFFSET
132        self.save()
133
134    def is_locker(self, ):
135        return bool(self.locker_num)
136
137    def __unicode__(self, ):
138        return u"<SpaceAssignment group=%s space=%s locker=%s start=%s end=%s>" % (
139            self.group,
140            self.space,
141            self.locker_num,
142            self.start,
143            self.end,
144        )
145
146
147class CurrentACLEntryManager(models.Manager):
148    def get_query_set(self, ):
149        return super(CurrentACLEntryManager, self).get_query_set().filter(
150            start__lte=datetime.datetime.now,
151            end__gte=datetime.datetime.now,
152        )
153
154def now_offset():
155    return datetime.datetime.now()-EXPIRE_OFFSET
156
157class SpaceAccessListEntry(models.Model):
158    END_NEVER       = datetime.datetime.max
159
160    group = models.ForeignKey(groups.models.Group, db_index=True, )
161    space = models.ForeignKey(Space, db_index=True, )
162    start = models.DateTimeField(default=now_offset, db_index=True, )
163    end = models.DateTimeField(default=END_NEVER, db_index=True, )
164
165    name = models.CharField(max_length=50)
166    card_number = models.CharField(max_length=20, verbose_name="MIT ID", help_text="MIT ID number (as printed on, eg, the relevant ID card)")
167
168    objects = models.Manager()
169    current = CurrentACLEntryManager()
170
171    def expire(self, ):
172        self.end = now_offset()
173        self.save()
174
175    def format_name(self, ):
176        return u"%s (%s)" % (self.name, self.card_number, )
177
178    def __unicode__(self, ):
179        return u"<SpaceAccessListEntry group=%s space=%s name=%s start=%s end=%s>" % (
180            self.group,
181            self.space,
182            self.name,
183            self.start,
184            self.end,
185        )
Note: See TracBrowser for help on using the repository browser.