mirror of
https://github.com/healthchecks/healthchecks.git
synced 2025-04-17 18:22:33 +00:00
Update the senddeletionscheduled command to notify team members too
This commit is contained in:
parent
c4851f3b49
commit
05742f42f9
4 changed files with 73 additions and 32 deletions
hc/accounts
templates/emails
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
|
@ -16,23 +17,35 @@ class Command(BaseCommand):
|
||||||
def pause(self):
|
def pause(self):
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
def members(self, user):
|
||||||
|
q = User.objects.filter(memberships__project__owner=user)
|
||||||
|
q = q.exclude(last_login=None)
|
||||||
|
return q.order_by("email")
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
q = Profile.objects.order_by("id")
|
q = Profile.objects.order_by("id")
|
||||||
q = q.filter(deletion_scheduled_date__gt=now())
|
q = q.filter(deletion_scheduled_date__gt=now())
|
||||||
|
|
||||||
sent = 0
|
sent = 0
|
||||||
for profile in q:
|
for profile in q:
|
||||||
self.stdout.write(f"Sending notice to {profile.user.email}")
|
recipients = [profile.user.email]
|
||||||
|
# Include team members in the recipient list too:
|
||||||
|
for u in self.members(profile.user):
|
||||||
|
recipients.append(u.email)
|
||||||
|
|
||||||
ctx = {
|
for recipient in recipients:
|
||||||
"email": profile.user.email,
|
role = "owner" if recipient == profile.user.email else "member"
|
||||||
"support_email": settings.SUPPORT_EMAIL,
|
self.stdout.write(f"Sending notice to {recipient} ({role})")
|
||||||
"deletion_scheduled_date": profile.deletion_scheduled_date,
|
ctx = {
|
||||||
}
|
"owner_email": profile.user.email,
|
||||||
emails.deletion_scheduled(profile.user.email, ctx)
|
"num_checks": profile.num_checks_used(),
|
||||||
sent += 1
|
"support_email": settings.SUPPORT_EMAIL,
|
||||||
|
"deletion_scheduled_date": profile.deletion_scheduled_date,
|
||||||
|
}
|
||||||
|
emails.deletion_scheduled(recipient, ctx)
|
||||||
|
sent += 1
|
||||||
|
|
||||||
# Throttle so we don't send too many emails at once:
|
# Throttle so we don't send too many emails at once:
|
||||||
self.pause()
|
self.pause()
|
||||||
|
|
||||||
return f"Done!\nNotices sent: {sent}\n"
|
return f"Done!\nNotices sent: {sent}\n"
|
||||||
|
|
|
@ -8,6 +8,7 @@ from django.core import mail
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
from hc.accounts.management.commands.senddeletionscheduled import Command
|
from hc.accounts.management.commands.senddeletionscheduled import Command
|
||||||
|
from hc.api.models import Check
|
||||||
from hc.test import BaseTestCase
|
from hc.test import BaseTestCase
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,19 +17,14 @@ def counts(result):
|
||||||
return [int(s) for s in re.findall(r"\d+", result)]
|
return [int(s) for s in re.findall(r"\d+", result)]
|
||||||
|
|
||||||
|
|
||||||
class SendDeletionNoticesTestCase(BaseTestCase):
|
class SendDeletionScheduledTestCase(BaseTestCase):
|
||||||
def test_it_skips_profiles_with_deletion_scheduled_date_not_set(self):
|
|
||||||
cmd = Command(stdout=Mock())
|
|
||||||
cmd.pause = Mock() # don't pause for 1s
|
|
||||||
|
|
||||||
result = cmd.handle()
|
|
||||||
self.assertEqual(counts(result), [0])
|
|
||||||
self.assertEqual(len(mail.outbox), 0)
|
|
||||||
|
|
||||||
def test_it_sends_notice(self):
|
def test_it_sends_notice(self):
|
||||||
self.profile.deletion_scheduled_date = now() + td(days=31)
|
self.profile.deletion_scheduled_date = now() + td(days=31)
|
||||||
self.profile.save()
|
self.profile.save()
|
||||||
|
|
||||||
|
Check.objects.create(project=self.project)
|
||||||
|
Check.objects.create(project=self.project)
|
||||||
|
|
||||||
cmd = Command(stdout=Mock())
|
cmd = Command(stdout=Mock())
|
||||||
cmd.pause = Mock() # don't pause for 1s
|
cmd.pause = Mock() # don't pause for 1s
|
||||||
|
|
||||||
|
@ -37,6 +33,33 @@ class SendDeletionNoticesTestCase(BaseTestCase):
|
||||||
|
|
||||||
email = mail.outbox[0]
|
email = mail.outbox[0]
|
||||||
self.assertEqual(email.subject, "Account Deletion Warning")
|
self.assertEqual(email.subject, "Account Deletion Warning")
|
||||||
|
self.assertEqual(email.to[0], "alice@example.org")
|
||||||
|
self.assertIn("Owner: alice@example.org", email.body)
|
||||||
|
self.assertIn("Number of checks in the account: 2", email.body)
|
||||||
|
|
||||||
|
def test_it_sends_notice_to_team_members(self):
|
||||||
|
self.profile.deletion_scheduled_date = now() + td(days=31)
|
||||||
|
self.profile.save()
|
||||||
|
|
||||||
|
self.bob.last_login = now()
|
||||||
|
self.bob.save()
|
||||||
|
|
||||||
|
cmd = Command(stdout=Mock())
|
||||||
|
cmd.pause = Mock() # don't pause for 1s
|
||||||
|
|
||||||
|
result = cmd.handle()
|
||||||
|
self.assertEqual(counts(result), [2])
|
||||||
|
|
||||||
|
self.assertEqual(mail.outbox[0].to[0], "alice@example.org")
|
||||||
|
self.assertEqual(mail.outbox[1].to[0], "bob@example.org")
|
||||||
|
|
||||||
|
def test_it_skips_profiles_with_deletion_scheduled_date_not_set(self):
|
||||||
|
cmd = Command(stdout=Mock())
|
||||||
|
cmd.pause = Mock() # don't pause for 1s
|
||||||
|
|
||||||
|
result = cmd.handle()
|
||||||
|
self.assertEqual(counts(result), [0])
|
||||||
|
self.assertEqual(len(mail.outbox), 0)
|
||||||
|
|
||||||
def test_it_skips_profiles_with_deletion_scheduled_date_in_past(self):
|
def test_it_skips_profiles_with_deletion_scheduled_date_in_past(self):
|
||||||
self.profile.deletion_scheduled_date = now() - td(minutes=1)
|
self.profile.deletion_scheduled_date = now() - td(minutes=1)
|
||||||
|
|
|
@ -2,16 +2,17 @@
|
||||||
{% load hc_extras %}
|
{% load hc_extras %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
Hello,<br /><br />
|
Hello,<br />
|
||||||
|
<br />
|
||||||
Your {% site_name %} account is
|
The {% site_name %} account registered to <strong>{{ owner_email }}</strong>
|
||||||
<strong>scheduled for deletion on {{ deletion_scheduled_date.date }}</strong>.
|
is scheduled for deletion on <strong>{{ deletion_scheduled_date.date }}</strong>.<br />
|
||||||
|
<br />
|
||||||
<br /><br />
|
Account details:<br />
|
||||||
If you wish to keep your account, please contact us at {{ support_email}} as
|
Owner: <strong>{{ owner_email }}</strong><br />
|
||||||
soon as possible.
|
Number of checks in the account: <strong>{{ num_checks }}</strong><br />
|
||||||
|
<br />
|
||||||
<br /><br />
|
To prevent deletion, please contact us at {{ support_email}} as soon as possible.<br />
|
||||||
|
<br />
|
||||||
Sincerely,<br />
|
Sincerely,<br />
|
||||||
The {% site_name %} Team
|
The {% site_name %} Team
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
{% load hc_extras %}
|
{% load hc_extras %}
|
||||||
Hello,
|
Hello,
|
||||||
|
|
||||||
Your {% site_name %} account is scheduled for deletion on {{ deletion_scheduled_date.date }}.
|
The {% site_name %} account registered to {{ owner_email }} is scheduled for deletion
|
||||||
|
on {{ deletion_scheduled_date.date }}.
|
||||||
|
|
||||||
If you wish to keep your account, please contact us at {{ support_email}} as
|
Account details:
|
||||||
soon as possible.
|
Owner: {{ owner_email }}
|
||||||
|
Number of checks in the account: {{ num_checks }}
|
||||||
|
|
||||||
|
To prevent deletion, please contact us at {{ support_email}} as soon as possible.
|
||||||
|
|
||||||
--
|
--
|
||||||
Sincerely,
|
Sincerely,
|
||||||
|
|
Loading…
Add table
Reference in a new issue