~linuxgoose/bocpress

ref: e78dbbcef5121b4be449c26cca34d26410323fc6 bocpress/main/management/commands/processnotifications.py -rw-r--r-- 5.6 KiB
e78dbbceJordan Robinson update wording on methodology page 2 months ago
                                                                                
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
from datetime import timedelta

from django.conf import settings
from django.core import mail
from django.core.management.base import BaseCommand
from django.utils import timezone

from main import models, util


def get_mail_connection():
    if settings.DEBUG:
        return mail.get_connection("django.core.mail.backends.console.EmailBackend")

    # SMPT EmailBackend instantiated with the broadcast-specific email host
    return mail.get_connection(
        "django.core.mail.backends.smtp.EmailBackend",
        host=settings.EMAIL_HOST_BROADCASTS,
    )


def get_email_body(post, notification):
    """Returns the email body (which contains the post body) along with titles and links."""
    post_url = util.get_protocol() + post.get_proper_url()
    unsubscribe_url = util.get_protocol() + notification.get_unsubscribe_url()
    blog_title = post.owner.blog_title or post.owner.username

    body = f"""{blog_title} has published:

# {post.title}

{post_url}

{post.body}

---

Blog post URL:
{post_url}

---

Unsubscribe:
{unsubscribe_url}
"""
    return body


def get_email(post, notification):
    """Returns the email object, containing all info needed to be sent."""

    blog_title = post.owner.username
    # email sender name cannot contain commas
    if post.owner.blog_title and "," not in post.owner.blog_title:
        blog_title = post.owner.blog_title

    unsubscribe_url = util.get_protocol() + notification.get_unsubscribe_url()
    body = get_email_body(post, notification)
    email = mail.EmailMessage(
        subject=post.title,
        body=body,
        from_email=f"{blog_title} <{post.owner.username}@{settings.EMAIL_FROM_HOST}>",
        to=[notification.email],
        headers={
            "X-PM-Message-Stream": "newsletters",
            "List-Unsubscribe": unsubscribe_url,
            "List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
        },
    )
    return email


class Command(BaseCommand):
    help = "Process new posts and send email to subscribers"

    def add_arguments(self, parser):
        parser.add_argument(
            "--no-dryrun",
            action="store_false",
            dest="dryrun",
            help="No dry run. Send actual emails.",
        )
        parser.set_defaults(dryrun=True)

    def handle(self, *args, **options):
        self.stdout.write(self.style.NOTICE("Processing notifications."))

        yesterday = timezone.now().date() - timedelta(days=1)
        post_list = models.Post.objects.filter(
            owner__notifications_on=True,
            broadcasted_at__isnull=True,
            published_at=yesterday,
        )
        self.stdout.write(self.style.NOTICE(f"Post count to process: {len(post_list)}"))

        count_sent = 0
        connection = get_mail_connection()

        # for all posts that were published yesterday
        for post in post_list:
            # assume no notification will fail
            no_send_failures = True

            notification_list = models.Notification.objects.filter(
                blog_user=post.owner,
                is_active=True,
            )
            msg = (
                f"Subscriber count for: '{post.title}' (author: {post.owner.username})"
                f" is {len(notification_list)}."
            )
            self.stdout.write(self.style.NOTICE(msg))
            # for every email address subcribed to the post's blog owner
            for notification in notification_list:
                # don't send if dry run mode
                if options["dryrun"]:
                    msg = f"Would otherwise sent: '{post.title}' for '{notification.email}'."
                    self.stdout.write(self.style.NOTICE(msg))
                    continue

                # log record
                record, created = models.NotificationRecord.objects.get_or_create(
                    notification=notification,
                    post=post,
                )
                # check if this post id has already been sent to this email
                # could be because the published_at date has been changed
                if created:
                    # keep count of all emails of this run
                    count_sent += 1

                    # sent out email
                    email = get_email(post, notification)
                    try:
                        connection.send_messages([email])
                    except Exception as ex:
                        no_send_failures = False
                        msg = f"Failed to send '{post.title}' to {notification.email}."
                        self.stdout.write(self.style.ERROR(msg))
                        record.delete()
                        self.stdout.write(self.style.ERROR(ex))

                    msg = f"Email sent for '{post.title}' to '{notification.email}'."
                    self.stdout.write(self.style.SUCCESS(msg))
                else:
                    msg = (
                        f"No email sent for '{post.title}' to '{notification.email}'. "
                        f"Email was sent {record.sent_at}"
                    )
                    self.stdout.write(self.style.NOTICE(msg))

            # broadcast for this post done
            if not options["dryrun"] and no_send_failures:
                post.broadcasted_at = timezone.now()
                post.save()

        # broadcast for all posts done
        connection.close()

        # return if send mode is off
        if options["dryrun"]:
            self.stdout.write(
                self.style.SUCCESS("Broadcast dry run done. No emails were sent.")
            )
            return

        self.stdout.write(
            self.style.SUCCESS(f"Broadcast sent. Total {count_sent} emails.")
        )