Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-django-tables2 for 
openSUSE:Factory checked in at 2026-04-11 22:26:54
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-django-tables2 (Old)
 and      /work/SRC/openSUSE:Factory/.python-django-tables2.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-django-tables2"

Sat Apr 11 22:26:54 2026 rev:13 rq:1346089 version:2.9.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-django-tables2/python-django-tables2.changes  
    2026-03-30 18:35:30.001595528 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-django-tables2.new.21863/python-django-tables2.changes
   2026-04-11 22:32:35.411644588 +0200
@@ -1,0 +2,23 @@
+Sat Apr 11 17:36:54 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 2.9.0:
+  * Rename the `querystring` templatetag to `querystring_replace`
+    to avoid shadowing built-in one.
+  * If you use custom templates to render tables with django-
+    tables2, you should replace
+  * `{% querystring %}` with `{% querystring_replace %}`
+  * `RelatedLinkColumn` is removed. Replace `RelatedLinkColumn`
+    with `Column(linkify=True)`.
+  * Remove deprecated `RelatedLinkColumn` and `NullBooleanField`
+    support
+  * `NullBooleanField` was removed in django 4.0
+  * Export `DateColumn`/`DateTimeColumn`/`TimeColumn` in ISO
+    format
+  * Rename `querystring` template tag to `querystring_replace`
+  * Use string annotation_format in `inspect.signature` to
+    prevent runtime errors with type annotations (#1027) by
+    @mschoettle
+  * Add optional `context_object_name` to `TemplateColumn` and
+    make `extra_context` optionally callable (#931) fixes: #928
+
+-------------------------------------------------------------------

Old:
----
  django-tables2-2.8.0.tar.gz

New:
----
  django-tables2-2.9.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-django-tables2.spec ++++++
--- /var/tmp/diff_new_pack.WCy2pg/_old  2026-04-11 22:32:35.927665693 +0200
+++ /var/tmp/diff_new_pack.WCy2pg/_new  2026-04-11 22:32:35.927665693 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-django-tables2
-Version:        2.8.0
+Version:        2.9.0
 Release:        0
 Summary:        Table/data-grid framework for Django
 License:        BSD-2-Clause

++++++ django-tables2-2.8.0.tar.gz -> django-tables2-2.9.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/.github/workflows/ci.yml 
new/django-tables2-2.9.0/.github/workflows/ci.yml
--- old/django-tables2-2.8.0/.github/workflows/ci.yml   2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/.github/workflows/ci.yml   2026-04-06 
11:55:06.000000000 +0200
@@ -7,7 +7,7 @@
       - uses: actions/setup-python@v6
         with:
           python-version: "3.11"
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - run: pip install pre-commit
       - run: pre-commit run --show-diff-on-failure --all-files
 
@@ -44,8 +44,8 @@
         uses: actions/setup-python@v6
         with:
           python-version: ${{ matrix.python-version }}
-      - uses: actions/checkout@v5
-      - uses: actions/[email protected]
+      - uses: actions/checkout@v6
+      - uses: actions/[email protected]
         with:
           path: ~/.cache/pip
           key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
@@ -53,10 +53,7 @@
             ${{ runner.os }}-pip-
       - name: Install Django ${{ matrix.django-version }}
         run: python -m pip install Django==${{ matrix.django-version }}
-        if: ${{ matrix.django-version != 'main' && matrix.django-version != 
'6.0' }}
-      - name: Install Django ${{ matrix.django-version }}
-        run: python -m pip install "Django>=6.0a1,<6.1"
-        if: matrix.django-version == '6.0'
+        if: ${{ matrix.django-version != 'main' }}
       - name: Install Django main branch
         run: python -m pip install -U 
https://github.com/django/django/archive/master.tar.gz
         if: matrix.django-version == 'main'
@@ -68,7 +65,7 @@
   docs:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v5
+      - uses: actions/checkout@v6
       - uses: actions/setup-python@v6
         with:
             python-version: "3.11"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/.pre-commit-config.yaml 
new/django-tables2-2.9.0/.pre-commit-config.yaml
--- old/django-tables2-2.8.0/.pre-commit-config.yaml    2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/.pre-commit-config.yaml    2026-04-06 
11:55:06.000000000 +0200
@@ -1,20 +1,20 @@
 repos:
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.8.0
+    rev: v0.14.6
     hooks:
-      - id: ruff
+      - id: ruff-check
         args: [--fix, --exit-non-zero-on-fix]
       - id: ruff-format
         types_or: [ python, pyi ]
 
   - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
-    rev: v2.14.0
+    rev: v2.15.0
     hooks:
     - id: pretty-format-toml
       args: [--autofix]
 
   - repo: https://github.com/adamchainz/django-upgrade
-    rev: "1.22.1"
+    rev: "1.29.1"
     hooks:
      - id: django-upgrade
        args: [--target-version, "4.2"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/.readthedocs.yaml 
new/django-tables2-2.9.0/.readthedocs.yaml
--- old/django-tables2-2.8.0/.readthedocs.yaml  2025-11-21 11:14:43.000000000 
+0100
+++ new/django-tables2-2.9.0/.readthedocs.yaml  2026-04-06 11:55:06.000000000 
+0200
@@ -6,7 +6,7 @@
 
 # Set the version of Python and other tools you might need
 build:
-  os: ubuntu-22.04
+  os: ubuntu-24.04
   tools:
     python: "3.11"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/.readthedocs.yml 
new/django-tables2-2.9.0/.readthedocs.yml
--- old/django-tables2-2.8.0/.readthedocs.yml   2025-11-21 11:14:43.000000000 
+0100
+++ new/django-tables2-2.9.0/.readthedocs.yml   1970-01-01 01:00:00.000000000 
+0100
@@ -1,21 +0,0 @@
-# .readthedocs.yaml
-# Read the Docs configuration file
-# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
-
-# Required
-version: 2
-
-# Set the version of Python and other tools you might need
-build:
-  os: ubuntu-22.04
-  tools:
-    python: "3.11"
-
-# Build documentation in the docs/ directory with Sphinx
-sphinx:
-   configuration: docs/conf.py
-
-# Optionally declare the Python requirements required to build your docs
-python:
-   install:
-   - requirements: docs/requirements.txt
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/CHANGELOG.md 
new/django-tables2-2.9.0/CHANGELOG.md
--- old/django-tables2-2.8.0/CHANGELOG.md       2025-11-21 11:14:43.000000000 
+0100
+++ new/django-tables2-2.9.0/CHANGELOG.md       2026-04-06 11:55:06.000000000 
+0200
@@ -1,10 +1,27 @@
 # Change log
 
+## 2.9.0 (2026-04-06)
+
+**Breaking changes:**
+- Rename the `querystring` templatetag to `querystring_replace` to avoid 
shadowing built-in one.
+  If you use custom templates to render tables with django-tables2, you should 
replace
+  `{% querystring %}` with `{% querystring_replace %}`
+- `RelatedLinkColumn` is removed. Replace `RelatedLinkColumn` with 
`Column(linkify=True)`.
+
+Changes:
+- Remove deprecated `RelatedLinkColumn` and `NullBooleanField` support 
([#1016](https://github.com/jieter/django-tables2/pull/1016))
+  `NullBooleanField` was removed in django 4.0
+- Export `DateColumn`/`DateTimeColumn`/`TimeColumn` in ISO format 
([#1022](https://github.com/jieter/django-tables2/pull/1022) by 
[@spapas](https://github.com/spapas))
+- Rename `querystring` template tag to `querystring_replace` 
([#1021](https://github.com/jieter/django-tables2/pull/1021) by 
[@federicobond](https://github.com/federicobond))
+- Use string annotation_format in `inspect.signature` to prevent runtime 
errors with type annotations 
([#1027](https://github.com/jieter/django-tables2/pull/1027)) by 
[@mschoettle](https://github.com/mschoettle)
+- Add optional `context_object_name` to `TemplateColumn` and make 
`extra_context` optionally callable 
([#931](https://github.com/jieter/django-tables2/pull/931)) fixes: 
[#928](https://github.com/jieter/django-tables2/issues/928)
+
+
 ## 2.8.0 (2025-11-21)
 - Pass `request` to the template rendered in TemplateColumn (#1014) Fixes: 
#1008
 - Do not generate error when table data model is subclass of table model 
([#1015](https://github.com/jieter/django-tables2/pull/1015)) Fixes 
[#1010](https://github.com/jieter/django-tables2/issues/1010) by 
[@sjoerdjob](https://github.com/sjoerdjob)
 - Update supported Python and Django versions 
([#1011](https://github.com/jieter/django-tables2/issues/1011)) by 
[@dyve](https://github.com/dyve)
-  Supported Django versions: 4.1, 5.1, 5.2, 6.0
+  Supported Django versions: 4.2, 5.1, 5.2, 6.0
   Supported Python versions: 3.10, 3.11, 3.12, 3.13, 3.14
 
 ## 2.7.5 (2025-01-02)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/django_tables2/__init__.py 
new/django-tables2-2.9.0/django_tables2/__init__.py
--- old/django-tables2-2.8.0/django_tables2/__init__.py 2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/django_tables2/__init__.py 2026-04-06 
11:55:06.000000000 +0200
@@ -9,7 +9,6 @@
     JSONColumn,
     LinkColumn,
     ManyToManyColumn,
-    RelatedLinkColumn,
     TemplateColumn,
     TimeColumn,
     URLColumn,
@@ -20,7 +19,7 @@
 from .utils import A
 from .views import MultiTableMixin, SingleTableMixin, SingleTableView
 
-__version__ = "2.8.0"
+__version__ = "2.9.0"
 
 __all__ = (
     "Table",
@@ -35,7 +34,6 @@
     "JSONColumn",
     "LinkColumn",
     "ManyToManyColumn",
-    "RelatedLinkColumn",
     "TemplateColumn",
     "TimeColumn",
     "URLColumn",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/django_tables2/columns/__init__.py 
new/django-tables2-2.9.0/django_tables2/columns/__init__.py
--- old/django-tables2-2.8.0/django_tables2/columns/__init__.py 2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/django_tables2/columns/__init__.py 2026-04-06 
11:55:06.000000000 +0200
@@ -6,7 +6,7 @@
 from .emailcolumn import EmailColumn
 from .filecolumn import FileColumn
 from .jsoncolumn import JSONColumn
-from .linkcolumn import LinkColumn, RelatedLinkColumn
+from .linkcolumn import LinkColumn
 from .manytomanycolumn import ManyToManyColumn
 from .templatecolumn import TemplateColumn
 from .timecolumn import TimeColumn
@@ -26,7 +26,6 @@
     "JSONColumn",
     "LinkColumn",
     "ManyToManyColumn",
-    "RelatedLinkColumn",
     "TemplateColumn",
     "URLColumn",
     "TimeColumn",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/django_tables2/columns/base.py 
new/django-tables2-2.9.0/django_tables2/columns/base.py
--- old/django-tables2-2.8.0/django_tables2/columns/base.py     2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/django_tables2/columns/base.py     2026-04-06 
11:55:06.000000000 +0200
@@ -616,9 +616,9 @@
         This is useful otherwise in templates you'd need something like::
 
             {% if column.is_ordered %}
-                {% querystring 
table.prefixed_order_by_field=column.order_by_alias.opposite %}
+                {% querystring_replace 
table.prefixed_order_by_field=column.order_by_alias.opposite %}
             {% else %}
-                {% querystring 
table.prefixed_order_by_field=column.order_by_alias %}
+                {% querystring_replace 
table.prefixed_order_by_field=column.order_by_alias %}
             {% endif %}
 
         """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/django_tables2/columns/booleancolumn.py 
new/django-tables2-2.9.0/django_tables2/columns/booleancolumn.py
--- old/django-tables2-2.8.0/django_tables2/columns/booleancolumn.py    
2025-11-21 11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/django_tables2/columns/booleancolumn.py    
2026-04-06 11:55:06.000000000 +0200
@@ -58,8 +58,5 @@
 
     @classmethod
     def from_field(cls, field, **kwargs):
-        if isinstance(field, models.NullBooleanField):
-            return cls(null=True, **kwargs)
-
         if isinstance(field, models.BooleanField):
             return cls(null=getattr(field, "null", False), **kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/django_tables2/columns/datecolumn.py 
new/django-tables2-2.9.0/django_tables2/columns/datecolumn.py
--- old/django-tables2-2.8.0/django_tables2/columns/datecolumn.py       
2025-11-21 11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/django_tables2/columns/datecolumn.py       
2026-04-06 11:55:06.000000000 +0200
@@ -1,3 +1,5 @@
+from datetime import date
+
 from django.db import models
 
 from .base import library
@@ -7,7 +9,8 @@
 @library.register
 class DateColumn(TemplateColumn):
     """
-    A column that renders dates in the local timezone.
+    A column that renders dates in the local timezone and uses isoformat() when
+    exporting.
 
     Arguments:
         format (str): format string in same format as Django's ``date`` 
template
@@ -26,3 +29,8 @@
     def from_field(cls, field, **kwargs):
         if isinstance(field, models.DateField):
             return cls(**kwargs)
+
+    def value(self, value, **kwargs):
+        if isinstance(value, date):
+            return value.isoformat()
+        return super().value(value=value, **kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/django_tables2/columns/datetimecolumn.py 
new/django-tables2-2.9.0/django_tables2/columns/datetimecolumn.py
--- old/django-tables2-2.8.0/django_tables2/columns/datetimecolumn.py   
2025-11-21 11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/django_tables2/columns/datetimecolumn.py   
2026-04-06 11:55:06.000000000 +0200
@@ -1,3 +1,5 @@
+from datetime import datetime
+
 from django.db import models
 
 from .base import library
@@ -7,7 +9,8 @@
 @library.register
 class DateTimeColumn(TemplateColumn):
     """
-    A column that renders `datetime` instances in the local timezone.
+    A column that renders `datetime` instances in the local timezone and uses
+    isoformat() (with a space separator) when exporting.
 
     Arguments:
         format (str): format string for datetime (optional).
@@ -26,3 +29,8 @@
     def from_field(cls, field, **kwargs):
         if isinstance(field, models.DateTimeField):
             return cls(**kwargs)
+
+    def value(self, value, **kwargs):
+        if isinstance(value, datetime):
+            return value.isoformat(sep=" ")
+        return super().value(value=value, **kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/django_tables2/columns/linkcolumn.py 
new/django-tables2-2.9.0/django_tables2/columns/linkcolumn.py
--- old/django-tables2-2.8.0/django_tables2/columns/linkcolumn.py       
2025-11-21 11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/django_tables2/columns/linkcolumn.py       
2026-04-06 11:55:06.000000000 +0200
@@ -140,37 +140,3 @@
             ),
             **extra,
         )
-
-
[email protected]
-class RelatedLinkColumn(LinkColumn):
-    """
-    Render a link to a related object using related object's 
``get_absolute_url``,
-    same parameters as ``~.LinkColumn``.
-
-    .. note ::
-
-        This column should not be used anymore, the `linkify` keyword argument 
to
-        regular columns can be used achieve the same results.
-
-    If the related object does not have a method called ``get_absolute_url``,
-    or if it is not callable, the link will be rendered as '#'.
-
-    Traversing relations is also supported, suppose a Person has a foreign key 
to
-    Country which in turn has a foreign key to Continent::
-
-        class PersonTable(tables.Table):
-            name = tables.Column()
-            country = tables.RelatedLinkColumn()
-            continent = tables.RelatedLinkColumn(accessor="country.continent")
-
-    will render:
-
-     - in column 'country', link to ``person.country.get_absolute_url()`` with 
the output of
-       ``str(person.country)`` as ``<a>`` contents.
-     - in column 'continent', a link to 
``person.country.continent.get_absolute_url()`` with
-       the output of ``str(person.country.continent)`` as ``<a>`` contents.
-
-    Alternative contents of ``<a>`` can be supplied using the ``text`` keyword 
argument as
-    documented for `~.columns.LinkColumn`.
-    """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/django_tables2/columns/templatecolumn.py 
new/django-tables2-2.9.0/django_tables2/columns/templatecolumn.py
--- old/django-tables2-2.8.0/django_tables2/columns/templatecolumn.py   
2025-11-21 11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/django_tables2/columns/templatecolumn.py   
2026-04-06 11:55:06.000000000 +0200
@@ -1,7 +1,10 @@
+from collections.abc import Callable
+
 from django.template import Context, Template
 from django.template.loader import get_template
 from django.utils.html import strip_tags
 
+from ..utils import call_with_appropriate
 from .base import Column, library
 
 
@@ -13,7 +16,9 @@
     Arguments:
         template_code (str): template code to render
         template_name (str): name of the template to render
-        extra_context (dict): optional extra template context
+        context_object_name (str): name of the context variable that 
represents the record, defaults to "record".
+        extra_context (dict | callable): optional extra template context. Any 
callables passed will be called with the following
+            optional arguments: record, table, value, and bound_column.
 
     A `~django.template.Template` object is created from the
     *template_code* or *template_name* and rendered with a context containing:
@@ -36,38 +41,81 @@
                                         extra_context={"label": "Label"})
 
     Both columns will have the same output.
+
+    If you need more complex extra context, you can override the 
`get_context_data` method instead of passing
+    `extra_context`.
+
+    .. code-block:: python
+
+        class PriorityColumn(TemplateColumn):
+            template_name = "priority_column.html"
+            def get_context_data(self, record, **kwargs):
+                context = super().get_context_data(record=record, **kwargs)
+                context["overdue"] = record.due_date < date.today()
+                return context
     """
 
     empty_values = ()
 
-    def __init__(self, template_code=None, template_name=None, 
extra_context=None, **extra):
-        super().__init__(**extra)
-        self.template_code = template_code
-        self.template_name = template_name
+    def __init__(
+        self,
+        template_code: str | None = None,
+        template_name: str | None = None,
+        context_object_name: str | None = None,
+        extra_context: dict | Callable[..., dict] | None = None,
+        **kwargs,
+    ):
+        super().__init__(**kwargs)
+        self.template_code = template_code or getattr(self, "template_code", 
None)
+        self.template_name = template_name or getattr(self, "template_name", 
None)
         self.extra_context = extra_context or {}
+        self.context_object_name = context_object_name or getattr(
+            self, "context_object_name", "record"
+        )
 
-        if not self.template_code and not self.template_name:
+        if not getattr(self, "template_code", None) and not getattr(self, 
"template_name", None):
             raise ValueError("A template must be provided")
 
-    def render(self, record, table, value, bound_column, **kwargs):
-        # If the table is being rendered using `render_table`, it hackily
-        # attaches the context to the table as a gift to `TemplateColumn`.
-        context = getattr(table, "context", Context())
-        additional_context = {
+    def get_context_data(self, *, record, table, value, bound_column, 
**kwargs):
+        """
+        Generate the context data for rendering the template column template.
+
+        This context will be added to the parent template context if available.
+        """
+        context = {
             "default": bound_column.default,
             "column": bound_column,
-            "record": record,
+            self.context_object_name: record,
             "value": value,
             "row_counter": kwargs["bound_row"].row_counter,
         }
-        additional_context.update(self.extra_context)
-        with context.update(additional_context):
+
+        extra_context = self.extra_context
+        if callable(extra_context):
+            optional_kwargs = {
+                "record": record,
+                "table": table,
+                "value": value,
+                "bound_column": bound_column,
+            }
+            extra_context = call_with_appropriate(extra_context, 
optional_kwargs)
+        return context | extra_context
+
+    def render(self, table, **kwargs):
+        # If the table is being rendered using `render_table`, it hackily
+        # attaches the context to the table as a gift to `TemplateColumn`.
+        parent_context = getattr(table, "context", Context())
+
+        context = self.get_context_data(table=table, **kwargs)
+        with parent_context.update(context):
             request = getattr(table, "request", None)
             if self.template_code:
-                context["request"] = request
-                return Template(self.template_code).render(context)
+                parent_context["request"] = request
+                return Template(self.template_code).render(parent_context)
             else:
-                return 
get_template(self.template_name).render(context.flatten(), request=request)
+                return get_template(self.template_name).render(
+                    parent_context.flatten(), request=request
+                )
 
     def value(self, **kwargs):
         """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/django_tables2/columns/timecolumn.py 
new/django-tables2-2.9.0/django_tables2/columns/timecolumn.py
--- old/django-tables2-2.8.0/django_tables2/columns/timecolumn.py       
2025-11-21 11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/django_tables2/columns/timecolumn.py       
2026-04-06 11:55:06.000000000 +0200
@@ -1,3 +1,5 @@
+from datetime import time
+
 from django.db import models
 
 from .base import library
@@ -7,7 +9,7 @@
 @library.register
 class TimeColumn(TemplateColumn):
     """
-    A column that renders times in the local timezone.
+    A column that renders times in the local timezone and uses isoformat() 
when exporting.
 
     Arguments:
         format (str): format string in same format as Django's ``time`` 
template filter (optional).
@@ -24,3 +26,8 @@
     def from_field(cls, field, **kwargs):
         if isinstance(field, models.TimeField):
             return cls(**kwargs)
+
+    def value(self, value, **kwargs):
+        if isinstance(value, time):
+            return value.isoformat()
+        return super().value(value=value, **kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/django_tables2/templates/django_tables2/bootstrap.html 
new/django-tables2-2.9.0/django_tables2/templates/django_tables2/bootstrap.html
--- 
old/django-tables2-2.8.0/django_tables2/templates/django_tables2/bootstrap.html 
    2025-11-21 11:14:43.000000000 +0100
+++ 
new/django-tables2-2.9.0/django_tables2/templates/django_tables2/bootstrap.html 
    2026-04-06 11:55:06.000000000 +0200
@@ -11,7 +11,7 @@
                     {% for column in table.columns %}
                         <th {{ column.attrs.th.as_html }}>
                             {% if column.orderable %}
-                                <a href="{% querystring 
table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header 
}}</a>
+                                <a href="{% querystring_replace 
table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header 
}}</a>
                             {% else %}
                                 {{ column.header }}
                             {% endif %}
@@ -61,7 +61,7 @@
             {% if table.page.has_previous %}
                 {% block pagination.previous %}
                     <li class="previous">
-                        <a href="{% querystring 
table.prefixed_page_field=table.page.previous_page_number %}">
+                        <a href="{% querystring_replace 
table.prefixed_page_field=table.page.previous_page_number %}">
                             <span aria-hidden="true">&laquo;</span>
                             {% trans 'previous' %}
                         </a>
@@ -75,7 +75,7 @@
                             {% if p == '...' %}
                                 <a href="#">{{ p }}</a>
                             {% else %}
-                                <a href="{% querystring 
table.prefixed_page_field=p %}">
+                                <a href="{% querystring_replace 
table.prefixed_page_field=p %}">
                                     {{ p }}
                                 </a>
                             {% endif %}
@@ -87,7 +87,7 @@
             {% if table.page.has_next %}
                 {% block pagination.next %}
                 <li class="next">
-                    <a href="{% querystring 
table.prefixed_page_field=table.page.next_page_number %}">
+                    <a href="{% querystring_replace 
table.prefixed_page_field=table.page.next_page_number %}">
                         {% trans 'next' %}
                         <span aria-hidden="true">&raquo;</span>
                     </a>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/django_tables2/templates/django_tables2/bootstrap4.html
 
new/django-tables2-2.9.0/django_tables2/templates/django_tables2/bootstrap4.html
--- 
old/django-tables2-2.8.0/django_tables2/templates/django_tables2/bootstrap4.html
    2025-11-21 11:14:43.000000000 +0100
+++ 
new/django-tables2-2.9.0/django_tables2/templates/django_tables2/bootstrap4.html
    2026-04-06 11:55:06.000000000 +0200
@@ -11,7 +11,7 @@
                 {% for column in table.columns %}
                     <th {{ column.attrs.th.as_html }}>
                         {% if column.orderable %}
-                            <a href="{% querystring 
table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header 
}}</a>
+                            <a href="{% querystring_replace 
table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header 
}}</a>
                         {% else %}
                             {{ column.header }}
                         {% endif %}
@@ -61,7 +61,7 @@
             {% if table.page.has_previous %}
                 {% block pagination.previous %}
                 <li class="previous page-item">
-                    <a href="{% querystring 
table.prefixed_page_field=table.page.previous_page_number %}" class="page-link">
+                    <a href="{% querystring_replace 
table.prefixed_page_field=table.page.previous_page_number %}" class="page-link">
                         <span aria-hidden="true">&laquo;</span>
                         {% trans 'previous' %}
                     </a>
@@ -72,7 +72,7 @@
             {% block pagination.range %}
             {% for p in table.page|table_page_range:table.paginator %}
                 <li class="page-item{% if table.page.number == p %} active{% 
endif %}">
-                    <a class="page-link" {% if p != '...' %}href="{% 
querystring table.prefixed_page_field=p %}"{% endif %}>
+                    <a class="page-link" {% if p != '...' %}href="{% 
querystring_replace table.prefixed_page_field=p %}"{% endif %}>
                         {{ p }}
                     </a>
                 </li>
@@ -82,7 +82,7 @@
             {% if table.page.has_next %}
                 {% block pagination.next %}
                 <li class="next page-item">
-                    <a href="{% querystring 
table.prefixed_page_field=table.page.next_page_number %}" class="page-link">
+                    <a href="{% querystring_replace 
table.prefixed_page_field=table.page.next_page_number %}" class="page-link">
                         {% trans 'next' %}
                         <span aria-hidden="true">&raquo;</span>
                     </a>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/django_tables2/templates/django_tables2/bootstrap5.html
 
new/django-tables2-2.9.0/django_tables2/templates/django_tables2/bootstrap5.html
--- 
old/django-tables2-2.8.0/django_tables2/templates/django_tables2/bootstrap5.html
    2025-11-21 11:14:43.000000000 +0100
+++ 
new/django-tables2-2.9.0/django_tables2/templates/django_tables2/bootstrap5.html
    2026-04-06 11:55:06.000000000 +0200
@@ -11,7 +11,7 @@
                 {% for column in table.columns %}
                     <th {{ column.attrs.th.as_html }} scope="col">
                         {% if column.orderable %}
-                            <a href="{% querystring 
table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header 
}}</a>
+                            <a href="{% querystring_replace 
table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header 
}}</a>
                         {% else %}
                             {{ column.header }}
                         {% endif %}
@@ -61,7 +61,7 @@
             {% if table.page.has_previous %}
                 {% block pagination.previous %}
                 <li class="previous page-item">
-                    <a href="{% querystring 
table.prefixed_page_field=table.page.previous_page_number %}" class="page-link">
+                    <a href="{% querystring_replace 
table.prefixed_page_field=table.page.previous_page_number %}" class="page-link">
                         <span aria-hidden="true">&laquo;</span>
                         {% trans 'previous' %}
                     </a>
@@ -72,7 +72,7 @@
             {% block pagination.range %}
             {% for p in table.page|table_page_range:table.paginator %}
                 <li class="page-item{% if table.page.number == p %} active{% 
endif %}">
-                    <a class="page-link" {% if p != '...' %}href="{% 
querystring table.prefixed_page_field=p %}"{% endif %}>
+                    <a class="page-link" {% if p != '...' %}href="{% 
querystring_replace table.prefixed_page_field=p %}"{% endif %}>
                         {{ p }}
                     </a>
                 </li>
@@ -82,7 +82,7 @@
             {% if table.page.has_next %}
                 {% block pagination.next %}
                 <li class="next page-item">
-                    <a href="{% querystring 
table.prefixed_page_field=table.page.next_page_number %}" class="page-link">
+                    <a href="{% querystring_replace 
table.prefixed_page_field=table.page.next_page_number %}" class="page-link">
                         {% trans 'next' %}
                         <span aria-hidden="true">&raquo;</span>
                     </a>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/django_tables2/templates/django_tables2/semantic.html 
new/django-tables2-2.9.0/django_tables2/templates/django_tables2/semantic.html
--- 
old/django-tables2-2.8.0/django_tables2/templates/django_tables2/semantic.html  
    2025-11-21 11:14:43.000000000 +0100
+++ 
new/django-tables2-2.9.0/django_tables2/templates/django_tables2/semantic.html  
    2026-04-06 11:55:06.000000000 +0200
@@ -11,7 +11,7 @@
                     {% for column in table.columns %}
                         <th {{ column.attrs.th.as_html }}>
                             {% if column.orderable %}
-                                <a href="{% querystring 
table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header 
}}</a>
+                                <a href="{% querystring_replace 
table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header 
}}</a>
                             {% else %}
                                 {{ column.header }}
                             {% endif %}
@@ -56,7 +56,7 @@
                         <div class="ui right floated pagination menu">
                             {% if table.page.has_previous %}
                                 {% block pagination.previous %}
-                                <a href="{% querystring 
table.prefixed_page_field=table.page.previous_page_number %}" class="icon item">
+                                <a href="{% querystring_replace 
table.prefixed_page_field=table.page.previous_page_number %}" class="icon item">
                                     <i class="left chevron icon"></i>
                                 </a>
                                 {% endblock pagination.previous %}
@@ -68,7 +68,7 @@
                                         {% if p == '...' %}
                                             <a href="#" class="item">{{ p 
}}</a>
                                         {% else %}
-                                            <a href="{% querystring 
table.prefixed_page_field=p %}" class="item {% if p == table.page.number 
%}active{% endif %}">
+                                            <a href="{% querystring_replace 
table.prefixed_page_field=p %}" class="item {% if p == table.page.number 
%}active{% endif %}">
                                                 {{ p }}
                                             </a>
                                         {% endif %}
@@ -78,7 +78,7 @@
 
                             {% if table.page.has_next %}
                                 {% block pagination.next %}
-                                <a href="{% querystring 
table.prefixed_page_field=table.page.next_page_number %}" class="icon item">
+                                <a href="{% querystring_replace 
table.prefixed_page_field=table.page.next_page_number %}" class="icon item">
                                     <i class="right chevron icon"></i>
                                 </a>
                                 {% endblock pagination.next %}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/django_tables2/templates/django_tables2/table.html 
new/django-tables2-2.9.0/django_tables2/templates/django_tables2/table.html
--- old/django-tables2-2.8.0/django_tables2/templates/django_tables2/table.html 
2025-11-21 11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/django_tables2/templates/django_tables2/table.html 
2026-04-06 11:55:06.000000000 +0200
@@ -11,7 +11,7 @@
                     {% for column in table.columns %}
                         <th {{ column.attrs.th.as_html }}>
                             {% if column.orderable %}
-                                <a href="{% querystring 
table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header 
}}</a>
+                                <a href="{% querystring_replace 
table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header 
}}</a>
                             {% else %}
                                 {{ column.header }}
                             {% endif %}
@@ -60,7 +60,7 @@
             {% if table.page.has_previous %}
                 {% block pagination.previous %}
                     <li class="previous">
-                        <a href="{% querystring 
table.prefixed_page_field=table.page.previous_page_number %}">
+                        <a href="{% querystring_replace 
table.prefixed_page_field=table.page.previous_page_number %}">
                             {% trans 'previous' %}
                         </a>
                     </li>
@@ -73,7 +73,7 @@
                         {% if p == '...' %}
                             <a href="#">{{ p }}</a>
                         {% else %}
-                            <a href="{% querystring 
table.prefixed_page_field=p %}">
+                            <a href="{% querystring_replace 
table.prefixed_page_field=p %}">
                                 {{ p }}
                             </a>
                         {% endif %}
