From 89057c82a12234a71917628bdf226eb6b0ac8d11 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 20 Dec 2025 06:40:14 +0000
Subject: [PATCH 1/3] Initial plan
From 14811b662980fcc8eca0d473911022515e0d4e33 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 20 Dec 2025 06:45:34 +0000
Subject: [PATCH 2/3] Add Django 6.0 compatible jazzmin template tag override
Co-authored-by: gdsanger <59723667+gdsanger@users.noreply.github.com>
---
core/templatetags/__init__.py | 1 +
core/templatetags/jazzmin.py | 70 ++++++++++++++++++++++++++
core/tests_jazzmin_templatetag.py | 84 +++++++++++++++++++++++++++++++
3 files changed, 155 insertions(+)
create mode 100644 core/templatetags/__init__.py
create mode 100644 core/templatetags/jazzmin.py
create mode 100644 core/tests_jazzmin_templatetag.py
diff --git a/core/templatetags/__init__.py b/core/templatetags/__init__.py
new file mode 100644
index 0000000..d43656b
--- /dev/null
+++ b/core/templatetags/__init__.py
@@ -0,0 +1 @@
+# Template tags package for core app
diff --git a/core/templatetags/jazzmin.py b/core/templatetags/jazzmin.py
new file mode 100644
index 0000000..92ff065
--- /dev/null
+++ b/core/templatetags/jazzmin.py
@@ -0,0 +1,70 @@
+"""
+Django 6.0 compatibility fix for jazzmin pagination template tag.
+
+This module overrides the jazzmin_paginator_number template tag to use
+mark_safe() instead of format_html() for Django 6.0 compatibility.
+
+Django 6.0 changed format_html() to require at least one argument for formatting.
+Since the html_str is already properly formatted and doesn't need escaping,
+we use mark_safe() instead.
+"""
+
+from django.contrib.admin.views.main import PAGE_VAR, ChangeList
+from django.template import Library
+from django.utils.safestring import SafeText, mark_safe
+
+register = Library()
+
+
+@register.simple_tag
+def jazzmin_paginator_number(change_list: ChangeList, i: int) -> SafeText:
+ """
+ Generate an individual page index link in a paginated list.
+
+ This is a Django 6.0 compatible version that uses mark_safe() instead of
+ format_html() to avoid the "args or kwargs must be provided" TypeError.
+ """
+ html_str = ""
+ start = i == 1
+ end = i == change_list.paginator.num_pages
+ spacer = i in (".", "…")
+ current_page = i == change_list.page_num
+
+ if start:
+ link = change_list.get_query_string({PAGE_VAR: change_list.page_num - 1}) if change_list.page_num > 1 else "#"
+ html_str += """
+
+ «
+
+ """.format(link=link, disabled="disabled" if link == "#" else "")
+
+ if current_page:
+ html_str += """
+
+ {num}
+
+ """.format(num=i)
+ elif spacer:
+ html_str += """
+
+ …
+
+ """
+ else:
+ query_string = change_list.get_query_string({PAGE_VAR: i})
+ end_class = "end" if end else ""
+ html_str += """
+
+ {num}
+
+ """.format(num=i, query_string=query_string, end=end_class)
+
+ if end:
+ link = change_list.get_query_string({PAGE_VAR: change_list.page_num + 1}) if change_list.page_num < i else "#"
+ html_str += """
+
+ »
+
+ """.format(link=link, disabled="disabled" if link == "#" else "")
+
+ return mark_safe(html_str)
diff --git a/core/tests_jazzmin_templatetag.py b/core/tests_jazzmin_templatetag.py
new file mode 100644
index 0000000..e46a9e4
--- /dev/null
+++ b/core/tests_jazzmin_templatetag.py
@@ -0,0 +1,84 @@
+"""
+Tests for Django 6.0 compatibility fixes in core.templatetags.jazzmin
+"""
+from django.contrib.admin.views.main import ChangeList
+from django.test import TestCase, RequestFactory
+from django.contrib.admin.sites import AdminSite
+from django.contrib.auth.models import User
+from django.contrib.admin import ModelAdmin
+
+from core.templatetags.jazzmin import jazzmin_paginator_number
+
+
+class JazzminPaginatorNumberTestCase(TestCase):
+ """Test the jazzmin_paginator_number template tag for Django 6.0 compatibility."""
+
+ def setUp(self):
+ """Set up test fixtures."""
+ self.factory = RequestFactory()
+ self.site = AdminSite()
+
+ def test_jazzmin_paginator_number_returns_safe_html(self):
+ """Test that jazzmin_paginator_number returns SafeText without errors."""
+ # Create a mock request
+ request = self.factory.get('/admin/auth/user/')
+ request.user = User(username='admin', is_staff=True, is_superuser=True)
+
+ # Create a ModelAdmin instance
+ model_admin = ModelAdmin(User, self.site)
+
+ # Create a ChangeList - this would normally be done by Django admin
+ changelist = ChangeList(
+ request=request,
+ model=User,
+ list_display=['username'],
+ list_display_links=['username'],
+ list_filter=[],
+ date_hierarchy=None,
+ search_fields=[],
+ list_select_related=[],
+ list_per_page=100,
+ list_max_show_all=200,
+ list_editable=[],
+ model_admin=model_admin,
+ sortable_by=[],
+ search_help_text=None,
+ )
+
+ # Test the template tag with different page numbers
+ # This should not raise "TypeError: args or kwargs must be provided"
+ try:
+ result = jazzmin_paginator_number(changelist, 1)
+ self.assertIsNotNone(result)
+ self.assertIn('page-item', str(result))
+ except TypeError as e:
+ if "args or kwargs must be provided" in str(e):
+ self.fail("Template tag still uses format_html incorrectly")
+ raise
+
+ def test_jazzmin_paginator_number_start_page(self):
+ """Test pagination rendering for the first page."""
+ request = self.factory.get('/admin/auth/user/')
+ request.user = User(username='admin', is_staff=True, is_superuser=True)
+
+ model_admin = ModelAdmin(User, self.site)
+ changelist = ChangeList(
+ request=request,
+ model=User,
+ list_display=['username'],
+ list_display_links=['username'],
+ list_filter=[],
+ date_hierarchy=None,
+ search_fields=[],
+ list_select_related=[],
+ list_per_page=100,
+ list_max_show_all=200,
+ list_editable=[],
+ model_admin=model_admin,
+ sortable_by=[],
+ search_help_text=None,
+ )
+
+ result = jazzmin_paginator_number(changelist, 1)
+ self.assertIn('previous', str(result))
+ self.assertIn('«', str(result))
From 351dd5719159a5f560ad30fbb3ed30236613f23f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 20 Dec 2025 06:48:58 +0000
Subject: [PATCH 3/3] Remove unused template tag override files and update
README
Co-authored-by: gdsanger <59723667+gdsanger@users.noreply.github.com>
---
JAZZMIN_FIX_README.md | 53 +++++++++++++++++---------
core/templatetags/__init__.py | 1 -
core/templatetags/jazzmin.py | 70 -----------------------------------
3 files changed, 35 insertions(+), 89 deletions(-)
delete mode 100644 core/templatetags/__init__.py
delete mode 100644 core/templatetags/jazzmin.py
diff --git a/JAZZMIN_FIX_README.md b/JAZZMIN_FIX_README.md
index 250bdba..0ae96d5 100644
--- a/JAZZMIN_FIX_README.md
+++ b/JAZZMIN_FIX_README.md
@@ -3,33 +3,38 @@
## Problem
Django 6.0 introduced a breaking change where `format_html()` now requires at least one argument (args or kwargs). The jazzmin package (v3.0.1) uses `format_html(html_str)` which causes a `TypeError: args or kwargs must be provided` error when accessing admin list views with pagination.
+### Error Example
+```
+TypeError at /admin/core/booking/
+args or kwargs must be provided.
+
+Exception Location: /opt/Finoa/.venv/lib/python3.12/site-packages/django/utils/html.py, line 137, in format_html
+```
+
## Solution
This fix patches the jazzmin package's `jazzmin_paginator_number` template tag to use `mark_safe()` instead of `format_html()` for Django 6.0 compatibility.
### Implementation
-1. **Automated Patch Script** (`fix_jazzmin.sh`):
- - Automatically locates the jazzmin package installation
- - Patches line 256 in `jazzmin/templatetags/jazzmin.py`
- - Replaces `return format_html(html_str)` with `return mark_safe(html_str)`
+**Automated Patch Script** (`fix_jazzmin.sh`):
+- Automatically locates the jazzmin package installation
+- Patches line 256 in `jazzmin/templatetags/jazzmin.py`
+- Replaces `return format_html(html_str)` with `return mark_safe(html_str)`
-2. **Usage**:
- ```bash
- # Run after installing requirements.txt
- ./fix_jazzmin.sh
- ```
+### Usage
+```bash
+# Run after installing requirements.txt
+cd /path/to/Finoa
+pip install -r requirements.txt
+./fix_jazzmin.sh
+python manage.py runserver
+```
## Why This Works
- `mark_safe()` marks a string as safe for HTML output without requiring additional arguments
- The HTML string is already properly formatted by the template tag
- No security impact since the string is built from trusted sources (Django's own ChangeList object)
-
-## Files Changed
-- `fix_jazzmin.sh` - New patch script
-- `finoa/settings.py` - Updated INSTALLED_APPS order (core before jazzmin) for template tag override precedence
-- `core/templatetags/__init__.py` - Template tags package init
-- `core/templatetags/jazzmin.py` - Custom jazzmin template tag override (fallback solution)
-- `core/apps.py` - Reverted to original (monkey patch approach removed)
+- The patch is applied directly to the installed jazzmin package
## Deployment
Run the patch script as part of your deployment process:
@@ -39,10 +44,22 @@ pip install -r requirements.txt
python manage.py runserver
```
+**Note**: The patch needs to be reapplied after each time you reinstall or upgrade the jazzmin package.
+
## Alternative Solutions Considered
1. ✗ Monkey patching in `CoreConfig.ready()` - Doesn't work because template tags are loaded before AppConfig.ready()
-2. ✗ Custom template tag override - Doesn't work reliably due to Python module caching
+2. ✗ Custom template tag override - Doesn't work because Django loads the first matching template tag library name from INSTALLED_APPS, and both modules use the same name "jazzmin"
3. ✓ Direct file patch via shell script - Works reliably and is easy to integrate into deployment
+## Technical Details
+The issue is in `/path/to/site-packages/jazzmin/templatetags/jazzmin.py` at line 256:
+```python
+# Before (causes error in Django 6.0):
+return format_html(html_str)
+
+# After (works in Django 6.0):
+return mark_safe(html_str)
+```
+
## Note
-This is a temporary fix until jazzmin releases a Django 6.0 compatible version. Monitor the jazzmin GitHub repository for official updates.
+This is a temporary fix until jazzmin releases a Django 6.0 compatible version. Monitor the [jazzmin GitHub repository](https://github.com/farridav/django-jazzmin) for official updates.
diff --git a/core/templatetags/__init__.py b/core/templatetags/__init__.py
deleted file mode 100644
index d43656b..0000000
--- a/core/templatetags/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# Template tags package for core app
diff --git a/core/templatetags/jazzmin.py b/core/templatetags/jazzmin.py
deleted file mode 100644
index 92ff065..0000000
--- a/core/templatetags/jazzmin.py
+++ /dev/null
@@ -1,70 +0,0 @@
-"""
-Django 6.0 compatibility fix for jazzmin pagination template tag.
-
-This module overrides the jazzmin_paginator_number template tag to use
-mark_safe() instead of format_html() for Django 6.0 compatibility.
-
-Django 6.0 changed format_html() to require at least one argument for formatting.
-Since the html_str is already properly formatted and doesn't need escaping,
-we use mark_safe() instead.
-"""
-
-from django.contrib.admin.views.main import PAGE_VAR, ChangeList
-from django.template import Library
-from django.utils.safestring import SafeText, mark_safe
-
-register = Library()
-
-
-@register.simple_tag
-def jazzmin_paginator_number(change_list: ChangeList, i: int) -> SafeText:
- """
- Generate an individual page index link in a paginated list.
-
- This is a Django 6.0 compatible version that uses mark_safe() instead of
- format_html() to avoid the "args or kwargs must be provided" TypeError.
- """
- html_str = ""
- start = i == 1
- end = i == change_list.paginator.num_pages
- spacer = i in (".", "…")
- current_page = i == change_list.page_num
-
- if start:
- link = change_list.get_query_string({PAGE_VAR: change_list.page_num - 1}) if change_list.page_num > 1 else "#"
- html_str += """
-
- «
-
- """.format(link=link, disabled="disabled" if link == "#" else "")
-
- if current_page:
- html_str += """
-
- {num}
-
- """.format(num=i)
- elif spacer:
- html_str += """
-
- …
-
- """
- else:
- query_string = change_list.get_query_string({PAGE_VAR: i})
- end_class = "end" if end else ""
- html_str += """
-
- {num}
-
- """.format(num=i, query_string=query_string, end=end_class)
-
- if end:
- link = change_list.get_query_string({PAGE_VAR: change_list.page_num + 1}) if change_list.page_num < i else "#"
- html_str += """
-
- »
-
- """.format(link=link, disabled="disabled" if link == "#" else "")
-
- return mark_safe(html_str)