diff options
Diffstat (limited to 'patchwork/utils.py')
-rw-r--r-- | patchwork/utils.py | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/patchwork/utils.py b/patchwork/utils.py new file mode 100644 index 0000000..9ed9e41 --- /dev/null +++ b/patchwork/utils.py @@ -0,0 +1,248 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org> +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +import itertools +import datetime +from django.shortcuts import get_object_or_404 +from django.template.loader import render_to_string +from django.contrib.auth.models import User +from django.contrib.sites.models import Site +from django.conf import settings +from django.core.mail import EmailMessage +from django.db.models import Max, Q, F +from django.db.utils import IntegrityError +from patchwork.forms import MultiplePatchForm +from patchwork.models import Bundle, Project, BundlePatch, UserProfile, \ + PatchChangeNotification, EmailOptout, EmailConfirmation + +def get_patch_ids(d, prefix = 'patch_id'): + ids = [] + + for (k, v) in d.items(): + a = k.split(':') + if len(a) != 2: + continue + if a[0] != prefix: + continue + if not v: + continue + ids.append(a[1]) + + return ids + +class Order(object): + order_map = { + 'date': 'date', + 'name': 'name', + 'state': 'state__ordering', + 'submitter': 'submitter__name', + 'delegate': 'delegate__username', + } + default_order = ('date', True) + + def __init__(self, str = None, editable = False): + self.reversed = False + self.editable = editable + (self.order, self.reversed) = self.default_order + + if self.editable: + return + + if str is None or str == '': + return + + reversed = False + if str[0] == '-': + str = str[1:] + reversed = True + + if str not in self.order_map.keys(): + return + + self.order = str + self.reversed = reversed + + def __str__(self): + str = self.order + if self.reversed: + str = '-' + str + return str + + def name(self): + return self.order + + def reversed_name(self): + if self.reversed: + return self.order + else: + return '-' + self.order + + def apply(self, qs): + q = self.order_map[self.order] + if self.reversed: + q = '-' + q + + orders = [q] + + # if we're using a non-default order, add the default as a secondary + # ordering. We reverse the default if the primary is reversed. + (default_name, default_reverse) = self.default_order + if self.order != default_name: + q = self.order_map[default_name] + if self.reversed ^ default_reverse: + q = '-' + q + orders.append(q) + + return qs.order_by(*orders) + +bundle_actions = ['create', 'add', 'remove'] +def set_bundle(user, project, action, data, patches, context): + # set up the bundle + bundle = None + if action == 'create': + bundle_name = data['bundle_name'].strip() + if '/' in bundle_name: + return ['Bundle names can\'t contain slashes'] + + if not bundle_name: + return ['No bundle name was specified'] + + if Bundle.objects.filter(owner = user, name = bundle_name).count() > 0: + return ['You already have a bundle called "%s"' % bundle_name] + + bundle = Bundle(owner = user, project = project, + name = bundle_name) + bundle.save() + context.add_message("Bundle %s created" % bundle.name) + + elif action =='add': + bundle = get_object_or_404(Bundle, id = data['bundle_id']) + + elif action =='remove': + bundle = get_object_or_404(Bundle, id = data['removed_bundle_id']) + + if not bundle: + return ['no such bundle'] + + for patch in patches: + if action == 'create' or action == 'add': + bundlepatch_count = BundlePatch.objects.filter(bundle = bundle, + patch = patch).count() + if bundlepatch_count == 0: + bundle.append_patch(patch) + context.add_message("Patch '%s' added to bundle %s" % \ + (patch.name, bundle.name)) + else: + context.add_message("Patch '%s' already in bundle %s" % \ + (patch.name, bundle.name)) + + elif action == 'remove': + try: + bp = BundlePatch.objects.get(bundle = bundle, patch = patch) + bp.delete() + context.add_message("Patch '%s' removed from bundle %s\n" % \ + (patch.name, bundle.name)) + except Exception: + pass + + bundle.save() + + return [] + +def send_notifications(): + date_limit = datetime.datetime.now() - \ + datetime.timedelta(minutes = + settings.NOTIFICATION_DELAY_MINUTES) + + # This gets funky: we want to filter out any notifications that should + # be grouped with other notifications that aren't ready to go out yet. To + # do that, we join back onto PatchChangeNotification (PCN -> Patch -> + # Person -> Patch -> max(PCN.last_modified)), filtering out any maxima + # that are with the date_limit. + qs = PatchChangeNotification.objects \ + .annotate(m = Max('patch__submitter__patch__patchchangenotification' + '__last_modified')) \ + .filter(m__lt = date_limit) + + groups = itertools.groupby(qs.order_by('patch__submitter'), + lambda n: n.patch.submitter) + + errors = [] + + for (recipient, notifications) in groups: + notifications = list(notifications) + projects = set([ n.patch.project.linkname for n in notifications ]) + + def delete_notifications(): + pks = [ n.pk for n in notifications ] + PatchChangeNotification.objects.filter(pk__in = pks).delete() + + if EmailOptout.is_optout(recipient.email): + delete_notifications() + continue + + context = { + 'site': Site.objects.get_current(), + 'person': recipient, + 'notifications': notifications, + 'projects': projects, + } + + subject = render_to_string( + 'patchwork/patch-change-notification-subject.text', + context).strip() + content = render_to_string('patchwork/patch-change-notification.mail', + context) + + message = EmailMessage(subject = subject, body = content, + from_email = settings.NOTIFICATION_FROM_EMAIL, + to = [recipient.email], + headers = {'Precedence': 'bulk'}) + + try: + message.send() + except ex: + errors.append((recipient, ex)) + continue + + delete_notifications() + + return errors + +def do_expiry(): + # expire any pending confirmations + q = (Q(date__lt = datetime.datetime.now() - EmailConfirmation.validity) | + Q(active = False)) + EmailConfirmation.objects.filter(q).delete() + + # expire inactive users with no pending confirmation + pending_confs = EmailConfirmation.objects.values('user') + users = User.objects.filter( + is_active = False, + last_login = F('date_joined') + ).exclude( + id__in = pending_confs + ) + + # delete users + users.delete() + + + |