@@ -84,7 +84,7 @@
             {% if table.page.has_next %}
                 {% block pagination.next %}
                     <li class="next">
-                        <a href="{% querystring 
table.prefixed_page_field=table.page.next_page_number %}">
+                        <a href="{% querystring_replace 
table.prefixed_page_field=table.page.next_page_number %}">
                             {% trans 'next' %}
                         </a>
                     </li>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/django_tables2/templatetags/django_tables2.py 
new/django-tables2-2.9.0/django_tables2/templatetags/django_tables2.py
--- old/django-tables2-2.8.0/django_tables2/templatetags/django_tables2.py      
2025-11-21 11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/django_tables2/templatetags/django_tables2.py      
2026-04-06 11:55:06.000000000 +0200
@@ -44,7 +44,7 @@
     return kwargs
 
 
-class QuerystringNode(Node):
+class QuerystringReplaceNode(Node):
     def __init__(self, updates, removals, asvar=None):
         super().__init__()
         self.updates = updates
@@ -53,7 +53,7 @@
 
     def render(self, context):
         if "request" not in context:
-            raise ImproperlyConfigured(context_processor_error_msg % 
"querystring")
+            raise ImproperlyConfigured(context_processor_error_msg % 
"querystring_replace")
 
         params = dict(context["request"].GET)
         for key, value in self.updates.items():
@@ -76,9 +76,9 @@
             return value
 
 
-# {% querystring "name"="abc" "age"=15 as=qs %}
+# {% querystring_replace "name"="abc" "age"=15 as=qs %}
 @register.tag
-def querystring(parser, token):
+def querystring_replace(parser, token):
     """
     Create an URL (containing only the query string [including "?"]) 
derivedfrom the current URL's query string.
 
@@ -86,11 +86,11 @@
 
     Example (imagine URL is ``/abc/?gender=male&name=Brad``)::
 
-        # {% querystring "name"="abc" "age"=15 %}
+        # {% querystring_replace "name"="abc" "age"=15 %}
         ?name=abc&gender=male&age=15
-        {% querystring "name"="Ayers" "age"=20 %}
+        {% querystring_replace "name"="Ayers" "age"=20 %}
         ?name=Ayers&gender=male&age=20
-        {% querystring "name"="Ayers" without "gender" %}
+        {% querystring_replace "name"="Ayers" without "gender" %}
         ?name=Ayers
     """
     bits = token.split_contents()
@@ -113,7 +113,7 @@
     if bits and bits.pop(0) != "without":
         raise TemplateSyntaxError(f"Malformed arguments to '{tag}'")
     removals = [parser.compile_filter(bit) for bit in bits]
-    return QuerystringNode(updates, removals, asvar=asvar)
+    return QuerystringReplaceNode(updates, removals, asvar=asvar)
 
 
 class RenderTableNode(Node):
@@ -230,9 +230,9 @@
 
     export_trigger_param = export_trigger_param or "_export"
 
-    return QuerystringNode(updates={export_trigger_param: export_format}, 
removals=[]).render(
-        context
-    )
+    return QuerystringReplaceNode(
+        updates={export_trigger_param: export_format}, removals=[]
+    ).render(context)
 
 
 @register.filter
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/django_tables2/utils.py 
new/django-tables2-2.9.0/django_tables2/utils.py
--- old/django-tables2-2.8.0/django_tables2/utils.py    2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/django_tables2/utils.py    2026-04-06 
11:55:06.000000000 +0200
@@ -1,4 +1,5 @@
 import inspect
+import sys
 import warnings
 from collections import OrderedDict
 from functools import total_ordering
@@ -8,6 +9,13 @@
 from django.db import models
 from django.utils.html import format_html_join
 
+if sys.version_info >= (3, 14):
+    from annotationlib import Format
+
+    SIGNATURE_KWARGS = {"annotation_format": Format.STRING}
+else:
+    SIGNATURE_KWARGS = {}
+
 
 class Sequence(list):
     """
@@ -526,7 +534,7 @@
 
     The self-argument for methods is always removed.
     """
-    signature = inspect.signature(fn)
+    signature = inspect.signature(fn, **SIGNATURE_KWARGS)
 
     args = []
     keywords = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/django_tables2/views.py 
new/django-tables2-2.9.0/django_tables2/views.py
--- old/django-tables2-2.8.0/django_tables2/views.py    2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/django_tables2/views.py    2026-04-06 
11:55:06.000000000 +0200
@@ -1,5 +1,5 @@
 from itertools import count
-from typing import Any, Optional
+from typing import Any
 
 from django.core.exceptions import ImproperlyConfigured
 from django.views.generic.list import ListView
@@ -58,7 +58,7 @@
 
         return paginate
 
