]> git.ozlabs.org Git - patchwork/blob - apps/patchwork/utils.py
bundles: Add check for duplicate bundles
[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 django.db.utils import IntegrityError
30 from patchwork.forms import MultiplePatchForm
31 from patchwork.models import Bundle, Project, BundlePatch, UserProfile, \
32         PatchChangeNotification, EmailOptout
33
34 def get_patch_ids(d, prefix = 'patch_id'):
35     ids = []
36
37     for (k, v) in d.items():
38         a = k.split(':')
39         if len(a) != 2:
40             continue
41         if a[0] != prefix:
42             continue
43         if not v:
44             continue
45         ids.append(a[1])
46
47     return ids
48
49 class Order(object):
50     order_map = {
51         'date':         'date',
52         'name':         'name',
53         'state':        'state__ordering',
54         'submitter':    'submitter__name',
55         'delegate':     'delegate__username',
56     }
57     default_order = ('date', True)
58
59     def __init__(self, str = None, editable = False):
60         self.reversed = False
61         self.editable = editable
62         (self.order, self.reversed) = self.default_order
63
64         if self.editable:
65             return
66
67         if str is None or str == '':
68             return
69
70         reversed = False
71         if str[0] == '-':
72             str = str[1:]
73             reversed = True
74
75         if str not in self.order_map.keys():
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         if Bundle.objects.filter(owner = user, name = bundle_name).count() > 0:
112             return ['You already have a bundle called "%s"' % bundle_name]
113
114         bundle = Bundle(owner = user, project = project,
115                 name = bundle_name)
116         bundle.save()
117         context.add_message("Bundle %s created" % bundle.name)
118
119     elif action =='add':
120         bundle = get_object_or_404(Bundle, id = data['bundle_id'])
121
122     elif action =='remove':
123         bundle = get_object_or_404(Bundle, id = data['removed_bundle_id'])
124
125     if not bundle:
126         return ['no such bundle']
127
128     for patch in patches:
129         if action == 'create' or action == 'add':
130             bundlepatch_count = BundlePatch.objects.filter(bundle = bundle,
131                         patch = patch).count()
132             if bundlepatch_count == 0:
133                 bundle.append_patch(patch)
134                 context.add_message("Patch '%s' added to bundle %s" % \
135                         (patch.name, bundle.name))
136             else:
137                 context.add_message("Patch '%s' already in bundle %s" % \
138                         (patch.name, bundle.name))
139
140         elif action == 'remove':
141             try:
142                 bp = BundlePatch.objects.get(bundle = bundle, patch = patch)
143                 bp.delete()
144                 context.add_message("Patch '%s' removed from bundle %s\n" % \
145                         (patch.name, bundle.name))
146             except Exception:
147                 pass
148
149     bundle.save()
150
151     return []
152
153 def send_notifications():
154     date_limit = datetime.datetime.now() - \
155                      datetime.timedelta(minutes =
156                                 settings.NOTIFICATION_DELAY_MINUTES)
157
158     # This gets funky: we want to filter out any notifications that should
159     # be grouped with other notifications that aren't ready to go out yet. To
160     # do that, we join back onto PatchChangeNotification (PCN -> Patch ->
161     # Person -> Patch -> max(PCN.last_modified)), filtering out any maxima
162     # that are with the date_limit.
163     qs = PatchChangeNotification.objects \
164             .annotate(m = Max('patch__submitter__patch__patchchangenotification'
165                         '__last_modified')) \
166                 .filter(m__lt = date_limit)
167
168     groups = itertools.groupby(qs.order_by('patch__submitter'),
169                                lambda n: n.patch.submitter)
170
171     errors = []
172
173     for (recipient, notifications) in groups:
174         notifications = list(notifications)
175
176         def delete_notifications():
177             PatchChangeNotification.objects.filter(
178                                 pk__in = notifications).delete()
179
180         if EmailOptout.is_optout(recipient.email):
181             delete_notifications()
182             continue
183
184         context = {
185             'site': Site.objects.get_current(),
186             'person': recipient,
187             'notifications': notifications,
188         }
189         subject = render_to_string(
190                         'patchwork/patch-change-notification-subject.text',
191                         context).strip()
192         content = render_to_string('patchwork/patch-change-notification.mail',
193                                 context)
194
195         message = EmailMessage(subject = subject, body = content,
196                                from_email = settings.NOTIFICATION_FROM_EMAIL,
197                                to = [recipient.email],
198                                headers = {'Precedence': 'bulk'})
199
200         try:
201             message.send()
202         except ex:
203             errors.append((recipient, ex))
204             continue
205
206         delete_notifications()
207
208     return errors