+++ /dev/null
-# 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()
-
-
-