Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 17 additions & 27 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name: Tests
on:
push:
branches:
- main
- master
pull_request:

jobs:
Expand All @@ -13,72 +13,62 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Ensure latest setuptools
run: |
python -m pip install --upgrade pip setuptools
- name: Install dependencies
run: |
python -m pip install coverage tox tox-py unittest-xml-reporting
python -m pip install --upgrade pip
python -m pip install coverage tox unittest-xml-reporting
- name: Run tox
run: |
python -m pip --version
python -m tox --version
python -m tox --py current
python -m tox run -f py$(echo ${{ matrix.python-version }} | tr -d .)
- name: Coverage reporting
run: |
coverage combine
coverage report -m
coverage xml
coverage html
- name: Publish coverage results
uses: codecov/codecov-action@v1


isort:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Ensure latest setuptools
run: |
python -m pip install --upgrade pip setuptools
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install tox
- name: Run tox
run: |
python -m pip --version
python -m tox --version
python -m tox -e isort,lint,docs
python -m tox run -e isort,lint,docs


warnings:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Ensure latest setuptools
run: |
python -m pip install --upgrade pip setuptools
python-version: "3.13"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install tox
- name: Run tox
run: |
python -m pip --version
python -m tox --version
python -m tox -e warnings
python -m tox run -e warnings
39 changes: 27 additions & 12 deletions django_filters/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
RangeWidget,
)

try:
from django.utils.choices import BaseChoiceIterator, normalize_choices
except ImportError:
DJANGO_50 = False
else:
DJANGO_50 = True


class RangeField(forms.MultiValueField):
widget = RangeWidget
Expand Down Expand Up @@ -210,7 +217,7 @@ def clean(self, value):
return value


class ChoiceIterator:
class ChoiceIterator(BaseChoiceIterator if DJANGO_50 else object):
# Emulates the behavior of ModelChoiceIterator, but instead wraps
# the field's _choices iterable.

Expand All @@ -223,7 +230,10 @@ def __iter__(self):
yield ("", self.field.empty_label)
if self.field.null_label is not None:
yield (self.field.null_value, self.field.null_label)
yield from self.choices
if DJANGO_50:
yield from normalize_choices(self.choices)
else:
yield from self.choices

def __len__(self):
add = 1 if self.field.empty_label is not None else 0
Expand Down Expand Up @@ -257,16 +267,21 @@ def __init__(self, *args, **kwargs):

super().__init__(*args, **kwargs)

def _get_choices(self):
return super()._get_choices()

def _set_choices(self, value):
super()._set_choices(value)
value = self.iterator(self, self._choices)

self._choices = self.widget.choices = value

choices = property(_get_choices, _set_choices)
@property
def choices(self):
return super().choices

@choices.setter
def choices(self, value):
if DJANGO_50:
value = self.iterator(self, value)
# Simple `super()` syntax for calling a parent property setter is
# unsupported. See https://github.com/python/cpython/issues/59170
super(ChoiceIteratorMixin, self.__class__).choices.__set__(self, value)
else:
super()._set_choices(value)
value = self.iterator(self, self._choices)
self._choices = self.widget.choices = value


# Unlike their Model* counterparts, forms.ChoiceField and forms.MultipleChoiceField do not set empty_label
Expand Down
3 changes: 1 addition & 2 deletions django_filters/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from django.db.models import Q
from django.db.models.constants import LOOKUP_SEP
from django.forms.utils import pretty_name
from django.utils.itercompat import is_iterable
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _

Expand All @@ -27,7 +26,7 @@
RangeField,
TimeRangeField,
)
from .utils import get_model_field, label_for_filter
from .utils import get_model_field, is_iterable, label_for_filter

__all__ = [
"AllValuesFilter",
Expand Down
10 changes: 10 additions & 0 deletions django_filters/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@
from .exceptions import FieldLookupError


def is_iterable(x):
# Replacement for django.utils.itercompat.is_iterable, deprecated in
# Django 5.1 and removed in 6.0.
try:
iter(x)
except TypeError:
return False
return True


def deprecate(msg, level_modifier=0):
warnings.warn(msg, MigrationNotice, stacklevel=3 + level_modifier)

Expand Down
5 changes: 2 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@

# only import and set the theme if we're building docs locally
if not on_rtd:
import sphinx_rtd_theme

# sphinx_rtd_theme >= 1.0 registers itself with Sphinx;
# get_html_theme_path() is deprecated and warns under -W.
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
9 changes: 5 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,19 @@
"Operating System :: OS Independent",
"Framework :: Django",
"Framework :: Django :: 3.2",
"Framework :: Django :: 4.0",
"Framework :: Django :: 4.1",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.2",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
],
zip_safe=False,
python_requires=">=3.7",
python_requires=">=3.8",
install_requires=[
"Django>=3.2",
],
Expand Down
9 changes: 9 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import django.test

# Django 4.2 renamed assertQuerysetEqual to assertQuerySetEqual, and Django
# 5.1 removed the old alias. Tests use the new name; alias it on Django < 4.2
# so the full support matrix (3.2/4.2/5.2) runs the same test code.
if not hasattr(django.test.TransactionTestCase, "assertQuerySetEqual"):
django.test.TransactionTestCase.assertQuerySetEqual = (
django.test.TransactionTestCase.assertQuerysetEqual
)
Loading
Loading