Browse Source

Management command for sending inactive account notifications

pull/233/head
Pēteris Caune 6 years ago
parent
commit
945a66ab0a
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
12 changed files with 161 additions and 5 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +1
    -0
      hc/accounts/admin.py
  3. +19
    -5
      hc/accounts/management/commands/pruneusers.py
  4. +63
    -0
      hc/accounts/management/commands/senddeletionnotices.py
  5. +18
    -0
      hc/accounts/migrations/0027_profile_deletion_notice_date.py
  6. +1
    -0
      hc/accounts/models.py
  7. +18
    -0
      hc/api/migrations/0058_auto_20190312_1716.py
  8. +4
    -0
      hc/lib/emails.py
  9. +1
    -0
      hc/settings.py
  10. +20
    -0
      templates/emails/deletion-notice-body-html.html
  11. +14
    -0
      templates/emails/deletion-notice-body-text.html
  12. +1
    -0
      templates/emails/deletion-notice-subject.html

+ 1
- 0
CHANGELOG.md View File

@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
- Add maxlength attribute to HTML input=text elements - Add maxlength attribute to HTML input=text elements
- Improved logic for displaying job execution times in log (#219) - Improved logic for displaying job execution times in log (#219)
- Add Matrix integration - Add Matrix integration
- Add a management command for sending inactive account notifications
### Bug Fixes ### Bug Fixes
- Fix refreshing of the checks page filtered by tags (#221) - Fix refreshing of the checks page filtered by tags (#221)


+ 1
- 0
hc/accounts/admin.py View File

@ -22,6 +22,7 @@ class ProfileFieldset(Fieldset):
name = "User Profile" name = "User Profile"
fields = ("email", "current_project", "reports_allowed", fields = ("email", "current_project", "reports_allowed",
"next_report_date", "nag_period", "next_nag_date", "next_report_date", "nag_period", "next_nag_date",
"deletion_notice_date",
"token", "sort") "token", "sort")


+ 19
- 5
hc/accounts/management/commands/pruneusers.py View File

@ -2,8 +2,9 @@ from datetime import timedelta
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.db.models import Count
from django.utils import timezone
from django.db.models import Count, F
from django.utils.timezone import now
from hc.accounts.models import Profile
class Command(BaseCommand): class Command(BaseCommand):
@ -18,12 +19,25 @@ class Command(BaseCommand):
""" """
def handle(self, *args, **options): def handle(self, *args, **options):
cutoff = timezone.now() - timedelta(days=30)
month_ago = now() - timedelta(days=30)
# Old accounts, never logged in, no team memberships # Old accounts, never logged in, no team memberships
q = User.objects.order_by("id") q = User.objects.order_by("id")
q = q.annotate(n_teams=Count("memberships")) q = q.annotate(n_teams=Count("memberships"))
q = q.filter(date_joined__lt=cutoff, last_login=None, n_teams=0)
q = q.filter(date_joined__lt=month_ago, last_login=None, n_teams=0)
n, summary = q.delete() n, summary = q.delete()
return "Done! Pruned %d user accounts." % summary.get("auth.User", 0)
count = summary.get("auth.User", 0)
self.stdout.write("Pruned %d never-logged-in user accounts." % count)
# Profiles scheduled for deletion
q = Profile.objects.order_by("id")
q = q.filter(deletion_notice_date__lt=month_ago)
# Exclude users who have logged in after receiving deletion notice
q = q.exclude(user__last_login__gt=F("deletion_notice_date"))
for profile in q:
self.stdout.write("Deleting inactive %s" % profile.user.email)
profile.user.delete()
return "Done!"

+ 63
- 0
hc/accounts/management/commands/senddeletionnotices.py View File

@ -0,0 +1,63 @@
from datetime import timedelta
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, Member
from hc.api.models import Ping
from hc.lib import emails
class Command(BaseCommand):
help = """Send deletion notices to inactive user accounts.
Conditions for sending the notice:
- deletion notice has not been sent recently
- last login more than a year ago
- none of the owned projects has invited team members
"""
def handle(self, *args, **options):
year_ago = now() - timedelta(days=365)
q = Profile.objects.order_by("id")
# Exclude accounts with logins in the last year_ago
q = q.exclude(user__last_login__gt=year_ago)
# Exclude accounts less than a year_ago old
q = q.exclude(user__date_joined__gt=year_ago)
# Exclude accounts with the deletion notice already sent
q = q.exclude(deletion_notice_date__gt=year_ago)
# Exclude paid accounts
q = q.exclude(sms_limit__gt=0)
sent = 0
for profile in q:
members = Member.objects.filter(project__owner_id=profile.user_id)
if members.exists():
print("Skipping %s, has team members" % profile)
continue
pings = Ping.objects
pings = pings.filter(owner__project__owner_id=profile.user_id)
pings = pings.filter(created__gt=year_ago)
if pings.exists():
print("Skipping %s, has pings in last year" % profile)
continue
self.stdout.write("Sending notice to %s" % profile.user.email)
profile.deletion_notice_date = now()
profile.save()
ctx = {
"email": profile.user.email,
"support_email": settings.SUPPORT_EMAIL
}
emails.deletion_notice(profile.user.email, ctx)
# Throttle so we don't send too many emails at once:
time.sleep(1)
sent += 1
return "Done! Sent %d notices" % sent

+ 18
- 0
hc/accounts/migrations/0027_profile_deletion_notice_date.py View File

@ -0,0 +1,18 @@
# Generated by Django 2.1.7 on 2019-03-12 17:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0026_auto_20190204_2042'),
]
operations = [
migrations.AddField(
model_name='profile',
name='deletion_notice_date',
field=models.DateTimeField(blank=True, null=True),
),
]

+ 1
- 0
hc/accounts/models.py View File

@ -56,6 +56,7 @@ class Profile(models.Model):
sms_sent = models.IntegerField(default=0) sms_sent = models.IntegerField(default=0)
team_limit = models.IntegerField(default=2) team_limit = models.IntegerField(default=2)
sort = models.CharField(max_length=20, default="created") sort = models.CharField(max_length=20, default="created")
deletion_notice_date = models.DateTimeField(null=True, blank=True)
objects = ProfileManager() objects = ProfileManager()


+ 18
- 0
hc/api/migrations/0058_auto_20190312_1716.py View File

@ -0,0 +1,18 @@
# Generated by Django 2.1.7 on 2019-03-12 17:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0057_auto_20190118_1319'),
]
operations = [
migrations.AlterField(
model_name='channel',
name='kind',
field=models.CharField(choices=[('email', 'Email'), ('webhook', 'Webhook'), ('hipchat', 'HipChat'), ('slack', 'Slack'), ('pd', 'PagerDuty'), ('pagertree', 'PagerTree'), ('po', 'Pushover'), ('pushbullet', 'Pushbullet'), ('opsgenie', 'OpsGenie'), ('victorops', 'VictorOps'), ('discord', 'Discord'), ('telegram', 'Telegram'), ('sms', 'SMS'), ('zendesk', 'Zendesk'), ('trello', 'Trello'), ('matrix', 'Matrix')], max_length=20),
),
]

