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)