/vendor/packages/django-model-utils/model_utils/choices.py

https://github.com/openhatch/oh-mainline
Python | 153 lines | 77 code | 31 blank | 45 comment | 16 complexity | 42f11ad86799dac8b6b672c3ff0cfa5e MD5 | raw file
  1. from __future__ import unicode_literals
  2. import copy
  3. class Choices(object):
  4. """
  5. A class to encapsulate handy functionality for lists of choices
  6. for a Django model field.
  7. Each argument to ``Choices`` is a choice, represented as either a
  8. string, a two-tuple, or a three-tuple.
  9. If a single string is provided, that string is used as the
  10. database representation of the choice as well as the
  11. human-readable presentation.
  12. If a two-tuple is provided, the first item is used as the database
  13. representation and the second the human-readable presentation.
  14. If a triple is provided, the first item is the database
  15. representation, the second a valid Python identifier that can be
  16. used as a readable label in code, and the third the human-readable
  17. presentation. This is most useful when the database representation
  18. must sacrifice readability for some reason: to achieve a specific
  19. ordering, to use an integer rather than a character field, etc.
  20. Regardless of what representation of each choice is originally
  21. given, when iterated over or indexed into, a ``Choices`` object
  22. behaves as the standard Django choices list of two-tuples.
  23. If the triple form is used, the Python identifier names can be
  24. accessed as attributes on the ``Choices`` object, returning the
  25. database representation. (If the single or two-tuple forms are
  26. used and the database representation happens to be a valid Python
  27. identifier, the database representation itself is available as an
  28. attribute on the ``Choices`` object, returning itself.)
  29. Option groups can also be used with ``Choices``; in that case each
  30. argument is a tuple consisting of the option group name and a list
  31. of options, where each option in the list is either a string, a
  32. two-tuple, or a triple as outlined above.
  33. """
  34. def __init__(self, *choices):
  35. # list of choices expanded to triples - can include optgroups
  36. self._triples = []
  37. # list of choices as (db, human-readable) - can include optgroups
  38. self._doubles = []
  39. # dictionary mapping db representation to human-readable
  40. self._display_map = {}
  41. # dictionary mapping Python identifier to db representation
  42. self._identifier_map = {}
  43. # set of db representations
  44. self._db_values = set()
  45. self._process(choices)
  46. def _store(self, triple, triple_collector, double_collector):
  47. self._identifier_map[triple[1]] = triple[0]
  48. self._display_map[triple[0]] = triple[2]
  49. self._db_values.add(triple[0])
  50. triple_collector.append(triple)
  51. double_collector.append((triple[0], triple[2]))
  52. def _process(self, choices, triple_collector=None, double_collector=None):
  53. if triple_collector is None:
  54. triple_collector = self._triples
  55. if double_collector is None:
  56. double_collector = self._doubles
  57. store = lambda c: self._store(c, triple_collector, double_collector)
  58. for choice in choices:
  59. if isinstance(choice, (list, tuple)):
  60. if len(choice) == 3:
  61. store(choice)
  62. elif len(choice) == 2:
  63. if isinstance(choice[1], (list, tuple)):
  64. # option group
  65. group_name = choice[0]
  66. subchoices = choice[1]
  67. tc = []
  68. triple_collector.append((group_name, tc))
  69. dc = []
  70. double_collector.append((group_name, dc))
  71. self._process(subchoices, tc, dc)
  72. else:
  73. store((choice[0], choice[0], choice[1]))
  74. else:
  75. raise ValueError(
  76. "Choices can't take a list of length %s, only 2 or 3"
  77. % len(choice)
  78. )
  79. else:
  80. store((choice, choice, choice))
  81. def __len__(self):
  82. return len(self._doubles)
  83. def __iter__(self):
  84. return iter(self._doubles)
  85. def __getattr__(self, attname):
  86. try:
  87. return self._identifier_map[attname]
  88. except KeyError:
  89. raise AttributeError(attname)
  90. def __getitem__(self, key):
  91. return self._display_map[key]
  92. def __add__(self, other):
  93. if isinstance(other, self.__class__):
  94. other = other._triples
  95. else:
  96. other = list(other)
  97. return Choices(*(self._triples + other))
  98. def __radd__(self, other):
  99. # radd is never called for matching types, so we don't check here
  100. other = list(other)
  101. return Choices(*(other + self._triples))
  102. def __eq__(self, other):
  103. if isinstance(other, self.__class__):
  104. return self._triples == other._triples
  105. return False
  106. def __repr__(self):
  107. return '%s(%s)' % (
  108. self.__class__.__name__,
  109. ', '.join(("%s" % repr(i) for i in self._triples))
  110. )
  111. def __contains__(self, item):
  112. return item in self._db_values
  113. def __deepcopy__(self, memo):
  114. return self.__class__(*copy.deepcopy(self._triples, memo))