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 patchwork.forms import MultiplePatchForm
30 from patchwork.models import Bundle, Project, BundlePatch, UserProfile, \
31 PatchChangeNotification
33 def get_patch_ids(d, prefix = 'patch_id'):
36 for (k, v) in d.items():
52 'state': 'state__ordering',
53 'submitter': 'submitter__name',
54 'delegate': 'delegate__username',
56 default_order = ('date', True)
58 def __init__(self, str = None, editable = False):
60 self.editable = editable
65 if str is None or str == '':
66 (self.order, self.reversed) = self.default_order
74 if str not in self.order_map.keys():
75 (self.order, self.reversed) = self.default_order
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 bundle = Bundle(owner = user, project = project,
114 context.add_message("Bundle %s created" % bundle.name)
117 bundle = get_object_or_404(Bundle, id = data['bundle_id'])
119 elif action =='remove':
120 bundle = get_object_or_404(Bundle, id = data['removed_bundle_id'])
123 return ['no such bundle']
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))
134 context.add_message("Patch '%s' already in bundle %s" % \
135 (patch.name, bundle.name))
137 elif action == 'remove':
139 bp = BundlePatch.objects.get(bundle = bundle, patch = patch)
141 context.add_message("Patch '%s' removed from bundle %s\n" % \
142 (patch.name, bundle.name))
150 def send_notifications():
151 date_limit = datetime.datetime.now() - \
152 datetime.timedelta(minutes =
153 settings.NOTIFICATION_DELAY_MINUTES)
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)
165 groups = itertools.groupby(qs.order_by('patch__submitter'),
166 lambda n: n.patch.submitter)
170 for (recipient, notifications) in groups:
171 notifications = list(notifications)
173 'site': Site.objects.get_current(),
175 'notifications': notifications,
177 subject = render_to_string(
178 'patchwork/patch-change-notification-subject.text',
180 content = render_to_string('patchwork/patch-change-notification.mail',
183 message = EmailMessage(subject = subject, body = content,
184 from_email = settings.NOTIFICATION_FROM_EMAIL,
185 to = [recipient.email],
186 headers = {'Precedence': 'bulk'})
191 errors.append((recipient, ex))
194 PatchChangeNotification.objects.filter(pk__in = notifications).delete()