source: asadb/space/models.py @ 6dfcd66

space-accessstablestage
Last change on this file since 6dfcd66 was 5680065, checked in by Alex Dehnert <adehnert@…>, 14 years ago

Make locker updates work

I *think* this whole nasty pile of code may *finally* do what it's supposed to.

Fixes ASA Trac #15 and #7, maybe?

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