Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for clearing out old messages #40

Merged
merged 5 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/email_relay/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class AppSettings:
EMPTY_QUEUE_SLEEP: int = 30
EMAIL_THROTTLE: int = 0
MESSAGES_BATCH_SIZE: int | None = None
MESSAGES_RETENTION_SECONDS: int | None = None

def __getattribute__(self, __name: str) -> Any:
user_settings = getattr(settings, EMAIL_RELAY_SETTINGS_NAME, {})
Expand Down
17 changes: 17 additions & 0 deletions src/email_relay/management/commands/runrelay.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from __future__ import annotations

import datetime
import logging
import time

from django.core.management import BaseCommand
from django.utils import timezone

from email_relay.conf import app_settings
from email_relay.models import Message
Expand All @@ -27,3 +29,18 @@ def handle(self, *args, **options):
logger.debug(msg)

send_all()
self.delete_old_messages()

def delete_old_messages(self):
if app_settings.MESSAGES_RETENTION_SECONDS is not None:
logger.debug("deleting old messages")
if app_settings.MESSAGES_RETENTION_SECONDS == 0:
deleted_messages = Message.objects.sent().delete()
else:
deleted_messages = Message.objects.sent_before(
timezone.now()
- datetime.timedelta(
seconds=app_settings.MESSAGES_RETENTION_SECONDS
)
).delete()
logger.debug(f"deleted {deleted_messages[0]} messages")
3 changes: 2 additions & 1 deletion src/email_relay/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.2.5 on 2023-09-27 12:20
# Generated by Django 4.2.5 on 2023-09-27 18:38

from django.db import migrations, models

Expand Down Expand Up @@ -50,6 +50,7 @@ class Migration(migrations.Migration):
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("sent_at", models.DateTimeField(blank=True, null=True)),
],
options={
"ordering": ["created_at"],
Expand Down
7 changes: 5 additions & 2 deletions src/email_relay/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from django.core.mail import EmailMessage
from django.core.mail import EmailMultiAlternatives
from django.db import models
from django.utils import timezone


class Priority(models.IntegerChoices):
Expand All @@ -23,7 +24,7 @@ class Status(models.IntegerChoices):

class MessageQuerySet(models.QuerySet):
def prioritized(self):
return self.order_by("priority", "created_at")
return self.order_by("-priority", "created_at")

def high_priority(self):
return self.filter(priority=Priority.HIGH)
Expand All @@ -47,7 +48,7 @@ def sent(self):
return self.filter(status=Status.SENT)

def sent_before(self, dt: datetime.datetime):
return self.sent().filter(created_at__lte=dt)
return self.sent().filter(sent_at__lte=dt)


class Message(models.Model):
Expand All @@ -65,6 +66,7 @@ class Message(models.Model):

created_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True, editable=False)
sent_at = models.DateTimeField(null=True, blank=True)

objects = MessageQuerySet.as_manager()

Expand All @@ -89,6 +91,7 @@ def save(self, *args, **kwargs):

def mark_sent(self):
self.status = Status.SENT
self.sent_at = timezone.now()
self.save()

def defer(self, log: str = ""):
Expand Down
95 changes: 95 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,107 @@
from __future__ import annotations

import datetime

import pytest
from django.utils import timezone
from model_bakery import baker

from email_relay.models import Message
from email_relay.models import Priority
from email_relay.models import Status


@pytest.mark.django_db(databases=["default", "email_relay_db"])
def test_message():
baker.make("email_relay.Message")
assert Message.objects.all().count() == 1


@pytest.mark.django_db(databases=["default", "email_relay_db"])
class TestMessageQuerySet:
@pytest.fixture
def messages_with_priority(self):
low = baker.make("email_relay.Message", priority=Priority.LOW)
medium = baker.make("email_relay.Message", priority=Priority.MEDIUM)
high = baker.make("email_relay.Message", priority=Priority.HIGH)
return low, medium, high

@pytest.fixture
def messages_with_status(self):
queued = baker.make("email_relay.Message", status=Status.QUEUED)
deferred = baker.make("email_relay.Message", status=Status.DEFERRED)
failed = baker.make("email_relay.Message", status=Status.FAILED)
sent = baker.make("email_relay.Message", status=Status.SENT)
return queued, deferred, failed, sent

