Skip to content

Commit 81d369e

Browse files
committed
send_welcome_emails command
Sends emails to users who have a Marker with send_welcome_email set to True. This is an improvement on the previous send_password_reset_emails as it makes it more explicit who should be getting emails, and can also send emails to already registered users, who get a slightly different email. Also enables restriction by stage and marking session as an extra safeguard Fixes #183
1 parent edac52c commit 81d369e

File tree

4 files changed

+277
-0
lines changed

4 files changed

+277
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
[
2+
{
3+
"model": "auth.user",
4+
"pk": 101,
5+
"fields": {
6+
"password": "thisisasecret",
7+
"last_login": null,
8+
"is_superuser": true,
9+
"username": "already_registered",
10+
"first_name": "",
11+
"last_name": "",
12+
"email": "already@example.org",
13+
"is_staff": false,
14+
"is_active": true,
15+
"date_joined": "2022-12-19T13:40:36.075Z",
16+
"groups": [],
17+
"user_permissions": []
18+
}
19+
},
20+
{
21+
"model": "auth.user",
22+
"pk": 102,
23+
"fields": {
24+
"password": "",
25+
"last_login": null,
26+
"is_superuser": false,
27+
"username": "new_marker",
28+
"first_name": "Anew",
29+
"last_name": "Marker",
30+
"email": "new_marker@example.org",
31+
"is_staff": false,
32+
"is_active": true,
33+
"date_joined": "2022-12-19T13:40:36.075Z",
34+
"groups": [],
35+
"user_permissions": []
36+
}
37+
},
38+
{
39+
"model": "crowdsourcer.marker",
40+
"pk": 101,
41+
"fields": {
42+
"user_id": 101,
43+
"response_type_id": 1,
44+
"authority_id": 2,
45+
"send_welcome_email": true,
46+
"marking_session": [1]
47+
}
48+
},
49+
{
50+
"model": "crowdsourcer.marker",
51+
"pk": 102,
52+
"fields": {
53+
"user_id": 102,
54+
"response_type_id": 1,
55+
"send_welcome_email": true,
56+
"marking_session": [1]
57+
}
58+
}
59+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from time import sleep
2+
3+
from django.contrib.auth.forms import PasswordResetForm
4+
from django.core.management.base import BaseCommand
5+
from django.http import HttpRequest
6+
from django.utils.crypto import get_random_string
7+
8+
from crowdsourcer.models import Marker, MarkingSession, ResponseType
9+
10+
YELLOW = "\033[33m"
11+
NOBOLD = "\033[0m"
12+
13+
14+
class Command(BaseCommand):
15+
help = "Emails password reset instructions to all users"
16+
17+
new_user_template = "registration/initial_password_email.html"
18+
previous_user_template = "registration/repeat_password_email.html"
19+
20+
def add_arguments(self, parser):
21+
parser.add_argument("--send_emails", action="store_true", help="Send emails")
22+
23+
parser.add_argument(
24+
"--stage", action="store", help="Only send emails to people in this stage"
25+
)
26+
27+
parser.add_argument(
28+
"--session",
29+
action="store",
30+
help="Only send emails to people in this session",
31+
)
32+
33+
def handle(self, *args, **kwargs):
34+
if not kwargs["send_emails"]:
35+
self.stdout.write(
36+
f"{YELLOW}Not sending emails. Call with --send_emails to send{NOBOLD}"
37+
)
38+
39+
users = Marker.objects.filter(send_welcome_email=True).select_related("user")
40+
41+
if kwargs["stage"]:
42+
try:
43+
rt = ResponseType.objects.get(type=kwargs["stage"])
44+
users = users.filter(response_type=rt)
45+
except ResponseType.NotFoundException:
46+
self.stderr.write(f"{YELLOW}No such stage: {kwargs['stage']}{NOBOLD}")
47+
return
48+
49+
if kwargs["session"]:
50+
try:
51+
rt = MarkingSession.objects.get(label=kwargs["session"])
52+
users = users.filter(marking_session=rt)
53+
except ResponseType.NotFoundException:
54+
self.stderr.write(
55+
f"{YELLOW}No such session: {kwargs['session']}{NOBOLD}"
56+
)
57+
return
58+
59+
user_count = users.count()
60+
self.stdout.write(f"Sending emails for {user_count} users")
61+
count = 0
62+
for marker in users:
63+
user = marker.user
64+
try:
65+
if user.email:
66+
self.stdout.write(f"Sending email for to this email: {user.email}")
67+
if kwargs["send_emails"]:
68+
template = self.new_user_template
69+
if user.password == "":
70+
user.set_password(get_random_string(length=20))
71+
user.save()
72+
else:
73+
template = self.previous_user_template
74+
75+
form = PasswordResetForm({"email": user.email})
76+
assert form.is_valid()
77+
request = HttpRequest()
78+
request.META["SERVER_NAME"] = (
79+
"marking.councilclimatescorecards.uk"
80+
)
81+
request.META["SERVER_PORT"] = 443
82+
form.save(
83+
request=request,
84+
domain_override="marking.councilclimatescorecards.uk",
85+
use_https=True,
86+
from_email="CEUK Scorecards Marking <climate-right-of-reply@mysociety.org>",
87+
subject_template_name="registration/initial_password_email_subject.txt",
88+
email_template_name=template,
89+
)
90+
marker.send_welcome_email = False
91+
marker.save()
92+
sleep(1)
93+
count = count + 1
94+
except Exception as e:
95+
print(e)
96+
continue
97+
98+
if kwargs["send_emails"]:
99+
self.stdout.write(f"Sent {count} emails")
100+
else:
101+
self.stdout.write(
102+
f"{YELLOW}Dry Run{NOBOLD}. Live would have sent {count} emails"
103+
)
104+
105+
return "done"

crowdsourcer/tests/test_commands.py

+91
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from unittest import skip
44

55
from django.contrib.auth.models import User
6+
from django.core import mail
67
from django.core.management import call_command
78
from django.test import TestCase
89

@@ -516,3 +517,93 @@ def test_multiple_choice_question(self):
516517
self.assertIsNotNone(r.multi_option)
517518

518519
self.assertEquals(r.multi_option.all()[0].description, "Car share")
520+
521+
522+
class SendWelcomeEmails(BaseCommandTestCase):
523+
fixtures = [
524+
"basics.json",
525+
"authorities.json",
526+
"users.json",
527+
"welcome_email_users.json",
528+
]
529+
530+
def test_basic_run(self):
531+
self.assertEquals(len(mail.outbox), 0)
532+
self.call_command(
533+
"send_welcome_emails",
534+
)
535+
self.assertEquals(len(mail.outbox), 0)
536+
537+
self.call_command(
538+
"send_welcome_emails",
539+
send_emails=True,
540+
)
541+
self.assertEquals(len(mail.outbox), 2)
542+
543+
self.call_command(
544+
"send_welcome_emails",
545+
send_emails=True,
546+
)
547+
self.assertEquals(len(mail.outbox), 2)
548+
549+
def test_only_sends_if_flag_set(self):
550+
marker = Marker.objects.get(user__email="new_marker@example.org")
551+
marker.send_welcome_email = False
552+
marker.save()
553+
554+
self.assertEquals(len(mail.outbox), 0)
555+
556+
self.call_command(
557+
"send_welcome_emails",
558+
send_emails=True,
559+
)
560+
self.assertEquals(len(mail.outbox), 1)
561+
email = mail.outbox[0]
562+
self.assertEquals(email.to, ["already@example.org"])
563+
564+
def test_email_comtent(self):
565+
self.call_command(
566+
"send_welcome_emails",
567+
send_emails=True,
568+
)
569+
570+
emails = mail.outbox
571+
572+
text = "your previous password"
573+
for m in emails:
574+
if m.to[0] == "already@example.org":
575+
self.assertTrue(m.body.rfind(text) >= 0)
576+
else:
577+
self.assertTrue(m.body.rfind(text) == -1)
578+
579+
def test_limit_stage(self):
580+
self.assertEquals(len(mail.outbox), 0)
581+
self.call_command(
582+
"send_welcome_emails",
583+
send_emails=True,
584+
stage="Audit",
585+
)
586+
self.assertEquals(len(mail.outbox), 0)
587+
588+
self.call_command(
589+
"send_welcome_emails",
590+
send_emails=True,
591+
stage="First Mark",
592+
)
593+
self.assertEquals(len(mail.outbox), 2)
594+
595+
def test_limit_session(self):
596+
self.assertEquals(len(mail.outbox), 0)
597+
self.call_command(
598+
"send_welcome_emails",
599+
send_emails=True,
600+
session="Second Session",
601+
)
602+
self.assertEquals(len(mail.outbox), 0)
603+
604+
self.call_command(
605+
"send_welcome_emails",
606+
send_emails=True,
607+
session="Default",
608+
)
609+
self.assertEquals(len(mail.outbox), 2)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{% autoescape off %}
2+
Hi,
3+
4+
You're receiving this email because you registered to volunteer for the CEUK Council Climate Scorecards. This email allows you to log into the online data collection system that we will use to score Councils.
5+
6+
If you can remember your previous password from the 2023 Scorecards then you can log in with your username at
7+
8+
{{ protocol }}://{{ domain }}
9+
10+
If you cannot remember your password then please go to the following page and choose a new password:
11+
{% block reset_link %}
12+
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
13+
{% endblock %}
14+
15+
Your username is {{ user.get_username }}
16+
17+
Thanks for volunteering!
18+
19+
The CEUK team.
20+
21+
{% endautoescape %}
22+

0 commit comments

Comments
 (0)