Skip to content

Instantly share code, notes, and snippets.

@stkrp
Created May 8, 2019 12:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stkrp/29d7f674b03f78d8bfbff8877fe0aa2f to your computer and use it in GitHub Desktop.
Save stkrp/29d7f674b03f78d8bfbff8877fe0aa2f to your computer and use it in GitHub Desktop.
Converts a mapping to Django field choices
from typing import Any, Mapping, Tuple, Callable
def mapping_to_choices(
mapping: Mapping[str, Any],
label_factory: Callable[[str], Any] = lambda v: v,
recursive: bool = True,
) -> Tuple[Tuple[Any, Any], ...]:
"""
Converts a mapping to Django field choices.
Mapping key is used to access the value and as a label.
from collections import OrderedDict
from django.db import models
class Event:
TYPES = OrderedDict((
('crash', 1),
('exit', 2),
))
type = models.IntegerField(choices=mapping_as_choices(TYPES))
event = Event.objects.create(type=Event.TYPES['crash'])
For a more intuitive view of keys as labels it is recommended to use
localization files. For each key need to choose a clear label.
# locale/en/LC_MESSAGES/django.po
msgid "crash"
msgstr "Unexpected accident"
msgid "exit"
msgstr "Exit"
# models.py
from django.utils.translation import gettext_lazy
...
type = models.IntegerField(
choices=mapping_as_choices(TYPES, gettext_lazy),
)
[!] Supports nested structures.
https://docs.djangoproject.com/en/2.2/ref/models/fields/#choices
"""
choices = []
for key, value in mapping.items():
# Group
if isinstance(value, Mapping) and recursive:
group_label = label_factory(key)
# Django allows only two levels, so recursion can only be called
# from the first level.
group_items = mapping_to_choices(value, label_factory, False)
choices.append((group_label, group_items))
# Single item
else:
choices.append((value, label_factory(key)))
return tuple(choices)
from collections import OrderedDict
from unittest import TestCase
from mapping_to_choices import mapping_to_choices
class MappingToChoicesTestCase(TestCase):
def test_operability(self):
subtests_params = (
{
"msg": "Without label factory",
"input": OrderedDict((('x', 1), ('y', 2))),
"output": ((1, 'x'), (2, 'y')),
},
{
"msg": "With label factory",
"input": OrderedDict((('x', 1), ('y', 2))),
"output": ((1, 'X'), (2, 'Y')),
"label_factory": str.upper,
},
{
"msg": "With mapping but non-recursive",
"input": OrderedDict((
("unknown", 0),
("tools", OrderedDict((("saw", 5), ("hammer", 6)))),
)),
"output": (
(0, "UNKNOWN"),
(OrderedDict((("saw", 5), ("hammer", 6))), "TOOLS"),
),
"label_factory": str.upper,
"recursive": False,
},
{
"msg": "With mapping and recursive",
"input": OrderedDict((
(
"accessories",
OrderedDict((
("man", OrderedDict((("knife", 1), ("watch", 2)))),
("woman", OrderedDict((("bag", 3), ("belt", 4)))),
)),
),
("unknown", 0),
("tools", OrderedDict((("saw", 5), ("hammer", 6)))),
)),
"output": (
("ACCESSORIES", (
(OrderedDict((("knife", 1), ("watch", 2))), "MAN"),
(OrderedDict((("bag", 3), ("belt", 4))), "WOMAN"),
)),
(0, "UNKNOWN"),
("TOOLS", (
(5, "SAW"),
(6, "HAMMER"),
)),
),
"label_factory": str.upper,
},
)
for subtest_params in subtests_params:
with self.subTest(**subtest_params):
kwargs = {
"mapping": subtest_params["input"],
}
if "label_factory" in subtest_params:
kwargs["label_factory"] = subtest_params["label_factory"]
if "recursive" in subtest_params:
kwargs["recursive"] = subtest_params["recursive"]
self.assertSequenceEqual(
mapping_to_choices(**kwargs),
subtest_params["output"],
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment