source: asadb/space/models.py @ e5c5180

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

Incorporate feedback from CAC

  • Expand the description for "CAC Combo Only"
  • Link to the description of "CAC Key" on the ASA website
  • Property mode set to 100755
File size: 7.3 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    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
32class Space(models.Model):
33    number = models.CharField(max_length=20, unique=True, )
34    asa_owned = models.BooleanField(default=True, )
35    lock_type = models.ForeignKey(LockType)
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.")
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)
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.
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)
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)
93        # ID -> (Name -> (Set Group.pk))
94        access_by_id = collections.defaultdict(lambda: collections.defaultdict(set))
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)
101                access_by_id[ace.card_number][ace.name].add(ace.group.pk)
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, ))
105        return access, access_by_id, assignments, aces, errors
106
107reversion.register(Space)
108
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
117class SpaceAssignment(models.Model):
118    END_NEVER       = datetime.datetime.max
119
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, )
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
128    objects = models.Manager()
129    current = CurrentAssignmentManager()
130
131    def expire(self, ):
132        self.end_time = datetime.datetime.now()-self.EXPIRE_OFFSET
133        self.save()
134
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
148class CurrentACLEntryManager(models.Manager):
149    def get_query_set(self, ):
150        return super(CurrentACLEntryManager, self).get_query_set().filter(
151            start__lte=datetime.datetime.now,
152            end__gte=datetime.datetime.now,
153        )
154
155def now_offset():
156    return datetime.datetime.now()-EXPIRE_OFFSET
157
158class SpaceAccessListEntry(models.Model):
159    END_NEVER       = datetime.datetime.max
160
161    group = models.ForeignKey(groups.models.Group, db_index=True, )
162    space = models.ForeignKey(Space, db_index=True, )
163    start = models.DateTimeField(default=now_offset, db_index=True, )
164    end = models.DateTimeField(default=END_NEVER, db_index=True, )
165
166    name = models.CharField(max_length=50)
167    card_number = models.CharField(max_length=20, verbose_name="MIT ID", help_text="MIT ID number (as printed on, eg, the relevant ID card)")
168
169    objects = models.Manager()
170    current = CurrentACLEntryManager()
171
172    def expire(self, ):
173        self.end = now_offset()
174        self.save()
175
176    def format_name(self, ):
177        return u"%s (%s)" % (self.name, self.card_number, )
178
179    def __unicode__(self, ):
180        return u"<SpaceAccessListEntry group=%s space=%s name=%s start=%s end=%s>" % (
181            self.group,
182            self.space,
183            self.name,
184            self.start,
185            self.end,
186        )
Note: See TracBrowser for help on using the repository browser.