Skip to content

Commit

Permalink
Refs #34899 -- Extracted Field.flatchoices to flatten_choices helper …
Browse files Browse the repository at this point in the history
…function.

Co-authored-by: Natalia Bidart <[email protected]>
  • Loading branch information
ngnpope and nessita committed Oct 23, 2023
1 parent 07fa79e commit 74afcee
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 13 deletions.
21 changes: 8 additions & 13 deletions django/db/models/fields/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
from django.db.models.constants import LOOKUP_SEP
from django.db.models.query_utils import DeferredAttribute, RegisterLookupMixin
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.dateparse import (
parse_date,
Expand Down Expand Up @@ -1080,19 +1084,10 @@ def value_to_string(self, obj):
"""
return str(self.value_from_object(obj))

def _get_flatchoices(self):
@property
def flatchoices(self):
"""Flattened version of choices tuple."""
if self.choices is None:
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)
return list(flatten_choices(self.choices))

def save_form_data(self, instance, data):
setattr(instance, self.name, data)
Expand Down
10 changes: 10 additions & 0 deletions django/utils/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
__all__ = [
"BaseChoiceIterator",
"CallableChoiceIterator",
"flatten_choices",
"normalize_choices",
]

Expand Down Expand Up @@ -43,6 +44,15 @@ def __iter__(self):
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):
"""Normalize choices values consistently for fields and widgets."""
# Avoid circular import when importing django.forms.
Expand Down
42 changes: 42 additions & 0 deletions tests/utils_tests/test_choices.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import collections.abc
from unittest import mock

from django.db.models import TextChoices
from django.test import SimpleTestCase
from django.utils.choices import (
BaseChoiceIterator,
CallableChoiceIterator,
flatten_choices,
normalize_choices,
)
from django.utils.translation import gettext_lazy as _
Expand Down Expand Up @@ -56,6 +58,46 @@ def test_getitem_indexerror(self):
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):
expected = [
("C", _("Club")),
Expand Down

0 comments on commit 74afcee

Please sign in to comment.