]> git.ozlabs.org Git - patchwork/blob - apps/patchwork/utils.py
notifications: Add code to send notifications
[patchwork] / apps / patchwork / utils.py
1 # Patchwork - automated patch tracking system
2 # Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
3 #
4 # This file is part of the Patchwork package.
5 #
6 # Patchwork is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # Patchwork is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with Patchwork; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20
21 import itertools
22 import datetime
23 from django.shortcuts import get_object_or_404
24 from django.template.loader import render_to_string
25 from django.contrib.sites.models import Site
26 from django.conf import settings
27 from django.core.mail import EmailMessage
28 from django.db.models import Max
29 from patchwork.forms import MultiplePatchForm
30 from patchwork.models import Bundle, Project, BundlePatch, UserProfile, \
31         PatchChangeNotification
32
33 def get_patch_ids(d, prefix = 'patch_id'):
34     ids = []
35
36     for (k, v) in d.items():
37         a = k.split(':')
38         if len(a) != 2:
39             continue
40         if a[0] != prefix:
41             continue
42         if not v:
43             continue
44         ids.append(a[1])
45
46     return ids
47
48 class Order(object):
49     order_map = {
50         'date':         'date',
51         'name':         'name',
52         'state':        'state__ordering',
53         'submitter':    'submitter__name',
54         'delegate':     'delegate__username',
55     }
56     default_order = ('date', True)
57
58     def __init__(self, str = None, editable = False):
59         self.reversed = False
60         self.editable = editable
61
62         if self.editable:
63             return
64
65         if str is None or str == '':
66             (self.order, self.reversed) = self.default_order
67             return
68
69         reversed = False
70         if str[0] == '-':
71             str = str[1:]
72             reversed = True
73
74         if str not in self.order_map.keys():
75             (self.order, self.reversed) = self.default_order
76             return
77
78         self.order = str
79         self.reversed = reversed
80
81     def __str__(self):
82         str = self.order
83         if self.reversed:
84             str = '-' + str
85         return str
86
87     def name(self):
88         return self.order
89
90     def reversed_name(self):
91         if self.reversed:
92             return self.order
93         else:
94             return '-' + self.order
95
96     def query(self):
97         q = self.order_map[self.order]
98         if self.reversed:
99             q = '-' + q
100         return q
101
102 bundle_actions = ['create', 'add', 'remove']
103 def set_bundle(user, project, action, data, patches, context):
104     # set up the bundle
105     bundle = None
106     if action == 'create':
107         bundle_name = data['bundle_name'].strip()
108         if not bundle_name:
109             return ['No bundle name was specified']
110
111         bundle = Bundle(owner = user, project = project,
112                 name = bundle_name)
113         bundle.save()
114         context.add_message("Bundle %s created" % bundle.name)
115
116     elif action =='add':
117         bundle = get_object_or_404(Bundle, id = data['bundle_id'])
118
119     elif action =='remove':
120         bundle = get_object_or_404(Bundle, id = data['removed_bundle_id'])
121
122     if not bundle:
123         return ['no such bundle']
124
125     for patch in patches:
126         if action == 'create' or action == 'add':
127             bundlepatch_count = BundlePatch.objects.filter(bundle = bundle,
128                         patch = patch).count()
129             if bundlepatch_count == 0:
130                 bundle.append_patch(patch)
131                 context.add_message("Patch '%s' added to bundle %s" % \
132                         (patch.name, bundle.name))
133             else:
134                 context.add_message("Patch '%s' already in bundle %s" % \
135                         (patch.name, bundle.name))
136
137         elif action == 'remove':
138             try:
139                 bp = BundlePatch.objects.get(bundle = bundle, patch = patch)
140                 bp.delete()
141                 context.add_message("Patch '%s' removed from bundle %s\n" % \
142                         (patch.name, bundle.name))
143             except Exception:
144                 pass
145
146     bundle.save()
147
148     return []
149
150 def send_notifications():
151     date_limit = datetime.datetime.now() - \
152                      datetime.timedelta(minutes =
153                                 settings.NOTIFICATION_DELAY_MINUTES)
154
155     # This gets funky: we want to filter out any notifications that should
156     # be grouped with other notifications that aren't ready to go out yet. To
157     # do that, we join back onto PatchChangeNotification (PCN -> Patch ->
158     # Person -> Patch -> max(PCN.last_modified)), filtering out any maxima
159     # that are with the date_limit.
160     qs = PatchChangeNotification.objects \
161             .annotate(m = Max('patch__submitter__patch__patchchangenotification'
162                         '__last_modified')) \
163                 .filter(m__lt = date_limit)
164
165     groups = itertools.groupby(qs.order_by('patch__submitter'),
166                                lambda n: n.patch.submitter)
167
168     errors = []
169
170     for (recipient, notifications) in groups:
171         notifications = list(notifications)
172         context = {
173             'site': Site.objects.get_current(),
174             'person': recipient,
175             'notifications': notifications,
176         }
177         subject = render_to_string(
178                         'patchwork/patch-change-notification-subject.text',
179                         context).strip()
180         content = render_to_string('patchwork/patch-change-notification.mail',
181                                 context)
182
183         message = EmailMessage(subject = subject, body = content,
184                                from_email = settings.DEFAULT_FROM_EMAIL,
185                                to = [recipient.email],
186                                headers = {'Precedence': 'bulk'})
187
188         try:
189             message.send()
190         except ex:
191             errors.append((recipient, ex))
192             continue
193
194         PatchChangeNotification.objects.filter(pk__in = notifications).delete()
195
196     return errors