Repository: cdriehuys/django-rest-email-auth

Issue:
Add command for cleaning up email confirmations
The command should delete email confirmations that have been expired for a certain amount of time.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/cdriehuys/django-rest-email-auth/pull/19?src=pr&el=h1) Report
> Merging [#19](https://codecov.io/gh/cdriehuys/django-rest-email-auth/pull/19?src=pr&el=desc) into [develop](https://codecov.io/gh/cdriehuys/django-rest-email-auth/commit/a73a4500e89dd08ae490d412976756faf5f4693c?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/cdriehuys/django-rest-email-auth/pull/19/graphs/tree.svg?token=1Pcv0CWgJy&width=650&src=pr&height=150)](https://codecov.io/gh/cdriehuys/django-rest-email-auth/pull/19?src=pr&el=tree)

```diff
@@          Coverage Diff           @@
##           develop    #19   +/-   ##
======================================
  Coverage      100%   100%           
======================================
  Files            6      7    +1     
  Lines          144    164   +20     
  Branches         4      5    +1     
======================================
+ Hits           144    164   +20
```


| [Impacted Files](https://codecov.io/gh/cdriehuys/django-rest-email-auth/pull/19?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [rest\_email\_auth/app\_settings.py](https://codecov.io/gh/cdriehuys/django-rest-email-auth/pull/19?src=pr&el=tree#diff-cmVzdF9lbWFpbF9hdXRoL2FwcF9zZXR0aW5ncy5weQ==) | `100% <100%> (ø)` | :arrow_up: |
| [...uth/management/commands/cleanemailconfirmations.py](https://codecov.io/gh/cdriehuys/django-rest-email-auth/pull/19?src=pr&el=tree#diff-cmVzdF9lbWFpbF9hdXRoL21hbmFnZW1lbnQvY29tbWFuZHMvY2xlYW5lbWFpbGNvbmZpcm1hdGlvbnMucHk=) | `100% <100%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/cdriehuys/django-rest-email-auth/pull/19?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/cdriehuys/django-rest-email-auth/pull/19?src=pr&el=footer). Last update [a73a450...b1a67cc](https://codecov.io/gh/cdriehuys/django-rest-email-auth/pull/19?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/rest_email_auth/app_settings.py b/rest_email_auth/app_settings.py
index 51ccd0c..8f7d13f 100644
--- a/rest_email_auth/app_settings.py
+++ b/rest_email_auth/app_settings.py
@@ -39,10 +39,36 @@ class AppSettings(object):
 
         return settings_dict.get(name, default)
 
+    @property
+    def CONFIRMATION_EXPIRATION(self):
+        """
+        The duration that an email confirmation is valid for.
+
+        Defaults to 1 day.
+        """
+        import datetime
+
+        return self._setting(
+            'CONFIRMATION_EXPIRATION',
+            datetime.timedelta(days=1))
+
+    @property
+    def CONFIRMATION_SAVE_PERIOD(self):
+        """
+        The duration that expired confirmations are saved for.
+
+        Defaults to 7 days.
+        """
+        import datetime
+
+        return self._setting(
+            'CONFIRMATION_SAVE_PERIOD',
+            datetime.timedelta(days=7))
+
     @property
     def EMAIL_VERIFICATION_URL(self):
         """
-        Get the template to use for the email verification url.
+        The template to use for the email verification url.
         """
         return self._setting('EMAIL_VERIFICATION_URL', '')
 
diff --git a/rest_email_auth/management/__init__.py b/rest_email_auth/management/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/rest_email_auth/management/commands/__init__.py b/rest_email_auth/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/rest_email_auth/management/commands/cleanemailconfirmations.py b/rest_email_auth/management/commands/cleanemailconfirmations.py
new file mode 100644
index 0000000..7722d0a
--- /dev/null
+++ b/rest_email_auth/management/commands/cleanemailconfirmations.py
@@ -0,0 +1,34 @@
+from django.core import management
+from django.utils import timezone
+
+from rest_email_auth import app_settings, models
+
+
+class Command(management.BaseCommand):
+    """
+    Command to clean up old email confirmations.
+    """
+    help = "Clean up expired email confirmations."
+
+    def handle(self, *args, **kwargs):
+        """
+        Handle execution of the command.
+        """
+        cutoff = timezone.now()
+        cutoff -= app_settings.CONFIRMATION_EXPIRATION
+        cutoff -= app_settings.CONFIRMATION_SAVE_PERIOD
+
+        queryset = models.EmailConfirmation.objects.filter(
+            created_at__lte=cutoff)
+
+        count = queryset.count()
+
+        queryset.delete()
+
+        if count:
+            self.stdout.write(
+                self.style.SUCCESS(
+                    'Removed {count} old email confirmation(s)'.format(
+                        count=count)))
+        else:
+            self.stdout.write('No email confirmations to remove.')


Test patch:
diff --git a/rest_email_auth/tests/management/__init__.py b/rest_email_auth/tests/management/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/rest_email_auth/tests/management/commands/__init__.py b/rest_email_auth/tests/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/rest_email_auth/tests/management/commands/test_cleanemailconfirmations_command.py b/rest_email_auth/tests/management/commands/test_cleanemailconfirmations_command.py
new file mode 100644
index 0000000..f946fcd
--- /dev/null
+++ b/rest_email_auth/tests/management/commands/test_cleanemailconfirmations_command.py
@@ -0,0 +1,54 @@
+import datetime
+
+from django.core import management
+from django.utils.six import StringIO
+
+import pytest
+
+from rest_email_auth import models
+
+
+def test_clean_expired(email_confirmation_factory, settings):
+    """
+    Calling the command should remove any email confirmations that are
+    a specified time older than their expiration date.
+    """
+    settings.REST_EMAIL_AUTH = {
+        'CONFIRMATION_EXPIRATION': datetime.timedelta(seconds=0),
+        'CONFIRMATION_SAVE_PERIOD': datetime.timedelta(seconds=0),
+    }
+
+    email_confirmation_factory()
+
+    out = StringIO()
+    management.call_command('cleanemailconfirmations', stdout=out)
+
+    assert models.EmailConfirmation.objects.count() == 0
+    assert 'Removed 1' in out.getvalue()
+
+
+def test_clean_just_expired(email_confirmation_factory, settings):
+    """
+    Email confirmations that have just expired should not be removed.
+    """
+    settings.REST_EMAIL_AUTH = {
+        'CONFIRMATION_EXPIRATION': datetime.timedelta(seconds=0),
+    }
+
+    email_confirmation_factory()
+
+    management.call_command('cleanemailconfirmations')
+
+    assert models.EmailConfirmation.objects.count() == 1
+
+
+@pytest.mark.django_db
+def test_clean_none():
+    """
+    If there are no expired email confirmations, a message should be
+    displayed indicating that nothing happened.
+    """
+    out = StringIO()
+    management.call_command('cleanemailconfirmations', stdout=out)
+
+    assert 'No email confirmations to remove' in out.getvalue()