-    def get_paginate_by(self, table_data) -> Optional[int]:
+    def get_paginate_by(self, table_data) -> int | None:
         """
         Determine the number of items per page, or ``None`` for no pagination.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/docs/pages/api-reference.rst 
new/django-tables2-2.9.0/docs/pages/api-reference.rst
--- old/django-tables2-2.8.0/docs/pages/api-reference.rst       2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/docs/pages/api-reference.rst       2026-04-06 
11:55:06.000000000 +0200
@@ -323,13 +323,6 @@
 .. autoclass:: django_tables2.columns.ManyToManyColumn
     :members:
 
-`.RelatedLinkColumn`
-~~~~~~~~~~~~~~~~~~~~
-
-.. autoclass:: django_tables2.columns.RelatedLinkColumn
-    :members:
-
-
 `.TemplateColumn`
 ~~~~~~~~~~~~~~~~~
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/docs/pages/builtin-columns.rst 
new/django-tables2-2.9.0/docs/pages/builtin-columns.rst
--- old/django-tables2-2.8.0/docs/pages/builtin-columns.rst     2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/docs/pages/builtin-columns.rst     2026-04-06 
11:55:06.000000000 +0200
@@ -15,7 +15,6 @@
 - `.JSONColumn` -- renders JSON as an indented string in ``<pre></pre>``
 - `.LinkColumn` -- renders ``<a href="...">`` tags (compose a Django URL)
 - `.ManyToManyColumn` -- renders a list objects from a `ManyToManyField`
-- `.RelatedLinkColumn` -- renders ``<a href="...">`` tags linking related 
objects
 - `.TemplateColumn` -- renders template code
 - `.TimeColumn` -- time formatting
 - `.URLColumn` -- renders ``<a href="...">`` tags (absolute URL)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/docs/pages/faq.rst 
new/django-tables2-2.9.0/docs/pages/faq.rst
--- old/django-tables2-2.8.0/docs/pages/faq.rst 2025-11-21 11:14:43.000000000 
+0100
+++ new/django-tables2-2.9.0/docs/pages/faq.rst 2026-04-06 11:55:06.000000000 
+0200
@@ -17,7 +17,7 @@
 
 The error message looks something like this::
 
-    Tag {% querystring %} requires django.template.context_processors.request 
to be
+    Tag {% querystring_replace %} requires 
django.template.context_processors.request to be
     in the template configuration in 
settings.TEMPLATES[]OPTIONS.context_processors)
     in order for the included template tags to function correctly.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/docs/pages/template-tags.rst 
new/django-tables2-2.9.0/docs/pages/template-tags.rst
--- old/django-tables2-2.8.0/docs/pages/template-tags.rst       2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/docs/pages/template-tags.rst       2026-04-06 
11:55:06.000000000 +0200
@@ -36,10 +36,16 @@
 
 .. _TEMPLATES-setting: 
https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-TEMPLATES
 
-.. _template-tags.querystring:
+.. _template-tags.querystring_replace:
 
-``querystring``
----------------
+querystring_replace
+-------------------
+
+.. note::
+
+    This tag has been renamed from `querystring` to avoid shadowing the `Django
+    template tag 
<https://docs.djangoproject.com/en/dev/ref/templates/builtins/#querystring>`
+    with the same name.
 
 A utility that allows you to update a portion of the query-string without
 overwriting the entire thing.
@@ -49,12 +55,12 @@
 
 .. sourcecode:: django
 
-    {% querystring "sort"="dob" %}           # ?search=pirates&sort=dob&page=5
-    {% querystring "sort"="" %}              # ?search=pirates&page=5
-    {% querystring "sort"="" "search"="" %}  # ?page=5
+    {% querystring_replace "sort"="dob" %}           # 
?search=pirates&sort=dob&page=5
+    {% querystring_replace "sort"="" %}              # ?search=pirates&page=5
+    {% querystring_replace "sort"="" "search"="" %}  # ?page=5
 
-    {% with "search" as key %}               # supports variables as keys
-    {% querystring key="robots" %}           # ?search=robots&page=5
+    {% with "search" as key %}                       # supports variables as 
keys
+    {% querystring_replace key="robots" %}           # ?search=robots&page=5
     {% endwith %}
 
 This tag requires the ``django.template.context_processors.request`` context
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/example/app/tables.py 
new/django-tables2-2.9.0/example/app/tables.py
--- old/django-tables2-2.8.0/example/app/tables.py      2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/example/app/tables.py      2026-04-06 
11:55:06.000000000 +0200
@@ -73,7 +73,7 @@
 
 
 class SemanticTable(tables.Table):
-    country = tables.RelatedLinkColumn()
+    country = tables.Column(linkify=True)
 
     class Meta:
         model = Person
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/maintenance.py 
new/django-tables2-2.9.0/maintenance.py
--- old/django-tables2-2.8.0/maintenance.py     2025-11-21 11:14:43.000000000 
+0100
+++ new/django-tables2-2.9.0/maintenance.py     2026-04-06 11:55:06.000000000 
+0200
@@ -20,7 +20,6 @@
 
 elif sys.argv[-1] == "publish":
     os.system("hatch publish")
-    os.system("rm -f dist/django_tables2-2.7.4*")
 
 elif sys.argv[-1] == "tag":
     os.system("hatch build")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/pyproject.toml 
new/django-tables2-2.9.0/pyproject.toml
--- old/django-tables2-2.8.0/pyproject.toml     2025-11-21 11:14:43.000000000 
+0100
+++ new/django-tables2-2.9.0/pyproject.toml     2026-04-06 11:55:06.000000000 
+0200
@@ -21,7 +21,6 @@
   "Programming Language :: Python",
   "Programming Language :: Python :: 3",
   "Programming Language :: Python :: 3 :: Only",
-  "Programming Language :: Python :: 3.9",
   "Programming Language :: Python :: 3.10",
   "Programming Language :: Python :: 3.11",
   "Programming Language :: Python :: 3.12",
@@ -36,7 +35,7 @@
 license = {file = "LICENSE"}
 name = "django-tables2"
 readme = "README.md"
-requires-python = ">=3.9"
+requires-python = ">=3.10"
 
 [project.optional-dependencies]
 tablib = ["tablib"]
@@ -58,6 +57,7 @@
 
 [tool.ruff]
 line-length = 100
+target-version = "py311"
 
 [tool.ruff.lint]
 fixable = [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/tests/app/templates/minimal.html 
new/django-tables2-2.9.0/tests/app/templates/minimal.html
--- old/django-tables2-2.8.0/tests/app/templates/minimal.html   2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/tests/app/templates/minimal.html   2026-04-06 
11:55:06.000000000 +0200
@@ -13,7 +13,7 @@
         </tr>
     {% endfor %}
     {% if table.page.has_next %}
-        <a href="{% querystring 
table.prefixed_page_field=table.page.next_page_number %}">
+        <a href="{% querystring_replace 
table.prefixed_page_field=table.page.next_page_number %}">
             next
         </a>
     {% endif %}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/tests/columns/test_datecolumn.py 
new/django-tables2-2.9.0/tests/columns/test_datecolumn.py
--- old/django-tables2-2.8.0/tests/columns/test_datecolumn.py   2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/tests/columns/test_datecolumn.py   2026-04-06 
11:55:06.000000000 +0200
@@ -78,7 +78,7 @@
             date_linkify = tables.DateColumn(accessor="date", 
linkify=isoformat_link)
 
         table = Table([{"date": date(2012, 9, 12)}])
-        self.assertEqual(table.rows[0].get_cell_value("date"), "09/12/2012")
+        self.assertEqual(table.rows[0].get_cell_value("date"), "2012-09-12")
         self.assertEqual(
             table.rows[0].get_cell("date_linkify"), '<a 
href="/test/2012-09-12/">09/12/2012</a>'
         )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/tests/columns/test_datetimecolumn.py 
new/django-tables2-2.9.0/tests/columns/test_datetimecolumn.py
--- old/django-tables2-2.8.0/tests/columns/test_datetimecolumn.py       
2025-11-21 11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/tests/columns/test_datetimecolumn.py       
2026-04-06 11:55:06.000000000 +0200
@@ -86,4 +86,4 @@
             col = tables.DateTimeColumn()
 
         table = Table([{"col": self.dt()}])
-        self.assertEqual(table.rows[0].get_cell_value("col"), "09/11/2012 
12:30 p.m.")
+        self.assertEqual(table.rows[0].get_cell_value("col"), "2012-09-11 
12:30:00+02:00")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/tests/columns/test_linkcolumn.py 
new/django-tables2-2.9.0/tests/columns/test_linkcolumn.py
--- old/django-tables2-2.8.0/tests/columns/test_linkcolumn.py   2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/tests/columns/test_linkcolumn.py   2026-04-06 
11:55:06.000000000 +0200
@@ -6,7 +6,7 @@
 import django_tables2 as tables
 from django_tables2 import A
 
-from ..app.models import Occupation, Person
+from ..app.models import Person
 from ..utils import attrs, build_request
 
 
@@ -40,7 +40,7 @@
             first_name = tables.LinkColumn("person", text="foo::bar", 
args=[A("pk")])
             last_name = tables.LinkColumn(
                 "person",
-                text=lambda row: f'{row["last_name"]} {row["first_name"]}',
+                text=lambda row: f"{row['last_name']} {row['first_name']}",
                 args=[A("pk")],
             )
 
@@ -107,13 +107,9 @@
             name_linkify = tables.Column(accessor="name", linkify=("escaping", 
{"pk": A("pk")}))
 
         table = PersonTable([{"name": "<brad>", "pk": 1}])
-        # django==3.0 replaces &#39; with &#x27;, drop first option if 
django==2.2 support is removed
-        self.assertIn(
-            table.rows[0].get_cell("name"),
-            (
-                '<a href="/&amp;&#39;%22/1/">&lt;brad&gt;</a>'
-                '<a href="/&amp;&#x27;%22/1/">&lt;brad&gt;</a>'
-            ),
+
+        self.assertEqual(
+            table.rows[0].get_cell("name"), '<a 
href="/&amp;&#x27;%22/1/">&lt;brad&gt;</a>'
         )
 
         # the two columns should result in the same rendered cell contents
@@ -199,29 +195,6 @@
         with self.assertRaisesMessage(TypeError, message):
             table.as_html(build_request())
 
-    def test_RelatedLinkColumn(self):
-        carpenter = Occupation.objects.create(name="Carpenter")
-        Person.objects.create(first_name="Bob", last_name="Builder", 
occupation=carpenter)
-
-        class Table(tables.Table):
-            occupation = tables.RelatedLinkColumn()
-            occupation_linkify = tables.Column(accessor="occupation", 
linkify=True)
-
-        table = Table(Person.objects.all())
-
-        url = reverse("occupation", args=[carpenter.pk])
-        self.assertEqual(table.rows[0].cells["occupation"], f'<a 
href="{url}">Carpenter</a>')
-
-    def test_RelatedLinkColumn_without_model(self):
-        class Table(tables.Table):
-            occupation = tables.RelatedLinkColumn()
-
-        table = Table([{"occupation": "Fabricator"}])
-
-        message = "for linkify=True, 'Fabricator' must have a method 
get_absolute_url"
-        with self.assertRaisesMessage(TypeError, message):
-            table.rows[0].cells["occupation"]
-
     def test_value_returns_a_raw_value_without_html(self):
         class Table(tables.Table):
             col = tables.LinkColumn("occupation", args=(A("id"),))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tables2-2.8.0/tests/columns/test_templatecolumn.py 
new/django-tables2-2.9.0/tests/columns/test_templatecolumn.py
--- old/django-tables2-2.8.0/tests/columns/test_templatecolumn.py       
2025-11-21 11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/tests/columns/test_templatecolumn.py       
2026-04-06 11:55:06.000000000 +0200
@@ -115,6 +115,72 @@
 
         self.assertEqual(list(table.as_values()), [["Track"], ["Space 
Oddity"]])
 
+    def test_context_object_name(self):
+        class Table(tables.Table):
+            name = tables.TemplateColumn("{{ user.name }}", 
context_object_name="user")
+
+        table = Table([{"name": "Bob"}])
+        self.assertEqual(list(table.as_values()), [["Name"], ["Bob"]])
+
+    def test_extra_context_dict(self):
+        class Table(tables.Table):
+            clothes__size = tables.TemplateColumn(
+                "{{ filter }}: {{ value }}", verbose_name="Size", 
extra_context={"filter": "size"}
+            )
+
+        table = Table([{"clothes": {"size": "XL"}}])
+        self.assertEqual(list(table.as_values()), [["Size"], ["size: XL"]])
+
+    def test_extra_context_callable(self):
+        class Table(tables.Table):
+            size = tables.TemplateColumn(
+                "{{ size }}", extra_context=lambda record: {"size": 
record["clothes"]["size"]}
+            )
+            clothes__size = tables.TemplateColumn(
+                "{{ size }}",
+                verbose_name="Clothes Size",
+                extra_context=lambda value: {"size": f"size: {value}"},
+            )
+
+        table = Table([{"clothes": {"size": "XL"}}])
+        self.assertEqual(list(table.as_values()), [["Size", "Clothes Size"], 
["XL", "size: XL"]])
+
+    def test_class_attribute_template_code(self):
+        class MyColumn(tables.TemplateColumn):
+            template_code = "value={{ value }}"
+
+        class Table(tables.Table):
+            foo = MyColumn()
+            bar = MyColumn(template_code="explicit={{ value }}")
+
+        table = Table([{"foo": "bar", "bar": "baz"}])
+        self.assertEqual(table.rows[0].get_cell("foo"), "value=bar")
+        self.assertEqual(table.rows[0].get_cell("bar"), "explicit=baz")
+
+    def test_class_attribute_template_name(self):
+        class MyColumn(tables.TemplateColumn):
+            template_name = "test_template_column.html"
+
+        class Table(tables.Table):
+            col = MyColumn()
+            col2 = MyColumn(template_name="column.html")
+
+        table = Table([{"col": "brad", "col2": "brad"}])
+        self.assertEqual(table.rows[0].get_cell("col"), "name:brad-empty\n")
+        self.assertNotEqual(table.rows[0].get_cell("col2"), 
"name:brad-empty\n")
+
+    def test_class_attribute_context_object_name(self):
+        class MyColumn(tables.TemplateColumn):
+            template_code = "{{ user.name }}"
+            context_object_name = "user"
+
+        class Table(tables.Table):
+            name = MyColumn()
+            name2 = MyColumn(context_object_name="record")
+
+        table = Table([{"name": "Bob", "name2": "Bob"}])
+        self.assertEqual(list(table.as_values()), [["Name", "Name2"], ["Bob", 
""]])
+
     def test_request_passthrough(self):
         class Table(tables.Table):
             track = tables.TemplateColumn(template_code="{{ request.path }}")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/tests/test_export.py 
new/django-tables2-2.9.0/tests/test_export.py
--- old/django-tables2-2.8.0/tests/test_export.py       2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/tests/test_export.py       2026-04-06 
11:55:06.000000000 +0200
@@ -278,20 +278,26 @@
             def get_queryset(self):
                 return [
                     {
-                        "date": date(2019, 7, 22),
+                        "date": date(2019, 5, 21),
                         "time": time(11, 11, 11),
-                        "datetime": utc.localize(datetime(2019, 7, 22, 11, 11, 
11)),
+                        "datetime": utc.localize(datetime(2019, 7, 22, 11, 12, 
13)),
                     }
                 ]
 
         response = View.as_view()(build_request("/?_export=csv"))
         data = response.getvalue().decode("utf8")
 
-        expected_csv = "Date,Time,Datetime\r\n07/22/2019,11:11 a.m.,07/22/2019 
1:11 p.m.\r\n"
+        expected_csv = "Date,Time,Datetime\r\n{},{},{}\r\n".format(
+            "2019-05-21",
+            "11:11:11",
+            "2019-07-22 11:12:13+00:00",
+        )
         self.assertEqual(data, expected_csv)
 
         response = View.as_view()(build_request("/?_export=xls"))
-        self.assertIn(b"07/22/2019 1:11 p.m.", response.content)
+        self.assertIn(b"2019-05-21", response.content)
+        self.assertIn(b"11:11:11", response.content)
+        self.assertIn(b"2019-07-22 11:12:13+00:00", response.content)
 
     def test_export_invisible_columns(self):
         """Verify columns with visible=False *do* get exported."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/tests/test_extra_columns.py 
