diff --git a/django_filters/filters.py b/django_filters/filters.py index a024e9b0..cada331c 100644 --- a/django_filters/filters.py +++ b/django_filters/filters.py @@ -30,6 +30,14 @@ ) from .utils import get_model_field, label_for_filter +try: + from django.utils.choices import normalize_choices +except ImportError: + DJANGO_50 = False +else: + DJANGO_50 = True + + __all__ = [ "AllValuesFilter", "AllValuesMultipleFilter", @@ -479,6 +487,12 @@ def __init__(self, choices=None, filters=None, *args, **kwargs): if filters is not None: self.filters = filters + if isinstance(self.choices, dict): + if DJANGO_50: + self.choices = normalize_choices(self.choices) + else: + raise ValueError("Django 5.0 or later is required for dict choices") + all_choices = list( chain.from_iterable( [subchoice[0] for subchoice in choice[1]] diff --git a/tests/test_filters.py b/tests/test_filters.py index 290fbc13..bcbf9c17 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -1,8 +1,10 @@ import inspect +import unittest from collections import OrderedDict from datetime import date, datetime, time, timedelta from unittest import mock +import django from django import forms from django.test import TestCase, override_settings from django.utils import translation @@ -1105,6 +1107,22 @@ def test_choices_with_optgroups_dont_mistmatch(self): choices=[("group", ("a", "a")), ("b", "b")], filters={"a": None, "b": None} ) + @unittest.skipUnless(django.VERSION >= (5, 0), "Django 5.0 introduced new dictionary choices option") + def test_grouped_choices_as_dictionary(self): + DateRangeFilter( + choices={"group": {"a": "a", "b": "b"}}, filters={"a": None, "b": None} + ) + + @unittest.skipUnless(django.VERSION <= (4, 2), "Django 5.0 introduced new dictionary choices option") + def test_grouped_choices_error(self): + with self.assertRaisesMessage( + ValueError, + "Django 5.0 or later is required for dict choices" + ): + DateRangeFilter( + choices={"group": {"a": "a", "b": "b"}}, filters={"a": None, "b": None} + ) + def test_filtering_for_this_year(self): qs = mock.Mock(spec=["filter"]) with mock.patch("django_filters.filters.now") as mock_now: