0
0
Fork 0
mirror of https://github.com/healthchecks/healthchecks.git synced 2025-04-03 04:15:29 +00:00

Add management command for sending "scheduled for deletion" warnings

This commit is contained in:
Pēteris Caune 2023-07-04 12:50:50 +03:00
parent 9304536131
commit c69c1f5ec4
No known key found for this signature in database
GPG key ID: E28D7679E9A9EDE2
8 changed files with 141 additions and 6 deletions

View file

@ -1,5 +1,7 @@
from __future__ import annotations
from datetime import timedelta as td
from django.contrib import admin
from django.contrib.auth import login as auth_login
from django.contrib.auth.admin import UserAdmin
@ -55,7 +57,6 @@ class ProfileFieldset(Fieldset):
"next_report_date",
"nag_period",
"next_nag_date",
"deletion_notice_date",
"token",
"sort",
)
@ -76,6 +77,14 @@ class TeamFieldset(Fieldset):
)
class DeletionFieldset(Fieldset):
name = "Deletion"
fields = (
"deletion_notice_date",
"deletion_scheduled_date",
)
class NumChecksFilter(admin.SimpleListFilter):
title = "check count"
@ -133,11 +142,15 @@ class ProfileAdmin(admin.ModelAdmin):
"send_report",
"send_nag",
"remove_totp",
"mark_for_deletion",
"mark_for_deletion_in_month",
"unmark_for_deletion",
)
fieldsets = (ProfileFieldset.tuple(), TeamFieldset.tuple())
fieldsets = (
ProfileFieldset.tuple(),
TeamFieldset.tuple(),
DeletionFieldset.tuple(),
)
def get_queryset(self, 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())
def mark_for_deletion(self, request, qs):
qs.update(deletion_scheduled_date=now())
def mark_for_deletion_in_month(self, request, qs):
qs.update(deletion_scheduled_date=now() + td(days=31))
self.message_user(request, "%d user(s) marked for deletion" % qs.count())
def unmark_for_deletion(self, request, qs):

View 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"

View 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)

View file

@ -104,6 +104,11 @@ def deletion_notice(to, ctx, headers={}):
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):
send(make_message("sms-limit", to, ctx))

View file

@ -66,7 +66,7 @@
{% debug_warning %}
{% if request.user.is_authenticated and request.profile.deletion_scheduled_date %}
<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.
</div>
{% endif %}

View 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 %}

View 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

View file

@ -0,0 +1 @@
Account Deletion Warning