def test_prioritized(self, messages_with_priority):
queryset = Message.objects.prioritized()
assert queryset.count() == 3
assert queryset[0] == messages_with_priority[2]
assert queryset[1] == messages_with_priority[1]
assert queryset[2] == messages_with_priority[0]

def test_high_priority(self, messages_with_priority):
queryset = Message.objects.high_priority()
assert queryset.count() == 1
assert queryset[0] == messages_with_priority[2]

def test_medium_priority(self, messages_with_priority):
queryset = Message.objects.medium_priority()
assert queryset.count() == 1
assert queryset[0] == messages_with_priority[1]

def test_low_priority(self, messages_with_priority):
queryset = Message.objects.low_priority()
assert queryset.count() == 1
assert queryset[0] == messages_with_priority[0]

def test_queued(self, messages_with_status):
queryset = Message.objects.queued()
assert queryset.count() == 1
assert queryset[0] == messages_with_status[0]

def test_deferred(self, messages_with_status):
queryset = Message.objects.deferred()
assert queryset.count() == 1
assert queryset[0] == messages_with_status[1]

def test_failed(self, messages_with_status):
queryset = Message.objects.failed()
assert queryset.count() == 1
assert queryset[0] == messages_with_status[2]

def test_sent(self, messages_with_status):
queryset = Message.objects.sent()
assert queryset.count() == 1
assert queryset[0] == messages_with_status[3]

def test_sent_before(self):
one_week = baker.make(
"email_relay.Message",
status=Status.SENT,
sent_at=timezone.now() - datetime.timedelta(days=7),
)
now = baker.make(
"email_relay.Message",
status=Status.SENT,
sent_at=timezone.now(),
)
not_sent = baker.make(
"email_relay.Message",
status=Status.QUEUED,
sent_at=None,
)

queryset = Message.objects.sent_before(
timezone.now() - datetime.timedelta(days=1)
)

print(one_week.sent_at)
print(now.sent_at)
print(not_sent.sent_at)

assert queryset.count() == 1
assert one_week in queryset
assert now not in queryset
assert not_sent not in queryset
72 changes: 72 additions & 0 deletions tests/test_runrelay.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
from __future__ import annotations

import datetime

import pytest
from django.core.management import call_command
from django.test.utils import override_settings
from django.utils import timezone
from model_bakery import baker

from email_relay.management.commands.runrelay import Command
from email_relay.models import Message
from email_relay.models import Status


def test_runrelay_help():
Expand All @@ -13,3 +22,66 @@ def test_runrelay_help():

# Asserting that the command exits with a successful exit code (0 for help command)
assert exec_info.value.code == 0


@pytest.fixture
def runrelay():
return Command()


@pytest.mark.django_db(databases=["default", "email_relay_db"])
def test_delete_sent_messages_based_on_retention_default(runrelay):
baker.make(
"email_relay.Message",
status=Status.SENT,
sent_at=timezone.now(),
_quantity=10,
)

runrelay.delete_old_messages()

assert Message.objects.count() == 10


@override_settings(
DJANGO_EMAIL_RELAY={
"MESSAGES_RETENTION_SECONDS": 0,
}
)
@pytest.mark.django_db(databases=["default", "email_relay_db"])
def test_delete_sent_messages_based_on_retention_zero(runrelay):
baker.make(
"email_relay.Message",
status=Status.SENT,
sent_at=timezone.now(),
_quantity=10,
)

runrelay.delete_old_messages()

assert Message.objects.count() == 0


@override_settings(
DJANGO_EMAIL_RELAY={
"MESSAGES_RETENTION_SECONDS": 600,
}
)
@pytest.mark.django_db(databases=["default", "email_relay_db"])
def test_delete_sent_messages_based_on_retention_non_zero(runrelay):
baker.make(
"email_relay.Message",
status=Status.SENT,
sent_at=timezone.now(),
_quantity=5,
)
baker.make(
"email_relay.Message",
status=Status.SENT,
sent_at=timezone.now() - datetime.timedelta(seconds=601),
_quantity=5,
)

runrelay.delete_old_messages()

assert Message.objects.count() == 5