mirror of
https://github.com/healthchecks/healthchecks.git
synced 2025-04-06 21:58:48 +00:00
Add management command for sending "scheduled for deletion" warnings
This commit is contained in:
parent
9304536131
commit
c69c1f5ec4
8 changed files with 141 additions and 6 deletions
hc
accounts
lib
templates
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta as td
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth import login as auth_login
|
from django.contrib.auth import login as auth_login
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
|
@ -55,7 +57,6 @@ class ProfileFieldset(Fieldset):
|
||||||
"next_report_date",
|
"next_report_date",
|
||||||
"nag_period",
|
"nag_period",
|
||||||
"next_nag_date",
|
"next_nag_date",
|
||||||
"deletion_notice_date",
|
|
||||||
"token",
|
"token",
|
||||||
"sort",
|
"sort",
|
||||||
)
|
)
|
||||||
|
@ -76,6 +77,14 @@ class TeamFieldset(Fieldset):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DeletionFieldset(Fieldset):
|
||||||
|
name = "Deletion"
|
||||||
|
fields = (
|
||||||
|
"deletion_notice_date",
|
||||||
|
"deletion_scheduled_date",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NumChecksFilter(admin.SimpleListFilter):
|
class NumChecksFilter(admin.SimpleListFilter):
|
||||||
title = "check count"
|
title = "check count"
|
||||||
|
|
||||||
|
@ -133,11 +142,15 @@ class ProfileAdmin(admin.ModelAdmin):
|
||||||
"send_report",
|
"send_report",
|
||||||
"send_nag",
|
"send_nag",
|
||||||
"remove_totp",
|
"remove_totp",
|
||||||
"mark_for_deletion",
|
"mark_for_deletion_in_month",
|
||||||
"unmark_for_deletion",
|
"unmark_for_deletion",
|
||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (ProfileFieldset.tuple(), TeamFieldset.tuple())
|
fieldsets = (
|
||||||
|
ProfileFieldset.tuple(),
|
||||||
|
TeamFieldset.tuple(),
|
||||||
|
DeletionFieldset.tuple(),
|
||||||
|
)
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
qs = super(ProfileAdmin, self).get_queryset(request)
|
qs = super(ProfileAdmin, self).get_queryset(request)
|
||||||
|
@ -201,8 +214,8 @@ class ProfileAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
self.message_user(request, "Removed TOTP for %d profile(s)" % qs.count())
|
self.message_user(request, "Removed TOTP for %d profile(s)" % qs.count())
|
||||||
|
|
||||||
def mark_for_deletion(self, request, qs):
|
def mark_for_deletion_in_month(self, request, qs):
|
||||||
qs.update(deletion_scheduled_date=now())
|
qs.update(deletion_scheduled_date=now() + td(days=31))
|
||||||
self.message_user(request, "%d user(s) marked for deletion" % qs.count())
|
self.message_user(request, "%d user(s) marked for deletion" % qs.count())
|
||||||
|
|
||||||
def unmark_for_deletion(self, request, qs):
|
def unmark_for_deletion(self, request, qs):
|
||||||
|
|
38
hc/accounts/management/commands/senddeletionscheduled.py
Normal file
38
hc/accounts/management/commands/senddeletionscheduled.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.utils.timezone import now
|
||||||
|
|
||||||
|
from hc.accounts.models import Profile
|
||||||
|
from hc.lib import emails
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = """Send warnings to accounts marked for deletion. """
|
||||||
|
|
||||||
|
def pause(self):
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
q = Profile.objects.order_by("id")
|
||||||
|
q = q.filter(deletion_scheduled_date__gt=now())
|
||||||
|
|
||||||
|
sent = 0
|
||||||
|
for profile in q:
|
||||||
|
self.stdout.write(f"Sending notice to {profile.user.email}")
|
||||||
|
|
||||||
|
ctx = {
|
||||||
|
"email": profile.user.email,
|
||||||
|
"support_email": settings.SUPPORT_EMAIL,
|
||||||
|
"deletion_scheduled_date": profile.deletion_scheduled_date,
|
||||||
|
}
|
||||||
|
emails.deletion_scheduled(profile.user.email, ctx)
|
||||||
|
sent += 1
|
||||||
|
|
||||||
|
# Throttle so we don't send too many emails at once:
|
||||||
|
self.pause()
|
||||||
|
|
||||||
|
return f"Done!\nNotices sent: {sent}\n"
|
50
hc/accounts/tests/test_senddeletionscheduled.py
Normal file
50
hc/accounts/tests/test_senddeletionscheduled.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from datetime import timedelta as td
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
from django.core import mail
|
||||||
|
from django.utils.timezone import now
|
||||||
|
|
||||||
|
from hc.accounts.management.commands.senddeletionscheduled import Command
|
||||||
|
from hc.test import BaseTestCase
|
||||||
|
|
||||||
|
|
||||||
|
def counts(result):
|
||||||
|
"""Extract integer values from command's return value."""
|
||||||
|
return [int(s) for s in re.findall(r"\d+", result)]
|
||||||
|
|
||||||
|
|
||||||
|
class SendDeletionNoticesTestCase(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):
|
||||||
|
self.profile.deletion_scheduled_date = now() + td(days=31)
|
||||||
|
self.profile.save()
|
||||||
|
|
||||||
|
cmd = Command(stdout=Mock())
|
||||||
|
cmd.pause = Mock() # don't pause for 1s
|
||||||
|
|
||||||
|
result = cmd.handle()
|
||||||
|
self.assertEqual(counts(result), [1])
|
||||||
|
|
||||||
|
email = mail.outbox[0]
|
||||||
|
self.assertEqual(email.subject, "Account Deletion Warning")
|
||||||
|
|
||||||
|
def test_it_skips_profiles_with_deletion_scheduled_date_in_past(self):
|
||||||
|
self.profile.deletion_scheduled_date = now() - td(minutes=1)
|
||||||
|
self.profile.save()
|
||||||
|
|
||||||
|
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)
|
|
@ -104,6 +104,11 @@ def deletion_notice(to, ctx, headers={}):
|
||||||
send(m, block=True)
|
send(m, block=True)
|
||||||
|
|
||||||
|
|
||||||
|
def deletion_scheduled(to, ctx, headers={}):
|
||||||
|
m = make_message("deletion-scheduled", to, ctx, headers=headers)
|
||||||
|
send(m, block=True)
|
||||||
|
|
||||||
|
|
||||||
def sms_limit(to, ctx):
|
def sms_limit(to, ctx):
|
||||||
send(make_message("sms-limit", to, ctx))
|
send(make_message("sms-limit", to, ctx))
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
{% debug_warning %}
|
{% debug_warning %}
|
||||||
{% if request.user.is_authenticated and request.profile.deletion_scheduled_date %}
|
{% if request.user.is_authenticated and request.profile.deletion_scheduled_date %}
|
||||||
<div id="account-warning">
|
<div id="account-warning">
|
||||||
Warning: Your account is scheduled for deletion.
|
Warning: Your account is scheduled for deletion on {{ request.profile.deletion_scheduled_date.date }}.
|
||||||
<a href="mailto:{% support_email %}">Contact support</a> to resolve.
|
<a href="mailto:{% support_email %}">Contact support</a> to resolve.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
17
templates/emails/deletion-scheduled-body-html.html
Normal file
17
templates/emails/deletion-scheduled-body-html.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{% extends "emails/base.html" %}
|
||||||
|
{% load hc_extras %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
Hello,<br /><br />
|
||||||
|
|
||||||
|
Your {% site_name %} account is
|
||||||
|
<strong>scheduled for deletion on {{ deletion_scheduled_date.date }}</strong>.
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
If you wish to keep your account, please contact us at {{ support_email}} as
|
||||||
|
soon as possible.
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
Sincerely,<br />
|
||||||
|
The {% site_name %} Team
|
||||||
|
{% endblock %}
|
11
templates/emails/deletion-scheduled-body-text.html
Normal file
11
templates/emails/deletion-scheduled-body-text.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{% load hc_extras %}
|
||||||
|
Hello,
|
||||||
|
|
||||||
|
Your {% site_name %} account is scheduled for deletion on {{ deletion_scheduled_date.date }}.
|
||||||
|
|
||||||
|
If you wish to keep your account, please contact us at {{ support_email}} as
|
||||||
|
soon as possible.
|
||||||
|
|
||||||
|
--
|
||||||
|
Sincerely,
|
||||||
|
The {% site_name %} Team
|
1
templates/emails/deletion-scheduled-subject.html
Normal file
1
templates/emails/deletion-scheduled-subject.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Account Deletion Warning
|
Loading…
Add table
Reference in a new issue