source: asadb/groups/diffs.py @ 84af6d7

space-accessstablestagetest-hooks
Last change on this file since 84af6d7 was 281891e, checked in by Alex Dehnert <adehnert@…>, 14 years ago

Send mail about updating asa-official to asa-d

testing@… is probably the wrong address for anything real...

  • Property mode set to 100755
File size: 9.7 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.contrib.contenttypes.models import ContentType
14from django.core.mail import EmailMessage, mail_admins
15from django.db import connection
16from django.db.models import Q
17from django.template import Context, Template
18from django.template.loader import get_template
19
20import reversion.models
21
22import groups.models
23import settings
24import util.emails
25import util.mailman
26
27update_asa_exec = 'asa-exec@mit.edu'
28update_funding_board = 'asa-db@mit.edu'
29update_constitution_archive = 'asa-db@mit.edu'
30
31if settings.PRODUCTION_DEPLOYMENT:
32    asa_all_groups_list = util.mailman.MailmanList('asa-official')
33else:
34    asa_all_groups_list = util.mailman.MailmanList('asa-test-mailman')
35
36class DiffCallback(object):
37    def start_run(self, since, now, ):
38        pass
39    def end_run(self, ):
40        pass
41    def handle_group(self, before, after, before_fields, after_fields, ):
42        pass
43    def handle_signatories(self, signatories, ):
44        pass
45    def new_group(self, after, after_fields, ):
46        pass
47
48class StaticMailCallback(DiffCallback):
49    def __init__(self, fields, address, template, signatories=[]):
50        self.fields = fields
51        self.address = address
52        self.template = template
53        self.interesting_signatories = signatories
54        self.care_about_groups = True
55        self.care_about_signatories = True
56
57    def start_run(self, since, now, ):
58        self.updates = []
59        self.signatory_updates = []
60        self.signatory_type_counts = {
61            'Added': {},
62            'Expired': {},
63        }
64        self.since = since
65        self.now = now
66
67    def handle_group(self, before, after, before_fields, after_fields, ):
68        after_revision = after.revision
69        update = "Group: %s (ID #%d)\n" % (after_fields['name'], after_fields['id'], )
70        update += "At %s by %s (and possibly other people or times)\n" % (
71            after_revision.date_created, after_revision.user, )
72        for field in self.fields:
73            if before_fields[field] != after_fields[field]:
74                update += "%s: '%s' -> '%s'\n" % (
75                    field, before_fields[field], after_fields[field], )
76        self.updates.append(update)
77
78    def handle_signatories(self, signatories, ):
79        for signatory in signatories:
80            if signatory.end_time > self.now:
81                change_type = "Added"
82            else:
83                change_type = "Expired"
84            counter = self.signatory_type_counts[change_type]
85            if signatory.role.slug in counter:
86                counter[signatory.role.slug] += 1
87            else:
88                counter[signatory.role.slug] = 0
89            if signatory.role.slug in self.interesting_signatories:
90                self.signatory_updates.append(
91                    "%s: %s: %s: %s:\n\trange %s to %s" % (
92                        change_type,
93                        signatory.group,
94                        signatory.role,
95                        signatory.person,
96                        signatory.start_time.strftime(settings.DATETIME_FORMAT_PYTHON),
97                        signatory.end_time.strftime(settings.DATETIME_FORMAT_PYTHON),
98                    ))
99            else:
100                print "Ignoring role %s (signatory %s)" % (signatory.role.slug, signatory, )
101
102    def end_run(self, ):
103        message = "\n\n".join(self.updates)
104        signatories_message = "\n".join(self.signatory_updates)
105        if (self.care_about_groups and self.updates) or (self.care_about_signatories and self.signatory_updates):
106            pass
107        else:
108            return
109        tmpl = get_template(self.template)
110        ctx = Context({
111            'num_groups': len(self.updates),
112            'groups_message': message,
113            'num_signatory_records': len(self.signatory_updates),
114            'signatory_types': self.interesting_signatories,
115            'signatory_type_counts': self.signatory_type_counts,
116            'signatories_message': signatories_message,
117        })
118        body = tmpl.render(ctx)
119        email = EmailMessage(
120            subject='ASA Database Updates',
121            body=body,
122            from_email='asa-db@mit.edu',
123            to=[self.address, ],
124            bcc=['asa-db-outgoing@mit.edu', ]
125        )
126        email.send()
127        self.updates = []
128        self.signatory_updates = []
129
130
131class UpdateOfficerListCallback(DiffCallback):
132    def start_run(self, since, now, ):
133        self.add = []
134        self.delete = []
135
136    def end_run(self, ):
137        if self.add or self.delete:
138            errors = asa_all_groups_list.change_members(self.add, self.delete)
139            subject = "asa-official updater"
140            if errors:
141                subject = "ERROR: " + subject
142            context = {
143                'listname': asa_all_groups_list.name,
144                'add': self.add,
145                'delete': self.delete,
146                'errors': errors,
147            }
148            util.emails.email_from_template(
149                tmpl='groups/diffs/asa-official-update.txt',
150                context=context, subject=subject,
151                to=['asa-db@mit.edu'],
152            ).send()
153
154    def handle_group(self, before, after, before_fields, after_fields, ):
155        if before_fields['officer_email'] != after_fields['officer_email']:
156            self.add.append("%s <%s>" % (after_fields['name'], after_fields['officer_email'], ))
157            self.delete.append(before_fields['officer_email'])
158
159    def new_group(self, after, after_fields, ):
160        self.add.append(after_fields['officer_email'])
161
162
163diff_fields = {
164    'name' :            [ update_asa_exec, ],
165    'abbreviation' :    [ update_asa_exec, ],
166    'officer_email' :   [ update_asa_exec, update_funding_board ],
167    'constitution_url': [ update_asa_exec, update_constitution_archive ],
168}
169
170def build_callbacks():
171    callbacks = []
172    callbacks.append(StaticMailCallback(
173        fields=['name', 'abbreviation', 'officer_email', 'constitution_url', ],
174        address='asa-exec@mit.edu',
175        template='groups/diffs/asa-update-mail.txt',
176        signatories=['president', 'treasurer', 'financial', ]
177    ))
178    sao_callback = StaticMailCallback(
179        fields=['name', 'abbreviation', 'officer_email', ],
180        address='funds@mit.edu',
181        template='groups/diffs/sao-update-mail.txt',
182        signatories=['president', 'treasurer', 'financial', ]
183    )
184    sao_callback.care_about_groups = False
185    callbacks.append(sao_callback)
186    callbacks.append(UpdateOfficerListCallback())
187    return callbacks
188
189def recent_groups(since):
190    group_type = ContentType.objects.get_by_natural_key(app_label='groups', model='group')
191    revisions = reversion.models.Revision.objects.filter(date_created__gte=since)
192    versions = reversion.models.Version.objects.filter(revision__in=revisions, content_type=group_type)
193    objs = versions.values("content_type", "object_id").distinct()
194    return objs
195
196def diff_objects(objs, since, callbacks):
197    revs  = reversion.models.Revision.objects.all()
198    old_revs = revs.filter(date_created__lte=since)
199    new_revs = revs.filter(date_created__gte=since)
200    for obj in objs:
201        all_versions = reversion.models.Version.objects.filter(content_type=obj['content_type'], object_id=obj['object_id']).order_by('-revision__date_created')
202        before_versions = all_versions.filter(revision__in=old_revs)[:1]
203        # This object being passed in means that some version changed it.
204        after_versions = all_versions.filter(revision__in=new_revs).select_related('revision__user')
205        after = after_versions[0]
206
207        if len(before_versions) > 0 or len(after_versions) > 1:
208            if len(before_versions) > 0:
209                before = before_versions[0]
210            else:
211                # New group that's been edited since. Diff against the creation
212                # (since creation sent mail, but later changes haven't)
213                after = after_versions[-1]
214            print "Change?: before=%s (%d), after=%s (%d), type=%s, new=%s" % (
215                before, before.pk,
216                after, after.pk,
217                after.type, after.field_dict,
218            )
219            before_fields = before.field_dict
220            after_fields = after.field_dict
221            for callback in callbacks:
222                callback.handle_group(before, after, before_fields, after_fields)
223        else:
224            # New group that's only been edited once
225            pass
226
227def diff_signatories(since, now, callbacks):
228    # First: still around; then added recently
229    qobj_added = Q(end_time__gte=now, start_time__gte=since)
230    # First: already gone; then it existed for a while; finally expired recently
231    qobj_expired = Q(end_time__lte=now, start_time__lte=since, end_time__gte=since)
232    changed_signatories = groups.models.OfficeHolder.objects.filter(qobj_added|qobj_expired)
233    changed_signatories.order_by('group__name', 'role__display_name', 'person', )
234    changed_signatories = changed_signatories.select_related('role', 'group')
235    for callback in callbacks: callback.handle_signatories(changed_signatories)
236
237def generate_diffs():
238    now = datetime.datetime.now()
239    recent = now - datetime.timedelta(hours=24, minutes=15)
240    objs = recent_groups(since=recent)
241    callbacks = build_callbacks()
242    for callback in callbacks: callback.start_run(since=recent, now=now, )
243    diff_objects(objs, since=recent, callbacks=callbacks)
244    diff_signatories(recent, now, callbacks=callbacks, )
245    for callback in callbacks: callback.end_run()
246
247if __name__ == '__main__':
248    generate_diffs()
Note: See TracBrowser for help on using the repository browser.