Skip to content

Commit

Permalink
Create cron job endpoints for anonymisation warnings (#358)
Browse files Browse the repository at this point in the history
* Create cron job endpoints for anonymisation warnings

* Rename variables

* Permissions

* Feedback

* Feedback again (tests not working)

* Merge branch 'development' into anonymisation_warnings

* Fix test

* Simplify

* Feedback
  • Loading branch information
faucomte97 authored Aug 8, 2024
1 parent 36f7657 commit 4cd73d7
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 1 deletion.
3 changes: 3 additions & 0 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
"Email change notification": 1551600,
"Verify changed user email": 1551594,
"Account deletion": 1567477,
"Inactive users on website - first reminder": 1604381,
"Inactive users on website - second reminder": 1606208,
"Inactive users on website - final reminder": 1606215,
}

# Build paths inside the project like this: BASE_DIR / 'subdir'.
Expand Down
87 changes: 86 additions & 1 deletion src/api/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from codeforlife.user.views import UserViewSet as _UserViewSet
from codeforlife.views import action, cron_job
from django.conf import settings
from django.db.models import F
from django.db.models import F, Q
from django.urls import reverse
from django.utils import timezone
from rest_framework import status
Expand Down Expand Up @@ -64,6 +64,9 @@ def get_permissions(self):
"send_1st_verify_email_reminder",
"send_2nd_verify_email_reminder",
"anonymize_unverified_accounts",
"send_1st_inactivity_reminder",
"send_2nd_inactivity_reminder",
"send_final_inactivity_reminder",
]:
return [IsCronRequestFromGoogle()]

Expand Down Expand Up @@ -340,3 +343,85 @@ def anonymize_unverified_accounts(self, request: Request):
)

return Response()

def _get_inactive_users(self, days: int):
now = timezone.now()

# All users who haven't logged in in X days OR who've never logged in
# and registered over X days ago.
user_queryset = User.objects.filter(
Q(
last_login__isnull=False,
last_login__lte=now - timedelta(days=days),
last_login__gt=now - timedelta(days=days + 1),
)
| Q(
last_login__isnull=True,
date_joined__lte=now - timedelta(days=days),
date_joined__gt=now - timedelta(days=days + 1),
)
)

return user_queryset.exclude(email__isnull=True).exclude(email="")

def _send_inactivity_reminder(self, days: int, campaign_name: str):
user_queryset = self._get_inactive_users(days)
user_count = user_queryset.count()

logging.info("%d inactive users after %d days.", user_count, days)

if user_count > 0:
sent_email_count = 0
for email in user_queryset.values_list("email", flat=True).iterator(
chunk_size=500
):
try:
send_mail(
campaign_id=settings.DOTDIGITAL_CAMPAIGN_IDS[
campaign_name
],
to_addresses=[email],
)

sent_email_count += 1
# pylint: disable-next=broad-exception-caught
except Exception as ex:
logging.exception(ex)

logging.info(
"Reminded %d/%d inactive users.", sent_email_count, user_count
)

return Response()

@cron_job
def send_1st_inactivity_reminder(self, request: Request):
"""
Send the first reminder email to teachers and independent users who
haven't been active in a while.
"""
return self._send_inactivity_reminder(
days=730, campaign_name="Inactive users on website - first reminder"
)

@cron_job
def send_2nd_inactivity_reminder(self, request: Request):
"""
Send the second reminder email to teachers and independent users who
haven't been active in a while.
"""
return self._send_inactivity_reminder(
days=973,
campaign_name="Inactive users on website - second reminder",
)

@cron_job
def send_final_inactivity_reminder(self, request: Request):
"""
Send the final reminder email to teachers and independent users who
haven't been active in a while.
"""
return self._send_inactivity_reminder(
days=1065,
campaign_name="Inactive users on website - final reminder",
)
87 changes: 87 additions & 0 deletions src/api/views/user_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,27 @@ def test_get_permissions__anonymize_unverified_accounts(self):
action="anonymize_unverified_accounts",
)

def test_get_permissions__send_1st_inactivity_reminder(self):
"""Only Google can send the 1st inactivity reminder."""
self.assert_get_permissions(
permissions=[IsCronRequestFromGoogle()],
action="send_1st_inactivity_reminder",
)

def test_get_permissions__send_2nd_inactivity_reminder(self):
"""Only Google can send the 2nd inactivity reminder."""
self.assert_get_permissions(
permissions=[IsCronRequestFromGoogle()],
action="send_2nd_inactivity_reminder",
)

def test_get_permissions__send_final_inactivity_reminder(self):
"""Only Google can send the final inactivity reminder."""
self.assert_get_permissions(
permissions=[IsCronRequestFromGoogle()],
action="send_final_inactivity_reminder",
)

def test_get_permissions__register_to_newsletter(self):
"""Any one can register to our newsletter."""
self.assert_get_permissions(
Expand Down Expand Up @@ -723,6 +744,72 @@ def anonymize_unverified_users(
is_anonymized=True,
)

def _test_send_inactivity_reminder(
self, action: str, days: int, campaign_name: str
):
def test_send_inactivity_reminder(days: int, mail_sent: bool):
date_joined = timezone.now() - timedelta(days, hours=12)
last_login = timezone.now() - timedelta(days, hours=12)

assert StudentUser.objects.update(date_joined=date_joined)

TeacherUser.objects.update(date_joined=date_joined, last_login=None)
IndependentUser.objects.update(last_login=last_login)

teacher_users = list(TeacherUser.objects.all())
assert teacher_users
indy_users = list(IndependentUser.objects.all())
assert indy_users

with patch("src.api.views.user.send_mail") as send_mail_mock:
self.client.cron_job(action)

if mail_sent:
send_mail_mock.assert_has_calls(
[
call(
campaign_id=(
settings.DOTDIGITAL_CAMPAIGN_IDS[
campaign_name
]
),
to_addresses=[user.email],
)
for user in teacher_users + indy_users
],
any_order=True,
)
else:
send_mail_mock.assert_not_called()

test_send_inactivity_reminder(days=days - 1, mail_sent=False)
test_send_inactivity_reminder(days=days, mail_sent=True)
test_send_inactivity_reminder(days=days + 1, mail_sent=False)

def test_send_1st_inactivity_reminder(self):
"""Can send the 1st inactivity reminder."""
self._test_send_inactivity_reminder(
action="send_1st_inactivity_reminder",
days=730,
campaign_name="Inactive users on website - first reminder",
)

def test_send_2nd_inactivity_reminder(self):
"""Can send the 2nd inactivity reminder."""
self._test_send_inactivity_reminder(
action="send_2nd_inactivity_reminder",
days=973,
campaign_name="Inactive users on website - second reminder",
)

def test_send_final_inactivity_reminder(self):
"""Can send the final inactivity reminder."""
self._test_send_inactivity_reminder(
action="send_final_inactivity_reminder",
days=1065,
campaign_name="Inactive users on website - final reminder",
)

# test: other actions

def test_register_to_newsletter(self):
Expand Down

0 comments on commit 4cd73d7

Please sign in to comment.