From c70b190c235d65b19e7a5abeca635490e72aaed0 Mon Sep 17 00:00:00 2001 From: Kumi Date: Sun, 23 Jun 2024 08:56:22 +0200 Subject: [PATCH] feat: add inactive user cleanup and refine UI Introduce a management command to delete inactive users older than 7 days. Adjust `CustomUser` model to include `date_joined` with new default settings. Enhance UI across multiple templates to improve consistency and user experience. - Inactive user accounts cleanup enhances database hygiene. - Updates to `CustomUser` default settings ensure consistent account states. - Refined UI for identifier and suffix templates for better accessibility and UX. --- freedoi/accounts/management/__init__.py | 0 .../accounts/management/commands/__init__.py | 0 .../commands/delete_inactive_users.py | 14 ++++++ ..._date_joined_alter_customuser_is_active.py | 24 ++++++++++ freedoi/accounts/models.py | 33 +++++++++----- freedoi/accounts/urls.py | 3 +- freedoi/accounts/views.py | 24 +++++----- ...fix_options_alter_prefix_owner_and_more.py | 34 ++++++++++++++ .../resolver/identifier_confirm_delete.html | 23 ++++++---- .../templates/resolver/identifier_detail.html | 35 ++++++++++----- .../templates/resolver/identifier_form.html | 22 +++++++--- .../templates/resolver/identifier_list.html | 36 +++++++++------ .../resolver/suffix_confirm_delete.html | 19 ++++---- .../templates/resolver/suffix_detail.html | 44 ++++++++++--------- .../templates/resolver/suffix_form.html | 18 +++++--- .../templates/resolver/suffix_list.html | 23 ++++++---- 16 files changed, 248 insertions(+), 104 deletions(-) create mode 100644 freedoi/accounts/management/__init__.py create mode 100644 freedoi/accounts/management/commands/__init__.py create mode 100644 freedoi/accounts/management/commands/delete_inactive_users.py create mode 100644 freedoi/accounts/migrations/0003_customuser_date_joined_alter_customuser_is_active.py create mode 100644 freedoi/resolver/migrations/0006_alter_suffix_options_alter_prefix_owner_and_more.py diff --git a/freedoi/accounts/management/__init__.py b/freedoi/accounts/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/freedoi/accounts/management/commands/__init__.py b/freedoi/accounts/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/freedoi/accounts/management/commands/delete_inactive_users.py b/freedoi/accounts/management/commands/delete_inactive_users.py new file mode 100644 index 0000000..ed933ea --- /dev/null +++ b/freedoi/accounts/management/commands/delete_inactive_users.py @@ -0,0 +1,14 @@ +import datetime +from django.core.management.base import BaseCommand +from django.utils import timezone +from accounts.models import CustomUser + +class Command(BaseCommand): + help = 'Deletes inactive user accounts that were created more than 7 days ago' + + def handle(self, *args, **kwargs): + expiration_date = timezone.now() - datetime.timedelta(days=7) + inactive_users = CustomUser.objects.filter(is_active=False, date_joined__lt=expiration_date) + count = inactive_users.count() + inactive_users.delete() + self.stdout.write(f'Deleted {count} inactive user accounts.') \ No newline at end of file diff --git a/freedoi/accounts/migrations/0003_customuser_date_joined_alter_customuser_is_active.py b/freedoi/accounts/migrations/0003_customuser_date_joined_alter_customuser_is_active.py new file mode 100644 index 0000000..dd2c0e9 --- /dev/null +++ b/freedoi/accounts/migrations/0003_customuser_date_joined_alter_customuser_is_active.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.6 on 2024-06-23 05:39 + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0002_customuser_maximum_suffixes"), + ] + + operations = [ + migrations.AddField( + model_name="customuser", + name="date_joined", + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name="customuser", + name="is_active", + field=models.BooleanField(default=False), + ), + ] diff --git a/freedoi/accounts/models.py b/freedoi/accounts/models.py index 7e16d7c..5a476da 100644 --- a/freedoi/accounts/models.py +++ b/freedoi/accounts/models.py @@ -1,39 +1,50 @@ -from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin +from django.contrib.auth.models import ( + AbstractBaseUser, + BaseUserManager, + PermissionsMixin, +) from django.db import models +from django.utils import timezone + class CustomUserManager(BaseUserManager): def create_user(self, email, password=None, **extra_fields): if not email: - raise ValueError('The Email field must be set') + raise ValueError("The Email field must be set") email = self.normalize_email(email) + extra_fields.setdefault("is_active", False) + extra_fields.setdefault("date_joined", timezone.now()) user = self.model(email=email, **extra_fields) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, email, password=None, **extra_fields): - extra_fields.setdefault('is_staff', True) - extra_fields.setdefault('is_superuser', True) + extra_fields.setdefault("is_staff", True) + extra_fields.setdefault("is_superuser", True) + extra_fields.setdefault("is_active", True) - if extra_fields.get('is_staff') is not True: - raise ValueError('Superuser must have is_staff=True.') - if extra_fields.get('is_superuser') is not True: - raise ValueError('Superuser must have is_superuser=True.') + if extra_fields.get("is_staff") is not True: + raise ValueError("Superuser must have is_staff=True.") + if extra_fields.get("is_superuser") is not True: + raise ValueError("Superuser must have is_superuser=True.") return self.create_user(email, password, **extra_fields) + class CustomUser(AbstractBaseUser, PermissionsMixin): email = models.EmailField(unique=True) first_name = models.CharField(max_length=30, blank=True) last_name = models.CharField(max_length=30, blank=True) - is_active = models.BooleanField(default=True) + is_active = models.BooleanField(default=False) is_staff = models.BooleanField(default=False) maximum_suffixes = models.IntegerField(default=5) + date_joined = models.DateTimeField(default=timezone.now) objects = CustomUserManager() - USERNAME_FIELD = 'email' + USERNAME_FIELD = "email" REQUIRED_FIELDS = [] def __str__(self): - return self.email \ No newline at end of file + return self.email diff --git a/freedoi/accounts/urls.py b/freedoi/accounts/urls.py index 4237404..ce46f75 100644 --- a/freedoi/accounts/urls.py +++ b/freedoi/accounts/urls.py @@ -4,11 +4,10 @@ from django.views.generic import TemplateView from .views import SendLoginEmailView, LoginView urlpatterns = [ - path("send-login-email/", SendLoginEmailView.as_view(), name="send_login_email"), path("login///", LoginView.as_view(), name="login"), path( "login/", - auth_views.LoginView.as_view(template_name="accounts/login.html"), + SendLoginEmailView.as_view(), name="login", ), path("logout/", auth_views.LogoutView.as_view(), name="logout"), diff --git a/freedoi/accounts/views.py b/freedoi/accounts/views.py index 1567954..434e4b9 100644 --- a/freedoi/accounts/views.py +++ b/freedoi/accounts/views.py @@ -11,25 +11,27 @@ from .forms import EmailForm User = get_user_model() + class SendLoginEmailView(FormView): - template_name = 'accounts/send_login_email.html' + template_name = "accounts/send_login_email.html" form_class = EmailForm - success_url = '/accounts/email-sent/' + success_url = "/accounts/email-sent/" def form_valid(self, form): - email = form.cleaned_data['email'] + email = form.cleaned_data["email"] user = User.objects.get(email=email) token = default_token_generator.make_token(user) uid = urlsafe_base64_encode(force_bytes(user.pk)) - login_url = self.request.build_absolute_uri(f'/accounts/login/{uid}/{token}/') + login_url = self.request.build_absolute_uri(f"/accounts/login/{uid}/{token}/") send_mail( - 'Your login link', - f'Click here to log in: {login_url}', - 'from@example.com', + "Your login link", + f"Click here to log in: {login_url}", + None, [email], ) return super().form_valid(form) + class LoginView(View): def get(self, request, uidb64, token): try: @@ -39,8 +41,10 @@ class LoginView(View): user = None if user is not None and default_token_generator.check_token(user, token): - user.backend = 'django.contrib.auth.backends.ModelBackend' + if not user.is_active: + user.is_active = True + user.save() auth_login(request, user) - return redirect('home') + return redirect("home") else: - return HttpResponse('Login link is invalid') \ No newline at end of file + return HttpResponse("Login link is invalid") diff --git a/freedoi/resolver/migrations/0006_alter_suffix_options_alter_prefix_owner_and_more.py b/freedoi/resolver/migrations/0006_alter_suffix_options_alter_prefix_owner_and_more.py new file mode 100644 index 0000000..dc42e63 --- /dev/null +++ b/freedoi/resolver/migrations/0006_alter_suffix_options_alter_prefix_owner_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 5.0.6 on 2024-06-23 05:39 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("resolver", "0005_alter_prefix_options_alter_suffix_options"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterModelOptions( + name="suffix", + options={"ordering": ["prefix", "suffix"]}, + ), + migrations.AlterField( + model_name="prefix", + name="owner", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL + ), + ), + migrations.AlterField( + model_name="suffix", + name="prefix", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="resolver.prefix" + ), + ), + ] diff --git a/freedoi/resolver/templates/resolver/identifier_confirm_delete.html b/freedoi/resolver/templates/resolver/identifier_confirm_delete.html index 318fcc5..2407cd4 100644 --- a/freedoi/resolver/templates/resolver/identifier_confirm_delete.html +++ b/freedoi/resolver/templates/resolver/identifier_confirm_delete.html @@ -1,9 +1,16 @@ -{% extends "base_generic.html" %} {% block content %} -