new/django-tables2-2.9.0/tests/test_extra_columns.py
--- old/django-tables2-2.8.0/tests/test_extra_columns.py        2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/tests/test_extra_columns.py        2026-04-06 
11:55:06.000000000 +0200
@@ -176,7 +176,7 @@
 
                 if add_occupation_column:
                     kwargs["extra_columns"].append(
-                        ("occupation", 
tables.RelatedLinkColumn(orderable=False))
+                        ("occupation", tables.Column(linkify=True, 
orderable=False))
                     )
 
                 super().__init__(data, *args, **kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/tests/test_faq.py 
new/django-tables2-2.9.0/tests/test_faq.py
--- old/django-tables2-2.8.0/tests/test_faq.py  2025-11-21 11:14:43.000000000 
+0100
+++ new/django-tables2-2.9.0/tests/test_faq.py  2026-04-06 11:55:06.000000000 
+0200
@@ -32,7 +32,7 @@
         class CountryTable(tables.Table):
             name = tables.Column()
             population = tables.Column(
-                footer=lambda table: f'Total: {sum(x["population"] for x in 
table.data)}'
+                footer=lambda table: f"Total: {sum(x['population'] for x in 
table.data)}"
             )
 
         table = CountryTable(TEST_DATA)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/tests/test_templates.py 
new/django-tables2-2.9.0/tests/test_templates.py
--- old/django-tables2-2.8.0/tests/test_templates.py    2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/tests/test_templates.py    2026-04-06 
11:55:06.000000000 +0200
@@ -90,9 +90,9 @@
 
         # automatic and manual column verbose names
         template = Template(
-            "{% for column in countries.columns %}{{ column }}/" "{{ 
column.name }} {% endfor %}"
+            "{% for column in countries.columns %}{{ column }}/{{ column.name 
}} {% endfor %}"
         )
