1 # Patchwork - automated patch tracking system
2 # Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
4 # This file is part of the Patchwork package.
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.
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.
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
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
34 def get_patch_ids(d, prefix = 'patch_id'):
37 for (k, v) in d.items():
53 'state': 'state__ordering',
54 'submitter': 'submitter__name',
55 'delegate': 'delegate__username',
57 default_order = ('date', True)
59 def __init__(self, str = None, editable = False):
61 self.editable = editable
62 (self.order, self.reversed) = self.default_order
67 if str is None or str == '':
75 if str not in self.order_map.keys():
79 self.reversed = reversed
90 def reversed_name(self):
94 return '-' + self.order
97 q = self.order_map[self.order]
102 bundle_actions = ['create', 'add', 'remove']
103 def set_bundle(user, project, action, data, patches, context):
106 if action == 'create':
107 bundle_name = data['bundle_name'].strip()
109 return ['No bundle name was specified']
111 if Bundle.objects.filter(owner = user, name = bundle_name).count() > 0:
112 return ['You already have a bundle called "%s"' % bundle_name]
114 bundle = Bundle(owner = user, project = project,
117 context.add_message("Bundle %s created" % bundle.name)
120 bundle = get_object_or_404(Bundle, id = data['bundle_id'])
122 elif action =='remove':
123 bundle = get_object_or_404(Bundle, id = data['removed_bundle_id'])
126 return ['no such bundle']
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))
137 context.add_message("Patch '%s' already in bundle %s" % \
138 (patch.name, bundle.name))
140 elif action == 'remove':
142 bp = BundlePatch.objects.get(bundle = bundle, patch = patch)
144 context.add_message("Patch '%s' removed from bundle %s\n" % \
145 (patch.name, bundle.name))
153 def send_notifications():
154 date_limit = datetime.datetime.now() - \
155 datetime.timedelta(minutes =
156 settings.NOTIFICATION_DELAY_MINUTES)
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)
168 groups = itertools.groupby(qs.order_by('patch__submitter'),
169 lambda n: n.patch.submitter)
173 for (recipient, notifications) in groups:
174 notifications = list(notifications)
176 def delete_notifications():
177 PatchChangeNotification.objects.filter(
178 pk__in = notifications).delete()
180 if EmailOptout.is_optout(recipient.email):
181 delete_notifications()
185 'site': Site.objects.get_current(),
187 'notifications': notifications,
189 subject = render_to_string(
190 'patchwork/patch-change-notification-subject.text',
192 content = render_to_string('patchwork/patch-change-notification.mail',
195 message = EmailMessage(subject = subject, body = content,
196 from_email = settings.NOTIFICATION_FROM_EMAIL,
197 to = [recipient.email],
198 headers = {'Precedence': 'bulk'})
203 errors.append((recipient, ex))
206 delete_notifications()