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">«</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">»</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">«</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">»</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">«</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">»</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 ' with ', drop first option if
django==2.2 support is removed
- self.assertIn(
- table.rows[0].get_cell("name"),
- (
- '<a href="/&'%22/1/"><brad></a>'
- '<a href="/&'%22/1/"><brad></a>'
- ),
+
+ self.assertEqual(
+ table.rows[0].get_cell("name"), '<a
href="/&'%22/1/"><brad></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&a=b&c=5&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 ' with ', drop first option if
django==2.2 support is removed
- self.assertIn(
+ self.assertEqual(
AttributeDict({"x": "\"'x&"}).as_html(),
- ('x=""'x&"', 'x=""'x&"'),
+ 'x=""'x&"',
)
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)