Delete Identifier

-

Are you sure you want to delete "{{ object.identifier }}"?

-
- {% csrf_token %} - -
-Back to list +{% extends "base_generic.html" %} {% block title %}Delete Identifier +{% endblock %} {% block content %} +
+

Delete Identifier

+

Are you sure you want to delete "{{ object.identifier }}"?

+
+ {% csrf_token %} + +
+ Back to identifiers +
{% endblock %} diff --git a/freedoi/resolver/templates/resolver/identifier_detail.html b/freedoi/resolver/templates/resolver/identifier_detail.html index fcbcc1b..9028ca8 100644 --- a/freedoi/resolver/templates/resolver/identifier_detail.html +++ b/freedoi/resolver/templates/resolver/identifier_detail.html @@ -1,11 +1,26 @@ -{% extends "base_generic.html" %} {% block content %} -

{{ object.identifier }}

-

Suffix: {{ object.suffix }}

-

Target URL: {{ object.target_url }}

-Edit -
- {% csrf_token %} - -
-Back to list +{% extends "base_generic.html" %} {% block title %}Identifier Detail +{% endblock %} {% block content %} +
+

{{ object.identifier }}

+

Suffix: {{ object.suffix }}

+

Target URL: {{ object.target_url }}

+ Edit +
+ {% csrf_token %} + +
+ Back to identifiers +
{% endblock %} diff --git a/freedoi/resolver/templates/resolver/identifier_form.html b/freedoi/resolver/templates/resolver/identifier_form.html index e77b5ac..6c3614c 100644 --- a/freedoi/resolver/templates/resolver/identifier_form.html +++ b/freedoi/resolver/templates/resolver/identifier_form.html @@ -1,8 +1,16 @@ -{% extends "base_generic.html" %} {% block content %} -

