source: asadb/space/diffs.py @ 96ef09f

space-accessstablestage
Last change on this file since 96ef09f was 57a2ad6, checked in by Alex Dehnert <adehnert@…>, 14 years ago

Optimize space/diffs.py a bit by batching a query

We don't care for running this on the server, but cutting the edittest cycle
time from 10s to 5s is worthwhile...

  • Property mode set to 100755
File size: 6.9 KB
Line 
1#!/usr/bin/python
2import collections
3import datetime
4import os
5import sys
6
7if __name__ == '__main__':
8    cur_file = os.path.abspath(__file__)
9    django_dir = os.path.abspath(os.path.join(os.path.dirname(cur_file), '..'))
10    sys.path.append(django_dir)
11    os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
12
13from django.core.mail import EmailMessage
14from django.db import connection
15from django.db.models import Q
16from django.template import Context, Template
17from django.template.loader import get_template
18
19import groups.diffs
20import groups.models
21import space.models
22import util.emails
23
24role = {
25    'office': groups.models.OfficerRole.objects.get(slug='office-access')
26}
27
28people_name = {} # username -> full name
29people_id = {} # username -> MIT ID
30
31all_spaces = {} # Space.pk -> Space
32
33def bulk_fill_people(times):
34    max_time = max(times)
35    min_time = min(times)
36    active_holders = groups.models.OfficeHolder.objects.filter(
37        start_time__lte=max_time,
38        end_time__gte=min_time,
39        role__in=role.values(),
40    )
41    usernames = active_holders.values_list('person', flat=True,)
42    people = groups.models.AthenaMoiraAccount.objects.filter(username__in=usernames)
43    for person in people:
44        people_name[person.username] = person.format()
45        people_id[person.username] = person.mit_id
46
47def fill_people(holder):
48    if not holder.person in people_name:
49        #print "Person %s not pre-cached" % (holder.person, )
50        try:
51            person = groups.models.AthenaMoiraAccount.objects.get(username=holder.person)
52            people_name[holder.person] = person.format()
53            people_id[holder.person] = person.mit_id
54        except groups.models.AthenaMoiraAccount.DoesNotExist:
55            people_name[holder.person] = "<%s>" % (holder.person, )
56            people_id[holder.person] = None
57
58class GroupInfo(object):
59    def __init__(self, group, ):
60        self.group = group
61        self.offices = {}  # Space.pk -> (ID -> (Set name, Set name))
62        if not role:
63            role['office'] = groups.models.OfficerRole.objects.get(slug='office-access')
64
65    def learn_access(self, space_pk, old, new):
66        group_pk = self.group.pk
67        if group_pk in old:
68            old_access = old[group_pk]
69        else: old_access = {}
70        if group_pk in new:
71            new_access = new[group_pk]
72        else: new_access = {}
73        assert space_pk not in self.offices
74
75        # Let's fill out the self.offices set.
76        self.offices[space_pk] = collections.defaultdict(lambda: (set(), set()))
77        space_data = self.offices[space_pk]
78        for mit_id, old_set in old_access.items():
79            space_data[mit_id][0].update(old_set)
80        for mit_id, new_set in new_access.items():
81            space_data[mit_id][1].update(new_set)
82
83    def add_office_signatories_per_time(self, ind, time):
84        group = self.group
85        people = group.officers(as_of=time, role=role['office'])
86        for holder in people:
87            fill_people(holder)
88        for office_id, office_data in self.offices.items():
89            for holder in people:
90                holder_name = people_name[holder.person]
91                holder_id = people_id[holder.person]
92                office_data[holder_id][ind].add(holder_name)
93
94    def add_office_signatories(self, old_time, new_time, ):
95        group = self.group
96        self.add_office_signatories_per_time(0, old_time)
97        self.add_office_signatories_per_time(1, new_time)
98
99    def list_changes(self, ):
100        cac_lines = []
101        group_lines = []
102        def append_change(mit_id, verb, name):
103            cac_lines.append("%s:\t%s:\t%s" % (mit_id, verb, name))
104            group_lines.append("%s:\t%s" % (verb, name))
105        changes = False
106        for space_pk, space_data in self.offices.items():
107            line = "Changes in %s:" % (all_spaces[space_pk].number, )
108            cac_lines.append(line)
109            group_lines.append(line)
110            for mit_id, (old_names, new_names) in space_data.items():
111                if mit_id is None: mit_id = "ID unknown"
112                if old_names == new_names:
113                    pass
114                else:
115                    changes = True
116                    for name in old_names:
117                        if name in new_names:
118                            append_change(mit_id, "Keep", name)
119                        else:
120                            append_change(mit_id, "Remove", name)
121                    for name in new_names:
122                        if name in old_names:
123                            pass
124                        else:
125                            append_change(mit_id, "Add", name)
126            cac_lines.append("")
127            group_lines.append("")
128
129        cac_msg = "\n".join(cac_lines)
130        group_msg = "\n".join(group_lines)
131        return changes, cac_msg, group_msg
132
133def init_groups(the_groups, assignments):
134    for assignment in assignments:
135        group = assignment.group
136        if group.id not in the_groups:
137            the_groups[group.id] = GroupInfo(group)
138
139def space_specific_access(group_data, old_time, new_time, ):
140    process_spaces =  space.models.Space.objects.all()
141    #process_spaces = process_spaces.filter(number="W20-467")
142    for the_space in process_spaces:
143        old_data = the_space.build_access(time=old_time)
144        new_data = the_space.build_access(time=new_time)
145        all_spaces[the_space.pk] = the_space
146        init_groups(group_data, old_data[1])
147        init_groups(group_data, new_data[1])
148        for group_pk, group in group_data.items():
149            if group_pk in old_data[0] or group_pk in new_data[0]:
150                group.learn_access(the_space.pk, old_data[0], new_data[0])
151
152
153def space_access_diffs():
154    new_time = datetime.datetime.utcnow()
155    old_time = new_time - datetime.timedelta(days=1)
156    bulk_fill_people([old_time, new_time])
157    group_data = {} # Group.pk -> GroupInfo
158    changed_groups = []
159    space_specific_access(group_data, old_time, new_time)
160    for group_pk, group_info in group_data.items():
161        group_info.add_office_signatories(old_time, new_time)
162        changes, cac_changes, group_changes = group_info.list_changes()
163        if changes:
164            changed_groups.append((group_info.group, cac_changes, group_changes))
165
166    asa_rcpts = ['asa-space@mit.edu', 'asa-db@mit.edu', ]
167    util.emails.email_from_template(
168        tmpl='space/cac-change-email.txt',
169        context={'changed_groups': changed_groups},
170        subject="Office access updates",
171        to=['caclocks@mit.edu'],
172        cc=asa_rcpts,
173    ).send()
174    for group, cac_msg, group_msg in changed_groups:
175        util.emails.email_from_template(
176            tmpl='space/group-change-email.txt',
177            context={
178                'group':group,
179                'msg':group_msg,
180            },
181            subject="Office access updates",
182            to=[group.officer_email],
183            cc=asa_rcpts,
184        ).send()
185       
186
187if __name__ == "__main__":
188    space_access_diffs()
Note: See TracBrowser for help on using the repository browser.