+ 4
- 0
hc/lib/emails.py View File

@ -70,3 +70,7 @@ def invoice(to, ctx, filename, pdf_data):
msg.attach_alternative(html, "text/html") msg.attach_alternative(html, "text/html")
msg.attach(filename, pdf_data, "application/pdf") msg.attach(filename, pdf_data, "application/pdf")
msg.send() msg.send()
def deletion_notice(to, ctx, headers={}):
send("deletion-notice", to, ctx, headers)

+ 1
- 0
hc/settings.py View File

@ -31,6 +31,7 @@ SECRET_KEY = os.getenv("SECRET_KEY", "---")
DEBUG = envbool("DEBUG", "True") DEBUG = envbool("DEBUG", "True")
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "*").split(",") ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "*").split(",")
DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", "[email protected]") DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", "[email protected]")
SUPPORT_EMAIL = os.getenv("SUPPORT_EMAIL")
USE_PAYMENTS = envbool("USE_PAYMENTS", "False") USE_PAYMENTS = envbool("USE_PAYMENTS", "False")
REGISTRATION_OPEN = envbool("REGISTRATION_OPEN", "True") REGISTRATION_OPEN = envbool("REGISTRATION_OPEN", "True")


+ 20
- 0
templates/emails/deletion-notice-body-html.html View File

@ -0,0 +1,20 @@
{% extends "emails/base.html" %}
{% load hc_extras %}
{% block content %}
Hello,<br />
We’re sending this email to notify you that your {% site_name %} account, registered to {{ email }} has been inactive for 1 year or more. If you no longer wish to keep your {% site_name %} account active then we will make sure that your account is closed and any data associated with your account is permanently deleted from our systems.<br /><br />
If you wish to keep your account, simply log in within 30 days. If you continue to be inactive, <strong>your account will be permanently deleted after the 30 day period</strong>.<br /><br />
If you have issues logging in, or have any questions, please reach out to us at {{ support_email }}.<br /><br />
Sincerely,<br />
The {% site_name %} Team
{% endblock %}
{% block unsub %}
<br />
This is a one-time message we're sending out to notify you about your account closure.
{% endblock %}

+ 14
- 0
templates/emails/deletion-notice-body-text.html View File

@ -0,0 +1,14 @@
{% load hc_extras %}
Hello,
We’re sending this email to notify you that your {% site_name %} account, registered to {{ email }} has been inactive for 1 year or more. If you no longer wish to keep your {% site_name %} account active then we will make sure that your account is closed and any data associated with your account is permanently deleted from our systems.
If you wish to keep your account, simply log in within 30 days. If you continue to be inactive, your account will be permanently deleted after the 30 day period.
If you have issues logging in, or have any questions, please reach out to us at {{ support_email }}.
This is a one-time message we're sending out to notify you about your account closure.
--
Sincerely,
The {% site_name %} Team

+ 1
- 0
templates/emails/deletion-notice-subject.html View File

@ -0,0 +1 @@
Inactive Account Notification

Loading…
Cancel
Save