{% if form.instance.pk %}Edit{% else %}Create{% endif %} Identifier

-
- {% csrf_token %} {{ form.as_p }} - -
-Back to list +{% extends "base_generic.html" %} {% block title %} + {% if form.instance.pk %}Edit{% else %}Create{% endif %} Identifier +{% endblock %} {% block content %} +
+

{% if form.instance.pk %}Edit{% else %}Create{% endif %} Identifier

+
+ {% csrf_token %} {{ form.as_p }} + +
+ Back to identifiers +
{% endblock %} diff --git a/freedoi/resolver/templates/resolver/identifier_list.html b/freedoi/resolver/templates/resolver/identifier_list.html index 488a827..ed24cd7 100644 --- a/freedoi/resolver/templates/resolver/identifier_list.html +++ b/freedoi/resolver/templates/resolver/identifier_list.html @@ -1,14 +1,24 @@ -{% extends "base_generic.html" %} {% block content %} -

Identifiers

- -Create new identifier +{% extends "base_generic.html" %} {% block title %}Identifiers{% endblock %} +{% block content %} +
+

Identifiers for {{ suffix.name }}

+ + Create new identifier + Back to suffixes +
{% endblock %} diff --git a/freedoi/resolver/templates/resolver/suffix_confirm_delete.html b/freedoi/resolver/templates/resolver/suffix_confirm_delete.html index dcb9ecb..5ecc504 100644 --- a/freedoi/resolver/templates/resolver/suffix_confirm_delete.html +++ b/freedoi/resolver/templates/resolver/suffix_confirm_delete.html @@ -1,9 +1,12 @@ -{% extends "base_generic.html" %} {% block content %} -

