Skip to content

Commit 0853ea1

Browse files
authored
Notification: expand management command to follow conventions (#10470)
* Notification: expand management command to follow conventions - Follow the convention we have in other notifications regarind the context: use `production_uri` instead of `domain - Use the internal `get_context_data()` and `extra_context=` to follow the pattern stablished for the `Notification` class. - Make the new linter to pass. - Use structlog instead of `print()` function * Log (debug) rendered notification
1 parent b219727 commit 0853ea1

File tree

2 files changed

+80
-84
lines changed

2 files changed

+80
-84
lines changed

readthedocs/core/management/commands/contact_owners.py

+59-60
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import sys
22
from pathlib import Path
3-
from pprint import pprint
43

54
import structlog
65
from django.conf import settings
@@ -49,15 +48,18 @@ class Command(BaseCommand):
4948
to be included in the notification.
5049
* ``email.md`` is a Markdown file with the first line as the subject,
5150
and the rest is the content.
52-
Note that ``user`` and ``domain`` are available in the context.
51+
52+
The context available is:
53+
* ``user``
54+
* ``production_uri``
5355
5456
.. code:: markdown
5557
5658
Read the Docs deprecated option, action required
5759
5860
Dear {{ user.firstname }},
5961
60-
Greetings from [Read the Docs]({{ domain }}).
62+
Greetings from [Read the Docs]({{ production_uri }}).
6163
6264
.. note::
6365
@@ -77,30 +79,30 @@ class Command(BaseCommand):
7779

7880
def add_arguments(self, parser):
7981
parser.add_argument(
80-
'--production',
81-
action='store_true',
82-
dest='production',
82+
"--production",
83+
action="store_true",
84+
dest="production",
8385
default=False,
8486
help=(
85-
'Send the email/notification for real, '
86-
'otherwise we only print the notification in the console (dryrun).'
87-
)
87+
"Send the email/notification for real, "
88+
"otherwise we only logs the notification in the console (dryrun)."
89+
),
8890
)
8991
parser.add_argument(
90-
'--email',
92+
"--email",
9193
help=(
92-
'Path to a file with the email content in markdown. '
93-
'The first line would be the subject.'
94+
"Path to a file with the email content in markdown. "
95+
"The first line would be the subject."
9496
),
9597
)
9698
parser.add_argument(
97-
'--notification',
98-
help='Path to a file with the notification content in markdown.',
99+
"--notification",
100+
help="Path to a file with the notification content in markdown.",
99101
)
100102
parser.add_argument(
101-
'--sticky',
102-
action='store_true',
103-
dest='sticky',
103+
"--sticky",
104+
action="store_true",
105+
dest="sticky",
104106
default=False,
105107
help=(
106108
'Make the notification sticky '
@@ -143,7 +145,7 @@ def handle(self, *args, **options):
143145
users = AdminPermission.owners(organization)
144146
elif usernames:
145147
file = Path(usernames)
146-
with file.open() as f:
148+
with file.open(encoding="utf8") as f:
147149
usernames = f.readlines()
148150

149151
# remove "\n" from lines
@@ -155,62 +157,59 @@ def handle(self, *args, **options):
155157
organizationowner__organization__disabled=False
156158
).distinct()
157159
else:
158-
users = (
159-
User.objects
160-
.filter(projects__skip=False)
161-
.distinct()
162-
)
163-
164-
print(
165-
"len(owners)={} production={} email={} notification={} sticky={}".format(
166-
users.count(),
167-
bool(options["production"]),
168-
options["email"],
169-
options["notification"],
170-
options["sticky"],
171-
)
160+
users = User.objects.filter(projects__skip=False).distinct()
161+
162+
log.info(
163+
"Command arguments.",
164+
n_owners=users.count(),
165+
production=bool(options["production"]),
166+
email_filepath=options["email"],
167+
notification_filepath=options["notification"],
168+
sticky=options["sticky"],
172169
)
173170

174171
if input("Continue? y/N: ") != "y":
175172
print("Aborting run.")
176173
return
177174

178-
notification_content = ''
179-
if options['notification']:
180-
file = Path(options['notification'])
181-
with file.open() as f:
175+
notification_content = ""
176+
if options["notification"]:
177+
file = Path(options["notification"])
178+
with file.open(encoding="utf8") as f:
182179
notification_content = f.read()
183180

184-
email_subject = ''
185-
email_content = ''
186-
if options['email']:
187-
file = Path(options['email'])
188-
with file.open() as f:
189-
content = f.read().split('\n')
181+
email_subject = ""
182+
email_content = ""
183+
if options["email"]:
184+
file = Path(options["email"])
185+
with file.open(encoding="utf8") as f:
186+
content = f.read().split("\n")
190187
email_subject = content[0].strip()
191-
email_content = '\n'.join(content[1:]).strip()
188+
email_content = "\n".join(content[1:]).strip()
192189

193190
resp = contact_users(
194191
users=users,
195192
email_subject=email_subject,
196193
email_content=email_content,
197194
notification_content=notification_content,
198-
sticky_notification=options['sticky'],
199-
dryrun=not options['production'],
195+
sticky_notification=options["sticky"],
196+
dryrun=not options["production"],
197+
)
198+
199+
email = resp["email"]
200+
log.info(
201+
"Sending emails finished.",
202+
total=len(email["sent"]),
203+
total_failed=len(email["failed"]),
204+
sent_emails=email["sent"],
205+
failed_emails=email["failed"],
200206
)
201207

202-
email = resp['email']
203-
total = len(email['sent'])
204-
total_failed = len(email['failed'])
205-
print(f'Emails sent ({total}):')
206-
pprint(email['sent'])
207-
print(f'Failed emails ({total_failed}):')
208-
pprint(email['failed'])
209-
210-
notification = resp['notification']
211-
total = len(notification['sent'])
212-
total_failed = len(notification['failed'])
213-
print(f'Notifications sent ({total})')
214-
pprint(notification['sent'])
215-
print(f'Failed notifications ({total_failed})')
216-
pprint(notification['failed'])
208+
notification = resp["notification"]
209+
log.info(
210+
"Sending notifications finished.",
211+
total=len(notification["sent"]),
212+
total_failed=len(notification["failed"]),
213+
sent_notifications=notification["sent"],
214+
failed_notifications=notification["failed"],
215+
)

readthedocs/core/utils/contact.py

+21-24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import structlog
2-
from pprint import pprint
32

43
import markdown
54
from django.conf import settings
@@ -33,14 +32,14 @@ def contact_users(
3332
:param string notification_content: Content for the sticky notification (markdown)
3433
:param context_function: A callable that will receive an user
3534
and return a dict of additional context to be used in the email/notification content
36-
:param bool dryrun: If `True` don't sent the email or notification, just print the content
35+
:param bool dryrun: If `True` don't sent the email or notification, just logs the content
3736
3837
The `email_content` and `notification_content` contents will be rendered using
3938
a template with the following context::
4039
4140
{
4241
'user': <user object>,
43-
'domain': https://readthedocs.org,
42+
'production_uri': https://readthedocs.org,
4443
}
4544
4645
:returns: A dictionary with a list of sent/failed emails/notifications.
@@ -67,41 +66,41 @@ class TempNotification(SiteNotification):
6766
success_level = message_constants.SUCCESS_PERSISTENT
6867

6968
def render(self, *args, **kwargs):
70-
context = {
71-
'user': self.user,
72-
'domain': f'https://{settings.PRODUCTION_DOMAIN}',
73-
}
74-
context.update(context_function(self.user))
7569
return markdown.markdown(
76-
notification_template.render(Context(context))
70+
notification_template.render(Context(self.get_context_data()))
7771
)
7872

7973
total = users.count()
8074
for count, user in enumerate(users.iterator(), start=1):
8175
context = {
82-
'user': user,
83-
'domain': f'https://{settings.PRODUCTION_DOMAIN}',
76+
"user": user,
77+
"production_uri": f"https://{settings.PRODUCTION_DOMAIN}",
8478
}
8579
context.update(context_function(user))
8680

8781
if notification_content:
8882
notification = TempNotification(
8983
user=user,
9084
success=True,
85+
extra_context=context,
9186
)
9287
try:
9388
if not dryrun:
9489
backend.send(notification)
9590
else:
96-
pprint(markdown.markdown(
97-
notification_template.render(Context(context))
98-
))
91+
# Check we can render the notification with the context properly
92+
log.debug(
93+
"Rendered notification.",
94+
notification=markdown.markdown(
95+
notification_template.render(Context(context))
96+
),
97+
)
9998
except Exception:
10099
log.exception('Notification failed to send')
101100
failed_notifications.add(user.username)
102101
else:
103102
log.info(
104-
'Successfully set notification.',
103+
"Successfully sent notification.",
105104
user_username=user.username,
106105
count=count,
107106
total=total,
@@ -132,17 +131,15 @@ def render(self, *args, **kwargs):
132131
)
133132

134133
try:
135-
kwargs = dict(
136-
subject=email_subject,
137-
message=email_txt_rendered,
138-
html_message=email_html_rendered,
139-
from_email=from_email,
140-
recipient_list=emails,
141-
)
134+
kwargs = {
135+
"subject": email_subject,
136+
"message": email_txt_rendered,
137+
"html_message": email_html_rendered,
138+
"from_email": from_email,
139+
"recipient_list": emails,
140+
}
142141
if not dryrun:
143142
send_mail(**kwargs)
144-
else:
145-
pprint(kwargs)
146143
except Exception:
147144
log.exception('Email failed to send')
148145
failed_emails.update(emails)

0 commit comments

Comments
 (0)