summaryrefslogtreecommitdiff
path: root/patchwork/notifications.py
blob: 111feb2dc415b900b7a9a37729a417504e9b5577 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# Patchwork - automated patch tracking system
# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
#
# SPDX-License-Identifier: GPL-2.0-or-later

import datetime
import itertools
import smtplib

from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.core.mail import EmailMessage
from django.db.models import Count
from django.db.models import Q
from django.template.loader import render_to_string

from patchwork.models import EmailConfirmation
from patchwork.models import EmailOptout
from patchwork.models import PatchChangeNotification


def send_notifications():
    date_limit = datetime.datetime.utcnow() - datetime.timedelta(
        minutes=settings.NOTIFICATION_DELAY_MINUTES)

    # We delay sending notifications to a user if they have other
    # notifications that are still in the "pending" state. To do this,
    # we compare the total number of patch change notifications queued
    # for each user against the number of "ready" notifications.
    qs = PatchChangeNotification.objects.all()
    qs2 = PatchChangeNotification.objects\
        .filter(last_modified__lt=date_limit)\
        .values('patch__submitter')\
        .annotate(count=Count('patch__submitter'))
    qs2 = {elem['patch__submitter']: elem['count'] for elem in qs2}

    groups = itertools.groupby(qs.order_by('patch__submitter'),
                               lambda n: n.patch.submitter)

    errors = []

    for (recipient, notifications) in groups:
        notifications = list(notifications)

        if recipient.id not in qs2 or qs2[recipient.id] < len(notifications):
            continue

        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(),
            'notifications': notifications,
            'projects': projects,
        }

        subject = render_to_string(
            'patchwork/mails/patch-change-notification-subject.txt',
            context).strip()
        content = render_to_string(
            'patchwork/mails/patch-change-notification.txt',
            context)

        message = EmailMessage(subject=subject, body=content,
                               from_email=settings.NOTIFICATION_FROM_EMAIL,
                               to=[recipient.email],
                               headers={'Precedence': 'bulk'})

        try:
            message.send()
        except smtplib.SMTPException as ex:
            errors.append((recipient, ex))
            continue

        delete_notifications()

    return errors


def expire_notifications():
    """Expire any pending confirmations.

    Users whose registration confirmation has expired are removed.
    """
    # expire any invalid confirmations
    q = (Q(date__lt=datetime.datetime.utcnow() - EmailConfirmation.validity) |
         Q(active=False))
    EmailConfirmation.objects.filter(q).delete()

    # remove inactive users with no pending confirmation
    pending_confs = (EmailConfirmation.objects
                     .filter(user__isnull=False).values('user'))
    users = User.objects.filter(is_active=False).exclude(id__in=pending_confs)

    # delete users
    users.delete()