source: asadb/space/models.py @ d0bfc27

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

WIP: lock types (#96)

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