]> git.ozlabs.org Git - patchwork/blob - apps/patchwork/utils.py
requestcontext: Initialise 'messages' context var
[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, EmailOptout
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         (self.order, self.reversed) = self.default_order
62
63         if self.editable:
64             return
65
66         if str is None or str == '':
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             return
76
77         self.order = str
78         self.reversed = reversed
79
80     def __str__(self):
81         str = self.order
82         if self.reversed:
83             str = '-' + str
84         return str
85
86     def name(self):
87         return self.order
88
89     def reversed_name(self):
90         if self.reversed:
91             return self.order
92         else:
93             return '-' + self.order
94
95     def query(self):
96         q = self.order_map[self.order]
97         if self.reversed:
98             q = '-' + q
99         return q
100
101 bundle_actions = ['create', 'add', 'remove']
102 def set_bundle(user, project, action, data, patches, context):
103     # set up the bundle
104     bundle = None
105     if action == 'create':
106         bundle_name = data['bundle_name'].strip()
107         if not bundle_name:
108             return ['No bundle name was specified']
109
110         bundle = Bundle(owner = user, project = project,
111                 name = bundle_name)
112         bundle.save()
113         context.add_message("Bundle %s created" % bundle.name)
114
115     elif action =='add':
116         bundle = get_object_or_404(Bundle, id = data['bundle_id'])
117
118     elif action =='remove':
119         bundle = get_object_or_404(Bundle, id = data['removed_bundle_id'])
120
121     if not bundle:
122         return ['no such bundle']
123
124     for patch in patches:
125         if action == 'create' or action == 'add':
126             bundlepatch_count = BundlePatch.objects.filter(bundle = bundle,
127                         patch = patch).count()
128             if bundlepatch_count == 0:
129                 bundle.append_patch(patch)
130                 context.add_message("Patch '%s' added to bundle %s" % \
131                         (patch.name, bundle.name))
132             else:
133                 context.add_message("Patch '%s' already in bundle %s" % \
134                         (patch.name, bundle.name))
135
136         elif action == 'remove':
137             try:
138                 bp = BundlePatch.objects.get(bundle = bundle, patch = patch)
139                 bp.delete()
140                 context.add_message("Patch '%s' removed from bundle %s\n" % \
141                         (patch.name, bundle.name))
142             except Exception:
143                 pass
144
145     bundle.save()
146
147     return []
148
149 def send_notifications():
150     date_limit = datetime.datetime.now() - \
151                      datetime.timedelta(minutes =
152                                 settings.NOTIFICATION_DELAY_MINUTES)
153
154     # This gets funky: we want to filter out any notifications that should
155     # be grouped with other notifications that aren't ready to go out yet. To
156     # do that, we join back onto PatchChangeNotification (PCN -> Patch ->
157     # Person -> Patch -> max(PCN.last_modified)), filtering out any maxima
158     # that are with the date_limit.
159     qs = PatchChangeNotification.objects \
160             .annotate(m = Max('patch__submitter__patch__patchchangenotification'
161                         '__last_modified')) \
162                 .filter(m__lt = date_limit)
163
164     groups = itertools.groupby(qs.order_by('patch__submitter'),
165                                lambda n: n.patch.submitter)
166
167     errors = []
168
169     for (recipient, notifications) in groups:
170         notifications = list(notifications)
171
172         def delete_notifications():
173             PatchChangeNotification.objects.filter(
174                                 pk__in = notifications).delete()
175
176         if EmailOptout.is_optout(recipient.email):
177             delete_notifications()
178             continue
179
180         context = {
181             'site': Site.objects.get_current(),
182             'person': recipient,
183             'notifications': notifications,
184         }
185         subject = render_to_string(
186                         'patchwork/patch-change-notification-subject.text',
187                         context).strip()
188         content = render_to_string('patchwork/patch-change-notification.mail',
189                                 context)
190
191         message = EmailMessage(subject = subject, body = content,
192                                from_email = settings.NOTIFICATION_FROM_EMAIL,
193                                to = [recipient.email],
194                                headers = {'Precedence': 'bulk'})
195
196         try:
197             message.send()
198         except ex:
199             errors.append((recipient, ex))
200             continue
201
202         delete_notifications()
203
204     return errors