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, EmailOptout
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
61 (self.order, self.reversed) = self.default_order
66 if str is None or str == '':
74 if str not in self.order_map.keys():
78 self.reversed = reversed
89 def reversed_name(self):
93 return '-' + self.order
96 q = self.order_map[self.order]
101 bundle_actions = ['create', 'add', 'remove']
102 def set_bundle(user, project, action, data, patches, context):
105 if action == 'create':
106 bundle_name = data['bundle_name'].strip()
108 return ['No bundle name was specified']
110 bundle = Bundle(owner = user, project = project,
113 context.add_message("Bundle %s created" % bundle.name)
116 bundle = get_object_or_404(Bundle, id = data['bundle_id'])
118 elif action =='remove':
119 bundle = get_object_or_404(Bundle, id = data['removed_bundle_id'])
122 return ['no such bundle']
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))
133 context.add_message("Patch '%s' already in bundle %s" % \
134 (patch.name, bundle.name))
136 elif action == 'remove':
138 bp = BundlePatch.objects.get(bundle = bundle, patch = patch)
140 context.add_message("Patch '%s' removed from bundle %s\n" % \
141 (patch.name, bundle.name))
149 def send_notifications():
150 date_limit = datetime.datetime.now() - \
151 datetime.timedelta(minutes =
152 settings.NOTIFICATION_DELAY_MINUTES)
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)
164 groups = itertools.groupby(qs.order_by('patch__submitter'),
165 lambda n: n.patch.submitter)
169 for (recipient, notifications) in groups:
170 notifications = list(notifications)
172 def delete_notifications():
173 PatchChangeNotification.objects.filter(
174 pk__in = notifications).delete()
176 if EmailOptout.is_optout(recipient.email):
177 delete_notifications()
181 'site': Site.objects.get_current(),
183 'notifications': notifications,
185 subject = render_to_string(
186 'patchwork/patch-change-notification-subject.text',
188 content = render_to_string('patchwork/patch-change-notification.mail',
191 message = EmailMessage(subject = subject, body = content,
192 from_email = settings.NOTIFICATION_FROM_EMAIL,
193 to = [recipient.email],
194 headers = {'Precedence': 'bulk'})
199 errors.append((recipient, ex))
202 delete_notifications()