diff --git a/rodatraden/admin.py b/rodatraden/admin.py index 17c68b6..4f058cf 100644 --- a/rodatraden/admin.py +++ b/rodatraden/admin.py @@ -1,4 +1,12 @@ from django.contrib import admin +from django.contrib.auth.models import User +from django.contrib.auth.admin import UserAdmin +from django.utils.translation import gettext_lazy as _, ngettext +from django.utils import timezone +from django.contrib import messages +from django.conf import settings +from django.db import models +from datetime import timedelta from .models import * @@ -20,3 +28,58 @@ # Register all the models in myModels to the admin site admin.site.register(myModels) + + +def delete_inactive_users(modeladmin, request, queryset): + """Admin action to delete inactive users based on settings.""" + years = getattr(settings, 'INACTIVE_USER_AUTODELETE_YEARS', 5) + if years <= 0: + modeladmin.message_user( + request, + _("Inactive user auto-deletion is disabled (INACTIVE_USER_AUTODELETE_YEARS is 0 or not set)."), + messages.WARNING + ) + return + + threshold_date = timezone.now() - timedelta(days=years * 365) + + # Find inactive users: exclude staff and superusers + inactive_users = queryset.filter( + is_staff=False, + is_superuser=False + ).filter( + models.Q(last_login__lt=threshold_date) | + models.Q(last_login__isnull=True, date_joined__lt=threshold_date) + ) + + count = inactive_users.count() + if count == 0: + modeladmin.message_user( + request, + _("No inactive users found to delete."), + messages.INFO + ) + return + + inactive_users.delete() + modeladmin.message_user( + request, + ngettext( + "%d inactive user was successfully deleted.", + "%d inactive users were successfully deleted.", + count, + ) % count, + messages.SUCCESS, + ) + + +delete_inactive_users.short_description = _("Delete inactive users") + + +class CustomUserAdmin(UserAdmin): + actions = [delete_inactive_users] + list(UserAdmin.actions) + + +# Unregister the default User admin and register our custom one +admin.site.unregister(User) +admin.site.register(User, CustomUserAdmin) diff --git a/rodatraden/tests.py b/rodatraden/tests.py index 7ce503c..9db897f 100644 --- a/rodatraden/tests.py +++ b/rodatraden/tests.py @@ -1,3 +1,95 @@ -from django.test import TestCase +from django.test import TestCase, RequestFactory +from django.contrib.auth.models import User +from django.utils import timezone +from django.contrib.admin.sites import AdminSite +from django.contrib import messages +from unittest.mock import Mock +from datetime import timedelta +from django.conf import settings -# Create your tests here. +from .admin import CustomUserAdmin, delete_inactive_users + + +class InactiveUserDeletionTest(TestCase): + def setUp(self): + self.factory = RequestFactory() + self.admin_site = AdminSite() + self.user_admin = CustomUserAdmin(User, self.admin_site) + + # Create test users + self.old_user = User.objects.create_user( + username='olduser', + email='old@example.com', + date_joined=timezone.now() - timedelta(days=6*365) # 6 years ago + ) + self.old_user.last_login = timezone.now() - timedelta(days=6*365) + self.old_user.save() + + self.new_user = User.objects.create_user( + username='newuser', + email='new@example.com', + date_joined=timezone.now() - timedelta(days=1*365) # 1 year ago + ) + self.new_user.last_login = timezone.now() - timedelta(days=1*365) + self.new_user.save() + + self.never_logged_in_old = User.objects.create_user( + username='neverold', + email='never@example.com', + date_joined=timezone.now() - timedelta(days=6*365) # 6 years ago + ) + # last_login remains None + + self.staff_user = User.objects.create_user( + username='staff', + email='staff@example.com', + is_staff=True + ) + + def test_delete_inactive_users(self): + # Set the threshold to 5 years + original_setting = getattr(settings, 'INACTIVE_USER_AUTODELETE_YEARS', 5) + settings.INACTIVE_USER_AUTODELETE_YEARS = 5 + + request = self.factory.post('/') + request.user = self.staff_user + + # Mock message_user + self.user_admin.message_user = Mock() + + # Call the action + delete_inactive_users(self.user_admin, request, User.objects.all()) + + # Check that old users were deleted + self.assertFalse(User.objects.filter(username='olduser').exists()) + self.assertFalse(User.objects.filter(username='neverold').exists()) + + # Check that new user and staff were not deleted + self.assertTrue(User.objects.filter(username='newuser').exists()) + self.assertTrue(User.objects.filter(username='staff').exists()) + + # Restore setting + settings.INACTIVE_USER_AUTODELETE_YEARS = original_setting + + def test_delete_inactive_users_disabled(self): + # Set to disabled + original_setting = getattr(settings, 'INACTIVE_USER_AUTODELETE_YEARS', 5) + settings.INACTIVE_USER_AUTODELETE_YEARS = 0 + + request = self.factory.post('/') + request.user = self.staff_user + + # Mock message_user + self.user_admin.message_user = Mock() + + # Call the action + delete_inactive_users(self.user_admin, request, User.objects.all()) + + # No users should be deleted + self.assertTrue(User.objects.filter(username='olduser').exists()) + self.assertTrue(User.objects.filter(username='neverold').exists()) + self.assertTrue(User.objects.filter(username='newuser').exists()) + self.assertTrue(User.objects.filter(username='staff').exists()) + + # Restore setting + settings.INACTIVE_USER_AUTODELETE_YEARS = original_setting diff --git a/tf/settings-template.py b/tf/settings-template.py index 5a0bb60..eff2604 100644 --- a/tf/settings-template.py +++ b/tf/settings-template.py @@ -169,4 +169,11 @@ LOGIN_URL = '/anvandare/login/' # Url for login # Crispy forms -CRISPY_TEMPLATE_PACK = 'bootstrap5' \ No newline at end of file +CRISPY_TEMPLATE_PACK = 'bootstrap5' + +# Inactive user auto-deletion +# Set to number of years after which inactive users are deleted. +# Users are considered inactive if their last_login is older than this threshold, +# or if they never logged in, their date_joined is older than this threshold. +# Set to 0 to disable auto-deletion. +INACTIVE_USER_AUTODELETE_YEARS = 5 \ No newline at end of file