-        result = "Name/name Capital/capital Population Size/population " 
"Phone Ext./calling_code "
+        result = "Name/name Capital/capital Population Size/population Phone 
Ext./calling_code "
         assert result == template.render(context)
 
         # row values
@@ -100,7 +100,7 @@
             "{% for row in countries.rows %}{% for value in row %}"
             "{{ value }} {% endfor %}{% endfor %}"
         )
-        result = "Germany Berlin 83 49 France — 64 33 Netherlands Amsterdam " 
"— 31 Austria — 8 43 "
+        result = "Germany Berlin 83 49 France — 64 33 Netherlands Amsterdam — 
31 Austria — 8 43 "
         assert result == template.render(context)
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/tests/test_templatetags.py 
new/django-tables2-2.9.0/tests/test_templatetags.py
--- old/django-tables2-2.8.0/tests/test_templatetags.py 2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/tests/test_templatetags.py 2026-04-06 
11:55:06.000000000 +0200
@@ -110,13 +110,13 @@
 
     def test_should_support_template_argument(self):
         table = CountryTable(MEMORY_DATA, order_by=("name", "population"))
-        template = Template("{% load django_tables2 %}" '{% render_table table 
"dummy.html" %}')
+        template = Template('{% load django_tables2 %}{% render_table table 
"dummy.html" %}')
 
         context = RequestContext(build_request(), {"table": table})
         self.assertEqual(template.render(context), "dummy template contents\n")
 
     def test_template_argument_list(self):
-        template = Template("{% load django_tables2 %}" "{% render_table table 
template_list %}")
+        template = Template("{% load django_tables2 %}{% render_table table 
template_list %}")
 
         context = RequestContext(
             build_request(),
@@ -144,10 +144,10 @@
         self.assertEqual(td, db)
 
 
-class QuerystringTagTest(SimpleTestCase):
+class QuerystringReplaceTagTest(SimpleTestCase):
     def test_basic(self):
         template = Template(
-            "{% load django_tables2 %}" '<b>{% querystring "name"="Brad" 
foo.bar=value %}</b>'
+            '{% load django_tables2 %}<b>{% querystring_replace "name"="Brad" 
foo.bar=value %}</b>'
         )
 
         # Should be something like: 
<root>?name=Brad&amp;a=b&amp;c=5&amp;age=21</root>
@@ -167,8 +167,10 @@
         self.assertEqual(qs["c"], ["5"])
 
     def test_requires_request(self):
-        template = Template('{% load django_tables2 %}{% querystring 
"name"="Brad" %}')
-        message = "Tag {% querystring %} requires 
django.template.context_processors.request to "
+        template = Template('{% load django_tables2 %}{% querystring_replace 
"name"="Brad" %}')
+        message = (
+            "Tag {% querystring_replace %} requires 
django.template.context_processors.request to "
+        )
         with self.assertRaisesMessage(ImproperlyConfigured, message):
             template.render(Context())
 
@@ -176,7 +178,7 @@
         context = Context({"request": build_request("/?a=b&name=dog&c=5"), 
"a_var": "a"})
 
         template = Template(
-            "{% load django_tables2 %}" '<b>{% querystring "name"="Brad" 
without a_var %}</b>'
+            '{% load django_tables2 %}<b>{% querystring_replace "name"="Brad" 
without a_var %}</b>'
         )
         url = parse(template.render(context)).text
         qs = parse_qs(url[1:])  # trim the ?
@@ -185,21 +187,23 @@
     def test_only_without(self):
         context = Context({"request": build_request("/?a=b&name=dog&c=5"), 
"a_var": "a"})
         template = Template(
-            "{% load django_tables2 %}" '<b>{% querystring without "a" "name" 
%}</b>'
+            '{% load django_tables2 %}<b>{% querystring_replace without "a" 
"name" %}</b>'
         )
         url = parse(template.render(context)).text
         qs = parse_qs(url[1:])  # trim the ?
         self.assertEqual(set(qs.keys()), {"c"})
 
-    def test_querystring_syntax_error(self):
-        with self.assertRaisesMessage(TemplateSyntaxError, "Malformed 
arguments to 'querystring'"):
-            Template("{% load django_tables2 %}{% querystring foo= %}")
+    def test_querystring_replace_syntax_error(self):
+        with self.assertRaisesMessage(
+            TemplateSyntaxError, "Malformed arguments to 'querystring_replace'"
+        ):
+            Template("{% load django_tables2 %}{% querystring_replace foo= %}")
 
-    def test_querystring_as_var(self):
-        def assert_querystring_asvar(template_code, expected):
+    def test_querystring_replace_as_var(self):
+        def assert_querystring_replace_asvar(template_code, expected):
             template = Template(
                 "{% load django_tables2 %}"
-                "<b>{% querystring " + template_code + " %}</b>"
+                "<b>{% querystring_replace " + template_code + " %}</b>"
                 "<strong>{{ varname }}</strong>"
             )
 
@@ -218,7 +222,7 @@
         )
 
         for argstr, expected in tests:
-            assert_querystring_asvar(argstr, expected)
+            assert_querystring_replace_asvar(argstr, expected)
 
     def test_export_url_tag(self):
         class View(ExportMixin):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/tests/test_utils.py 
new/django-tables2-2.9.0/tests/test_utils.py
--- old/django-tables2-2.8.0/tests/test_utils.py        2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/tests/test_utils.py        2026-04-06 
11:55:06.000000000 +0200
@@ -1,3 +1,6 @@
+import sys
+from typing import TYPE_CHECKING
+
 from django.db import models
 from django.test import TestCase
 
@@ -165,10 +168,9 @@
 
 class AttributeDictTest(TestCase):
     def test_handles_escaping(self):
-        # django==3.0 replaces &#39; with &#x27;, drop first option if 
django==2.2 support is removed
-        self.assertIn(
+        self.assertEqual(
             AttributeDict({"x": "\"'x&"}).as_html(),
-            ('x="&quot;&#39;x&amp;"', 'x="&quot;&#x27;x&amp;"'),
+            'x="&quot;&#x27;x&amp;"',
         )
 
     def test_omits_None(self):
@@ -255,6 +257,24 @@
         assert args == ("bar", "baz")
         assert keywords == "kwargs"
 
+    def test_signature_with_type_hints(self):
+        # Python 3.14+
+        if sys.version_info >= (3, 14):
+            if TYPE_CHECKING:
+                from typing import Tuple  # noqa: UP035
+
+            def foo(x: Tuple[int, ...]) -> None:  # noqa: UP006
+                pass
+        else:
+            from typing import Tuple  # noqa: UP035
+
+            def foo(x: Tuple[int, ...]) -> None:  # noqa: UP006
+                pass
+
+        args, keywords = signature(foo)
+        assert args == ("x",)
+        assert keywords is None
+
 
 class CallWithAppropriateTest(TestCase):
     def test_basic(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tables2-2.8.0/tests/test_views.py 
new/django-tables2-2.9.0/tests/test_views.py
--- old/django-tables2-2.8.0/tests/test_views.py        2025-11-21 
11:14:43.000000000 +0100
+++ new/django-tables2-2.9.0/tests/test_views.py        2026-04-06 
11:55:06.000000000 +0200
@@ -1,5 +1,4 @@
 from math import ceil
-from typing import Optional
 
 import django_filters as filters
 from django.core.exceptions import ImproperlyConfigured
@@ -480,7 +479,7 @@
             tables = (TableB, TableB)
             tables_data = (Region.objects.all(), Region.objects.all())
 
-            def get_paginate_by(self, table_data) -> Optional[int]:
+            def get_paginate_by(self, table_data) -> int | None:
                 # Split data into 3 pages
                 return ceil(len(table_data) / 3)
 

Reply via email to