mirror of
https://github.com/django/django.git
synced 2025-01-03 15:06:09 +00:00
Refs #34899 -- Extracted Field.flatchoices to flatten_choices helper function.
Co-authored-by: Natalia Bidart <124304+nessita@users.noreply.github.com>
This commit is contained in:
parent
07fa79ef2b
commit
74afcee234
@ -15,7 +15,11 @@ from django.db import connection, connections, router
|
|||||||
from django.db.models.constants import LOOKUP_SEP
|
from django.db.models.constants import LOOKUP_SEP
|
||||||
from django.db.models.query_utils import DeferredAttribute, RegisterLookupMixin
|
from django.db.models.query_utils import DeferredAttribute, RegisterLookupMixin
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.choices import CallableChoiceIterator, normalize_choices
|
from django.utils.choices import (
|
||||||
|
CallableChoiceIterator,
|
||||||
|
flatten_choices,
|
||||||
|
normalize_choices,
|
||||||
|
)
|
||||||
from django.utils.datastructures import DictWrapper
|
from django.utils.datastructures import DictWrapper
|
||||||
from django.utils.dateparse import (
|
from django.utils.dateparse import (
|
||||||
parse_date,
|
parse_date,
|
||||||
@ -1080,19 +1084,10 @@ class Field(RegisterLookupMixin):
|
|||||||
"""
|
"""
|
||||||
return str(self.value_from_object(obj))
|
return str(self.value_from_object(obj))
|
||||||
|
|
||||||
def _get_flatchoices(self):
|
@property
|
||||||
|
def flatchoices(self):
|
||||||
"""Flattened version of choices tuple."""
|
"""Flattened version of choices tuple."""
|
||||||
if self.choices is None:
|
return list(flatten_choices(self.choices))
|
||||||
return []
|
|
||||||
flat = []
|
|
||||||
for choice, value in self.choices:
|
|
||||||
if isinstance(value, (list, tuple)):
|
|
||||||
flat.extend(value)
|
|
||||||
else:
|
|
||||||
flat.append((choice, value))
|
|
||||||
return flat
|
|
||||||
|
|
||||||
flatchoices = property(_get_flatchoices)
|
|
||||||
|
|
||||||
def save_form_data(self, instance, data):
|
def save_form_data(self, instance, data):
|
||||||
setattr(instance, self.name, data)
|
setattr(instance, self.name, data)
|
||||||
|
@ -6,6 +6,7 @@ from django.utils.functional import Promise
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"BaseChoiceIterator",
|
"BaseChoiceIterator",
|
||||||
"CallableChoiceIterator",
|
"CallableChoiceIterator",
|
||||||
|
"flatten_choices",
|
||||||
"normalize_choices",
|
"normalize_choices",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -43,6 +44,15 @@ class CallableChoiceIterator(BaseChoiceIterator):
|
|||||||
yield from normalize_choices(self.func())
|
yield from normalize_choices(self.func())
|
||||||
|
|
||||||
|
|
||||||
|
def flatten_choices(choices):
|
||||||
|
"""Flatten choices by removing nested values."""
|
||||||
|
for value_or_group, label_or_nested in choices or ():
|
||||||
|
if isinstance(label_or_nested, (list, tuple)):
|
||||||
|
yield from label_or_nested
|
||||||
|
else:
|
||||||
|
yield value_or_group, label_or_nested
|
||||||
|
|
||||||
|
|
||||||
def normalize_choices(value, *, depth=0):
|
def normalize_choices(value, *, depth=0):
|
||||||
"""Normalize choices values consistently for fields and widgets."""
|
"""Normalize choices values consistently for fields and widgets."""
|
||||||
# Avoid circular import when importing django.forms.
|
# Avoid circular import when importing django.forms.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import collections.abc
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.db.models import TextChoices
|
from django.db.models import TextChoices
|
||||||
@ -5,6 +6,7 @@ from django.test import SimpleTestCase
|
|||||||
from django.utils.choices import (
|
from django.utils.choices import (
|
||||||
BaseChoiceIterator,
|
BaseChoiceIterator,
|
||||||
CallableChoiceIterator,
|
CallableChoiceIterator,
|
||||||
|
flatten_choices,
|
||||||
normalize_choices,
|
normalize_choices,
|
||||||
)
|
)
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -56,6 +58,46 @@ class ChoiceIteratorTests(SimpleTestCase):
|
|||||||
self.assertTrue(str(ctx.exception).endswith("index out of range"))
|
self.assertTrue(str(ctx.exception).endswith("index out of range"))
|
||||||
|
|
||||||
|
|
||||||
|
class FlattenChoicesTests(SimpleTestCase):
|
||||||
|
def test_empty(self):
|
||||||
|
def generator():
|
||||||
|
yield from ()
|
||||||
|
|
||||||
|
for choices in ({}, [], (), set(), frozenset(), generator(), None, ""):
|
||||||
|
with self.subTest(choices=choices):
|
||||||
|
result = flatten_choices(choices)
|
||||||
|
self.assertIsInstance(result, collections.abc.Generator)
|
||||||
|
self.assertEqual(list(result), [])
|
||||||
|
|
||||||
|
def test_non_empty(self):
|
||||||
|
choices = [
|
||||||
|
("C", _("Club")),
|
||||||
|
("D", _("Diamond")),
|
||||||
|
("H", _("Heart")),
|
||||||
|
("S", _("Spade")),
|
||||||
|
]
|
||||||
|
result = flatten_choices(choices)
|
||||||
|
self.assertIsInstance(result, collections.abc.Generator)
|
||||||
|
self.assertEqual(list(result), choices)
|
||||||
|
|
||||||
|
def test_nested_choices(self):
|
||||||
|
choices = [
|
||||||
|
("Audio", [("vinyl", _("Vinyl")), ("cd", _("CD"))]),
|
||||||
|
("Video", [("vhs", _("VHS Tape")), ("dvd", _("DVD"))]),
|
||||||
|
("unknown", _("Unknown")),
|
||||||
|
]
|
||||||
|
expected = [
|
||||||
|
("vinyl", _("Vinyl")),
|
||||||
|
("cd", _("CD")),
|
||||||
|
("vhs", _("VHS Tape")),
|
||||||
|
("dvd", _("DVD")),
|
||||||
|
("unknown", _("Unknown")),
|
||||||
|
]
|
||||||
|
result = flatten_choices(choices)
|
||||||
|
self.assertIsInstance(result, collections.abc.Generator)
|
||||||
|
self.assertEqual(list(result), expected)
|
||||||
|
|
||||||
|
|
||||||
class NormalizeFieldChoicesTests(SimpleTestCase):
|
class NormalizeFieldChoicesTests(SimpleTestCase):
|
||||||
expected = [
|
expected = [
|
||||||
("C", _("Club")),
|
("C", _("Club")),
|
||||||
|
Loading…
Reference in New Issue
Block a user