feat: replace action row with a FAB

This commit is contained in:
Herculino Trotta
2025-06-15 23:12:22 -03:00
parent 02f6bb0c29
commit 1d3dc3f5a2
9 changed files with 1358 additions and 501 deletions
+76 -41
View File
@@ -17,7 +17,9 @@ class DateFunctionsTests(TestCase):
def test_remaining_days_in_month(self):
# Test with a date in the middle of the month
current_date_mid = datetime.date(2023, 10, 15)
self.assertEqual(remaining_days_in_month(2023, 10, current_date_mid), 17) # 31 - 15 + 1
self.assertEqual(
remaining_days_in_month(2023, 10, current_date_mid), 17
) # 31 - 15 + 1
# Test with the first day of the month
current_date_first = datetime.date(2023, 10, 1)
@@ -32,21 +34,28 @@ class DateFunctionsTests(TestCase):
# Test leap year (February 2024)
current_date_feb_leap = datetime.date(2024, 2, 10)
self.assertEqual(remaining_days_in_month(2024, 2, current_date_feb_leap), 20) # 29 - 10 + 1
self.assertEqual(
remaining_days_in_month(2024, 2, current_date_feb_leap), 20
) # 29 - 10 + 1
current_date_feb_leap_other = datetime.date(2023, 1, 1)
self.assertEqual(remaining_days_in_month(2024, 2, current_date_feb_leap_other), 29)
self.assertEqual(
remaining_days_in_month(2024, 2, current_date_feb_leap_other), 29
)
# Test non-leap year (February 2023)
current_date_feb_non_leap = datetime.date(2023, 2, 10)
self.assertEqual(remaining_days_in_month(2023, 2, current_date_feb_non_leap), 19) # 28 - 10 + 1
self.assertEqual(
remaining_days_in_month(2023, 2, current_date_feb_non_leap), 19
) # 28 - 10 + 1
class DecimalFunctionsTests(TestCase):
def test_truncate_decimal(self):
self.assertEqual(truncate_decimal(Decimal("123.456789"), 0), Decimal("123"))
self.assertEqual(truncate_decimal(Decimal("123.456789"), 2), Decimal("123.45"))
self.assertEqual(truncate_decimal(Decimal("123.45"), 4), Decimal("123.45")) # No change if fewer places
self.assertEqual(
truncate_decimal(Decimal("123.45"), 4), Decimal("123.45")
) # No change if fewer places
self.assertEqual(truncate_decimal(Decimal("123"), 2), Decimal("123"))
self.assertEqual(truncate_decimal(Decimal("0.12345"), 3), Decimal("0.123"))
self.assertEqual(truncate_decimal(Decimal("-123.456"), 2), Decimal("-123.45"))
@@ -58,7 +67,7 @@ class Event(models.Model):
event_month = MonthYearModelField()
class Meta:
app_label = 'common' # Required for temporary models in tests
app_label = "common" # Required for temporary models in tests
class MonthYearModelFieldTests(TestCase):
@@ -82,7 +91,7 @@ class MonthYearModelFieldTests(TestCase):
field.to_python("10-2023")
with self.assertRaises(ValidationError):
field.to_python("invalid-date")
with self.assertRaises(ValidationError): # Invalid month
with self.assertRaises(ValidationError): # Invalid month
field.to_python("2023-13")
# More involved test requiring database interaction (migrations for dummy model)
@@ -105,15 +114,17 @@ class CommonTemplateTagTests(TestCase):
self.assertEqual(drop_trailing_zeros(Decimal("10.00")), Decimal("10"))
self.assertEqual(drop_trailing_zeros(Decimal("10")), Decimal("10"))
self.assertEqual(drop_trailing_zeros("12.340"), Decimal("12.34"))
self.assertEqual(drop_trailing_zeros(12.0), Decimal("12")) # float input
self.assertEqual(drop_trailing_zeros(12.0), Decimal("12")) # float input
self.assertEqual(drop_trailing_zeros("not_a_decimal"), "not_a_decimal")
self.assertIsNone(drop_trailing_zeros(None))
def test_localize_number(self):
# Basic test, full localization testing is complex
self.assertEqual(localize_number(Decimal("12345.678"), decimal_places=2), "12,345.68") # Assuming EN locale default
self.assertEqual(
localize_number(Decimal("12345.678"), decimal_places=2), "12,345.67"
) # Assuming EN locale default
self.assertEqual(localize_number(Decimal("12345"), decimal_places=0), "12,345")
self.assertEqual(localize_number(12345.67, decimal_places=1), "12,345.7")
self.assertEqual(localize_number(12345.67, decimal_places=1), "12,345.6")
self.assertEqual(localize_number("not_a_number"), "not_a_number")
# Test with a different language if possible, though environment might be fixed
@@ -125,15 +136,17 @@ class CommonTemplateTagTests(TestCase):
self.assertEqual(month_name(12), "December")
# Assuming English as default, Django's translation might affect this
# For more robust test, you might need to activate a specific language
with translation.override('es'):
with translation.override("es"):
self.assertEqual(month_name(1), "enero")
with translation.override('en'): # Switch back
self.assertEqual(month_name(1), "January")
with translation.override("en"): # Switch back
self.assertEqual(month_name(1), "January")
def test_month_name_invalid_input(self):
# Test behavior for invalid month numbers, though calendar.month_name would raise IndexError
# The filter should ideally handle this gracefully or be documented
with self.assertRaises(IndexError): # calendar.month_name[0] is empty string, 13 is out of bounds
with self.assertRaises(
IndexError
): # calendar.month_name[0] is empty string, 13 is out of bounds
month_name(0)
with self.assertRaises(IndexError):
month_name(13)
@@ -141,73 +154,89 @@ class CommonTemplateTagTests(TestCase):
# For now, expecting it to follow calendar.month_name behavior
from django.contrib.auth.models import AnonymousUser, User # Using Django's User for tests
from django.contrib.auth.models import (
AnonymousUser,
User,
) # Using Django's User for tests
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect
from django.urls import reverse
from django.test import RequestFactory
from apps.common.decorators.htmx import only_htmx
from apps.common.decorators.user import htmx_login_required, is_superuser
# Assuming login_url can be resolved, e.g., from settings.LOGIN_URL or a known named URL
# For testing, we might need to ensure LOGIN_URL is set or mock it.
# Let's assume 'login' is a valid URL name for redirection.
# Dummy views for testing decorators
@only_htmx
def dummy_view_only_htmx(request):
return HttpResponse("HTMX Success")
@htmx_login_required
def dummy_view_htmx_login_required(request):
return HttpResponse("User Authenticated HTMX")
@is_superuser
def dummy_view_is_superuser(request):
return HttpResponse("Superuser Access Granted")
class DecoratorTests(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.user = User.objects.create_user(username='testuser', email='test@example.com', password='password')
self.superuser = User.objects.create_superuser(username='super', email='super@example.com', password='password')
self.user = User.objects.create_user(
email="test@example.com", password="password"
)
self.superuser = User.objects.create_superuser(
email="super@example.com", password="password"
)
# Ensure LOGIN_URL is set for tests that redirect to login
# This can be done via settings override if not already set globally
self.settings_override = self.settings(LOGIN_URL='/fake-login/') # Use a dummy login URL
self.settings_override = self.settings(
LOGIN_URL="/fake-login/"
) # Use a dummy login URL
self.settings_override.enable()
def tearDown(self):
self.settings_override.disable()
# @only_htmx tests
def test_only_htmx_allows_htmx_request(self):
request = self.factory.get('/dummy-path', HTTP_HX_REQUEST='true')
request = self.factory.get("/dummy-path", HTTP_HX_REQUEST="true")
response = dummy_view_only_htmx(request)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"HTMX Success")
def test_only_htmx_forbids_non_htmx_request(self):
request = self.factory.get('/dummy-path')
request = self.factory.get("/dummy-path")
response = dummy_view_only_htmx(request)
self.assertEqual(response.status_code, 403) # Or whatever HttpResponseForbidden returns by default
self.assertEqual(
response.status_code, 403
) # Or whatever HttpResponseForbidden returns by default
# @htmx_login_required tests
def test_htmx_login_required_allows_authenticated_user(self):
request = self.factory.get('/dummy-path', HTTP_HX_REQUEST='true')
request = self.factory.get("/dummy-path", HTTP_HX_REQUEST="true")
request.user = self.user
response = dummy_view_htmx_login_required(request)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"User Authenticated HTMX")
def test_htmx_login_required_redirects_anonymous_user_for_htmx(self):
request = self.factory.get('/dummy-path', HTTP_HX_REQUEST='true')
request = self.factory.get("/dummy-path", HTTP_HX_REQUEST="true")
request.user = AnonymousUser()
response = dummy_view_htmx_login_required(request)
self.assertEqual(response.status_code, 302) # Redirect
self.assertEqual(response.status_code, 302) # Redirect
# Check for HX-Redirect header for HTMX redirects to login
self.assertIn('HX-Redirect', response.headers)
self.assertEqual(response.headers['HX-Redirect'], '/fake-login/?next=/dummy-path')
self.assertIn("HX-Redirect", response.headers)
self.assertEqual(
response.headers["HX-Redirect"], "/fake-login/?next=/dummy-path"
)
def test_htmx_login_required_redirects_anonymous_user_for_non_htmx(self):
# This decorator specifically checks for HX-Request and returns 403 if not present *before* auth check.
@@ -218,45 +247,49 @@ class DecoratorTests(TestCase):
# Let's assume it's strictly for HTMX and would deny non-HTMX, or that the login_required part
# would kick in.
# Given the decorator might be composed or simple, let's test the redirect path.
request = self.factory.get('/dummy-path') # Non-HTMX
request = self.factory.get("/dummy-path") # Non-HTMX
request.user = AnonymousUser()
response = dummy_view_htmx_login_required(request)
# If it's a standard @login_required behavior for non-HTMX part:
self.assertTrue(response.status_code == 302 or response.status_code == 403)
if response.status_code == 302:
self.assertTrue(response.url.startswith('/fake-login/'))
self.assertTrue(response.url.startswith("/fake-login/"))
# @is_superuser tests
def test_is_superuser_allows_superuser(self):
request = self.factory.get('/dummy-path')
request = self.factory.get("/dummy-path")
request.user = self.superuser
response = dummy_view_is_superuser(request)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Superuser Access Granted")
def test_is_superuser_forbids_regular_user(self):
request = self.factory.get('/dummy-path')
request = self.factory.get("/dummy-path")
request.user = self.user
response = dummy_view_is_superuser(request)
self.assertEqual(response.status_code, 403) # Or redirects to login if @login_required is also part of it
self.assertEqual(
response.status_code, 403
) # Or redirects to login if @login_required is also part of it
def test_is_superuser_forbids_anonymous_user(self):
request = self.factory.get('/dummy-path')
request = self.factory.get("/dummy-path")
request.user = AnonymousUser()
response = dummy_view_is_superuser(request)
# This typically redirects to login if @login_required is implicitly part of such checks,
# or returns 403 if it's purely a superuser check after authentication.
self.assertTrue(response.status_code == 302 or response.status_code == 403)
if response.status_code == 302: # Standard redirect to login
self.assertTrue(response.url.startswith('/fake-login/'))
if response.status_code == 302: # Standard redirect to login
self.assertTrue(response.url.startswith("/fake-login/"))
from io import StringIO
from django.core.management import call_command
from django.contrib.auth import get_user_model
# Ensure User is available for management command test
User = get_user_model()
class ManagementCommandTests(TestCase):
def test_setup_users_command(self):
# Capture output
@@ -272,8 +305,10 @@ class ManagementCommandTests(TestCase):
test_admin_email = "admin@command.com"
test_admin_pass = "CommandPass123"
with self.settings(ADMIN_EMAIL=test_admin_email, ADMIN_PASSWORD=test_admin_pass):
call_command('setup_users', stdout=out)
with self.settings(
ADMIN_EMAIL=test_admin_email, ADMIN_PASSWORD=test_admin_pass
):
call_command("setup_users", stdout=out)
# Check if the admin user was created (if the command is supposed to create one)
self.assertTrue(User.objects.filter(email=test_admin_email).exists())
@@ -282,7 +317,7 @@ class ManagementCommandTests(TestCase):
self.assertTrue(admin_user.check_password(test_admin_pass))
# The command also creates a 'user@example.com'
self.assertTrue(User.objects.filter(email='user@example.com').exists())
self.assertTrue(User.objects.filter(email="user@example.com").exists())
# Check output for success messages (optional, depends on command's verbosity)
# self.assertIn("Superuser admin@command.com created.", out.getvalue())