forked from GithubBackups/healthchecks
senddeletionnotices
command skips profiles with recent last_active_date
This commit is contained in:
parent
01bb03c889
commit
15ba415298
@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Show Healthchecks version in Django admin header (#306)
|
- Show Healthchecks version in Django admin header (#306)
|
||||||
- Added JSON endpoint for Shields.io (#304)
|
- Added JSON endpoint for Shields.io (#304)
|
||||||
- Django 3.0
|
- Django 3.0
|
||||||
|
- `senddeletionnotices` command skips profiles with recent last_active_date
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
- Don't set CSRF cookie on first visit. Signup is exempt from CSRF protection
|
- Don't set CSRF cookie on first visit. Signup is exempt from CSRF protection
|
||||||
|
@ -19,16 +19,21 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def pause(self):
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
year_ago = now() - timedelta(days=365)
|
year_ago = now() - timedelta(days=365)
|
||||||
|
|
||||||
q = Profile.objects.order_by("id")
|
q = Profile.objects.order_by("id")
|
||||||
# Exclude accounts with logins in the last year_ago
|
# Exclude accounts with logins in the last year
|
||||||
q = q.exclude(user__last_login__gt=year_ago)
|
q = q.exclude(user__last_login__gt=year_ago)
|
||||||
# Exclude accounts less than a year_ago old
|
# Exclude accounts less than a year old
|
||||||
q = q.exclude(user__date_joined__gt=year_ago)
|
q = q.exclude(user__date_joined__gt=year_ago)
|
||||||
# Exclude accounts with the deletion notice already sent
|
# Exclude accounts with the deletion notice already sent
|
||||||
q = q.exclude(deletion_notice_date__gt=year_ago)
|
q = q.exclude(deletion_notice_date__gt=year_ago)
|
||||||
|
# Exclude accounts with activity in the last year
|
||||||
|
q = q.exclude(last_active_date__gt=year_ago)
|
||||||
# Exclude paid accounts
|
# Exclude paid accounts
|
||||||
q = q.exclude(sms_limit__gt=5)
|
q = q.exclude(sms_limit__gt=5)
|
||||||
|
|
||||||
@ -36,14 +41,14 @@ class Command(BaseCommand):
|
|||||||
for profile in q:
|
for profile in q:
|
||||||
members = Member.objects.filter(project__owner_id=profile.user_id)
|
members = Member.objects.filter(project__owner_id=profile.user_id)
|
||||||
if members.exists():
|
if members.exists():
|
||||||
print("Skipping %s, has team members" % profile)
|
self.stdout.write("Skipping %s, has team members" % profile)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
pings = Ping.objects
|
pings = Ping.objects
|
||||||
pings = pings.filter(owner__project__owner_id=profile.user_id)
|
pings = pings.filter(owner__project__owner_id=profile.user_id)
|
||||||
pings = pings.filter(created__gt=year_ago)
|
pings = pings.filter(created__gt=year_ago)
|
||||||
if pings.exists():
|
if pings.exists():
|
||||||
print("Skipping %s, has pings in last year" % profile)
|
self.stdout.write("Skipping %s, has pings in last year" % profile)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.stdout.write("Sending notice to %s" % profile.user.email)
|
self.stdout.write("Sending notice to %s" % profile.user.email)
|
||||||
@ -53,8 +58,10 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
ctx = {"email": profile.user.email, "support_email": settings.SUPPORT_EMAIL}
|
ctx = {"email": profile.user.email, "support_email": settings.SUPPORT_EMAIL}
|
||||||
emails.deletion_notice(profile.user.email, ctx)
|
emails.deletion_notice(profile.user.email, ctx)
|
||||||
|
|
||||||
# Throttle so we don't send too many emails at once:
|
# Throttle so we don't send too many emails at once:
|
||||||
time.sleep(1)
|
self.pause()
|
||||||
|
|
||||||
sent += 1
|
sent += 1
|
||||||
|
|
||||||
return "Done! Sent %d notices" % sent
|
return "Done! Sent %d notices" % sent
|
||||||
|
128
hc/accounts/tests/test_senddeletionnotices.py
Normal file
128
hc/accounts/tests/test_senddeletionnotices.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
from datetime import timedelta as td
|
||||||
|
|
||||||
|
from django.core import mail
|
||||||
|
from django.utils.timezone import now
|
||||||
|
from hc.accounts.management.commands.senddeletionnotices import Command
|
||||||
|
from hc.accounts.models import Member
|
||||||
|
from hc.api.models import Check, Ping
|
||||||
|
from hc.test import BaseTestCase
|
||||||
|
from mock import Mock
|
||||||
|
|
||||||
|
|
||||||
|
class SendDeletionNoticesTestCase(BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(SendDeletionNoticesTestCase, self).setUp()
|
||||||
|
|
||||||
|
# Make alice eligible for notice -- signed up more than 1 year ago
|
||||||
|
self.alice.date_joined = now() - td(days=500)
|
||||||
|
self.alice.save()
|
||||||
|
|
||||||
|
self.profile.sms_limit = 5
|
||||||
|
self.profile.save()
|
||||||
|
|
||||||
|
# remove members from alice's project
|
||||||
|
self.project.member_set.all().delete()
|
||||||
|
|
||||||
|
def test_it_sends_notice(self):
|
||||||
|
cmd = Command()
|
||||||
|
cmd.stdout = Mock() # silence output to stdout
|
||||||
|
cmd.pause = Mock() # don't pause for 1s
|
||||||
|
|
||||||
|
result = cmd.handle()
|
||||||
|
self.assertEqual(result, "Done! Sent 1 notices")
|
||||||
|
|
||||||
|
self.profile.refresh_from_db()
|
||||||
|
self.assertTrue(self.profile.deletion_notice_date)
|
||||||
|
|
||||||
|
email = mail.outbox[0]
|
||||||
|
self.assertEqual(email.subject, "Inactive Account Notification")
|
||||||
|
|
||||||
|
def test_it_checks_last_login(self):
|
||||||
|
# alice has logged in recently:
|
||||||
|
self.alice.last_login = now() - td(days=15)
|
||||||
|
self.alice.save()
|
||||||
|
|
||||||
|
cmd = Command()
|
||||||
|
cmd.stdout = Mock() # silence output to stdout
|
||||||
|
|
||||||
|
result = cmd.handle()
|
||||||
|
self.assertEqual(result, "Done! Sent 0 notices")
|
||||||
|
|
||||||
|
self.profile.refresh_from_db()
|
||||||
|
self.assertIsNone(self.profile.deletion_notice_date)
|
||||||
|
|
||||||
|
def test_it_checks_date_joined(self):
|
||||||
|
# alice signed up recently:
|
||||||
|
self.alice.date_joined = now() - td(days=15)
|
||||||
|
self.alice.save()
|
||||||
|
|
||||||
|
cmd = Command()
|
||||||
|
cmd.stdout = Mock() # silence output to stdout
|
||||||
|
|
||||||
|
result = cmd.handle()
|
||||||
|
self.assertEqual(result, "Done! Sent 0 notices")
|
||||||
|
|
||||||
|
self.profile.refresh_from_db()
|
||||||
|
self.assertIsNone(self.profile.deletion_notice_date)
|
||||||
|
|
||||||
|
def test_it_checks_deletion_notice_date(self):
|
||||||
|
# alice has already received a deletion notice
|
||||||
|
self.profile.deletion_notice_date = now() - td(days=15)
|
||||||
|
self.profile.save()
|
||||||
|
|
||||||
|
cmd = Command()
|
||||||
|
cmd.stdout = Mock() # silence output to stdout
|
||||||
|
|
||||||
|
result = cmd.handle()
|
||||||
|
self.assertEqual(result, "Done! Sent 0 notices")
|
||||||
|
|
||||||
|
def test_it_checks_sms_limit(self):
|
||||||
|
# alice has a paid account
|
||||||
|
self.profile.sms_limit = 50
|
||||||
|
self.profile.save()
|
||||||
|
|
||||||
|
cmd = Command()
|
||||||
|
cmd.stdout = Mock() # silence output to stdout
|
||||||
|
|
||||||
|
result = cmd.handle()
|
||||||
|
self.assertEqual(result, "Done! Sent 0 notices")
|
||||||
|
|
||||||
|
self.profile.refresh_from_db()
|
||||||
|
self.assertIsNone(self.profile.deletion_notice_date)
|
||||||
|
|
||||||
|
def test_it_checks_team_members(self):
|
||||||
|
# bob has access to alice's project
|
||||||
|
Member.objects.create(user=self.bob, project=self.project)
|
||||||
|
|
||||||
|
cmd = Command()
|
||||||
|
cmd.stdout = Mock() # silence output to stdout
|
||||||
|
|
||||||
|
result = cmd.handle()
|
||||||
|
self.assertEqual(result, "Done! Sent 0 notices")
|
||||||
|
|
||||||
|
self.profile.refresh_from_db()
|
||||||
|
self.assertIsNone(self.profile.deletion_notice_date)
|
||||||
|
|
||||||
|
def test_it_checks_recent_pings(self):
|
||||||
|
check = Check.objects.create(project=self.project)
|
||||||
|
Ping.objects.create(owner=check)
|
||||||
|
|
||||||
|
cmd = Command()
|
||||||
|
cmd.stdout = Mock() # silence output to stdout
|
||||||
|
|
||||||
|
result = cmd.handle()
|
||||||
|
self.assertEqual(result, "Done! Sent 0 notices")
|
||||||
|
|
||||||
|
self.profile.refresh_from_db()
|
||||||
|
self.assertIsNone(self.profile.deletion_notice_date)
|
||||||
|
|
||||||
|
def test_it_checks_last_active_date(self):
|
||||||
|
# alice has been browsing the site recently
|
||||||
|
self.profile.last_active_date = now() - td(days=15)
|
||||||
|
self.profile.save()
|
||||||
|
|
||||||
|
cmd = Command()
|
||||||
|
cmd.stdout = Mock() # silence output to stdout
|
||||||
|
|
||||||
|
result = cmd.handle()
|
||||||
|
self.assertEqual(result, "Done! Sent 0 notices")
|
@ -1,4 +1,3 @@
|
|||||||
from datetime import timedelta
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
Loading…
x
Reference in New Issue
Block a user