Delete Suffix

-

Are you sure you want to delete "{{ object.name }}"?

-
- {% csrf_token %} - -
-Back to list +{% extends "base_generic.html" %} {% block title %}Delete Suffix{% endblock %} +{% block content %} +
+

Delete Suffix

+

Are you sure you want to delete "{{ object.name }}"?

+
+ {% csrf_token %} + +
+ Back to list +
{% endblock %} diff --git a/freedoi/resolver/templates/resolver/suffix_detail.html b/freedoi/resolver/templates/resolver/suffix_detail.html index dc04229..b5829e7 100644 --- a/freedoi/resolver/templates/resolver/suffix_detail.html +++ b/freedoi/resolver/templates/resolver/suffix_detail.html @@ -1,23 +1,27 @@ {% extends "base_generic.html" %} {% block title %}Suffix Detail{% endblock %} {% block content %} -

{{ object.name }}

-

Suffix: {{ object.suffix }}

-

Prefix: {{ object.prefix }}

-

Type: {{ object.get_type_display }}

-

Remote Resolver: {{ object.remote_resolver }}

-Edit -
- {% csrf_token %} - -
-View Identifiers -Back to list +
+

{{ object.name }}

+

Suffix: {{ object.suffix }}

+

Prefix: {{ object.prefix }}

+

Type: {{ object.get_type_display }}

+

Remote Resolver: {{ object.remote_resolver }}

+ Edit +
+ {% csrf_token %} + +
+ View Identifiers + Back to list +
{% endblock %} diff --git a/freedoi/resolver/templates/resolver/suffix_form.html b/freedoi/resolver/templates/resolver/suffix_form.html index dc3534f..2881004 100644 --- a/freedoi/resolver/templates/resolver/suffix_form.html +++ b/freedoi/resolver/templates/resolver/suffix_form.html @@ -1,8 +1,12 @@ -{% extends "base_generic.html" %} {% block content %} -

{% if form.instance.pk %}Edit{% else %}Create{% endif %} Suffix

-
- {% csrf_token %} {{ form.as_p }} - -
-Back to list +{% extends "base_generic.html" %} {% block title %} +{% if form.instance.pk %}Edit{% else %}Create{% endif %} Suffix{% endblock %} +{% block content %} +
+

{% if form.instance.pk %}Edit{% else %}Create{% endif %} Suffix

+
+ {% csrf_token %} {{ form.as_p }} + +
+ Back to list +
{% endblock %} diff --git a/freedoi/resolver/templates/resolver/suffix_list.html b/freedoi/resolver/templates/resolver/suffix_list.html index 3002a4e..1af95f8 100644 --- a/freedoi/resolver/templates/resolver/suffix_list.html +++ b/freedoi/resolver/templates/resolver/suffix_list.html @@ -1,9 +1,16 @@ -{% extends "base_generic.html" %} {% block content %} -

Suffixes

- -Create new suffix +{% extends "base_generic.html" %} {% block title %}Suffixes{% endblock %} +{% block content %} +
+

Suffixes

+ + Create new suffix +
{% endblock %}