choices.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. from django.conf import settings
  2. class ChoiceSetMeta(type):
  3. """
  4. Metaclass for ChoiceSet
  5. """
  6. def __new__(mcs, name, bases, attrs):
  7. # Extend static choices with any configured choices
  8. if key := attrs.get('key'):
  9. assert type(attrs['CHOICES']) is list, f"{name} has a key defined but CHOICES is not a list"
  10. app = attrs['__module__'].split('.', 1)[0]
  11. replace_key = f'{app}.{key}'
  12. extend_key = f'{replace_key}+' if replace_key else None
  13. if replace_key and replace_key in settings.FIELD_CHOICES:
  14. # Replace the stock choices
  15. attrs['CHOICES'] = settings.FIELD_CHOICES[replace_key]
  16. elif extend_key and extend_key in settings.FIELD_CHOICES:
  17. # Extend the stock choices
  18. attrs['CHOICES'].extend(settings.FIELD_CHOICES[extend_key])
  19. # Define choice tuples and color maps
  20. attrs['_choices'] = []
  21. attrs['colors'] = {}
  22. for choice in attrs['CHOICES']:
  23. if isinstance(choice[1], (list, tuple)):
  24. grouped_choices = []
  25. for c in choice[1]:
  26. grouped_choices.append((c[0], c[1]))
  27. if len(c) == 3:
  28. attrs['colors'][c[0]] = c[2]
  29. attrs['_choices'].append((choice[0], grouped_choices))
  30. else:
  31. attrs['_choices'].append((choice[0], choice[1]))
  32. if len(choice) == 3:
  33. attrs['colors'][choice[0]] = choice[2]
  34. return super().__new__(mcs, name, bases, attrs)
  35. def __call__(cls, *args, **kwargs):
  36. # django-filters will check if a 'choices' value is callable, and if so assume that it returns an iterable
  37. return getattr(cls, '_choices', ())
  38. def __iter__(cls):
  39. return iter(getattr(cls, '_choices', ()))
  40. class ChoiceSet(metaclass=ChoiceSetMeta):
  41. """
  42. Holds an iterable of choice tuples suitable for passing to a Django model or form field. Choices can be defined
  43. statically within the class as CHOICES and/or gleaned from the FIELD_CHOICES configuration parameter.
  44. """
  45. CHOICES = list()
  46. @classmethod
  47. def values(cls):
  48. return [c[0] for c in unpack_grouped_choices(cls._choices)]
  49. def unpack_grouped_choices(choices):
  50. """
  51. Unpack a grouped choices hierarchy into a flat list of two-tuples. For example:
  52. choices = (
  53. ('Foo', (
  54. (1, 'A'),
  55. (2, 'B')
  56. )),
  57. ('Bar', (
  58. (3, 'C'),
  59. (4, 'D')
  60. ))
  61. )
  62. becomes:
  63. choices = (
  64. (1, 'A'),
  65. (2, 'B'),
  66. (3, 'C'),
  67. (4, 'D')
  68. )
  69. """
  70. unpacked_choices = []
  71. for key, value in choices:
  72. if isinstance(value, (list, tuple)):
  73. # Entered an optgroup
  74. for optgroup_key, optgroup_value in value:
  75. unpacked_choices.append((optgroup_key, optgroup_value))
  76. else:
  77. unpacked_choices.append((key, value))
  78. return unpacked_choices
  79. #
  80. # Generic color choices
  81. #
  82. class ColorChoices(ChoiceSet):
  83. COLOR_DARK_RED = 'aa1409'
  84. COLOR_RED = 'f44336'
  85. COLOR_PINK = 'e91e63'
  86. COLOR_ROSE = 'ffe4e1'
  87. COLOR_FUCHSIA = 'ff66ff'
  88. COLOR_PURPLE = '9c27b0'
  89. COLOR_DARK_PURPLE = '673ab7'
  90. COLOR_INDIGO = '3f51b5'
  91. COLOR_BLUE = '2196f3'
  92. COLOR_LIGHT_BLUE = '03a9f4'
  93. COLOR_CYAN = '00bcd4'
  94. COLOR_TEAL = '009688'
  95. COLOR_AQUA = '00ffff'
  96. COLOR_DARK_GREEN = '2f6a31'
  97. COLOR_GREEN = '4caf50'
  98. COLOR_LIGHT_GREEN = '8bc34a'
  99. COLOR_LIME = 'cddc39'
  100. COLOR_YELLOW = 'ffeb3b'
  101. COLOR_AMBER = 'ffc107'
  102. COLOR_ORANGE = 'ff9800'
  103. COLOR_DARK_ORANGE = 'ff5722'
  104. COLOR_BROWN = '795548'
  105. COLOR_LIGHT_GREY = 'c0c0c0'
  106. COLOR_GREY = '9e9e9e'
  107. COLOR_DARK_GREY = '607d8b'
  108. COLOR_BLACK = '111111'
  109. COLOR_WHITE = 'ffffff'
  110. CHOICES = (
  111. (COLOR_DARK_RED, 'Dark Red'),
  112. (COLOR_RED, 'Red'),
  113. (COLOR_PINK, 'Pink'),
  114. (COLOR_ROSE, 'Rose'),
  115. (COLOR_FUCHSIA, 'Fuchsia'),
  116. (COLOR_PURPLE, 'Purple'),
  117. (COLOR_DARK_PURPLE, 'Dark Purple'),
  118. (COLOR_INDIGO, 'Indigo'),
  119. (COLOR_BLUE, 'Blue'),
  120. (COLOR_LIGHT_BLUE, 'Light Blue'),
  121. (COLOR_CYAN, 'Cyan'),
  122. (COLOR_TEAL, 'Teal'),
  123. (COLOR_AQUA, 'Aqua'),
  124. (COLOR_DARK_GREEN, 'Dark Green'),
  125. (COLOR_GREEN, 'Green'),
  126. (COLOR_LIGHT_GREEN, 'Light Green'),
  127. (COLOR_LIME, 'Lime'),
  128. (COLOR_YELLOW, 'Yellow'),
  129. (COLOR_AMBER, 'Amber'),
  130. (COLOR_ORANGE, 'Orange'),
  131. (COLOR_DARK_ORANGE, 'Dark Orange'),
  132. (COLOR_BROWN, 'Brown'),
  133. (COLOR_LIGHT_GREY, 'Light Grey'),
  134. (COLOR_GREY, 'Grey'),
  135. (COLOR_DARK_GREY, 'Dark Grey'),
  136. (COLOR_BLACK, 'Black'),
  137. (COLOR_WHITE, 'White'),
  138. )
  139. #
  140. # Button color choices
  141. #
  142. class ButtonColorChoices(ChoiceSet):
  143. """
  144. Map standard button color choices to Bootstrap 3 button classes
  145. """
  146. DEFAULT = 'outline-dark'
  147. BLUE = 'blue'
  148. INDIGO = 'indigo'
  149. PURPLE = 'purple'
  150. PINK = 'pink'
  151. RED = 'red'
  152. ORANGE = 'orange'
  153. YELLOW = 'yellow'
  154. GREEN = 'green'
  155. TEAL = 'teal'
  156. CYAN = 'cyan'
  157. GRAY = 'gray'
  158. GREY = 'gray' # Backward compatability for <3.2
  159. BLACK = 'black'
  160. WHITE = 'white'
  161. CHOICES = (
  162. (DEFAULT, 'Default'),
  163. (BLUE, 'Blue'),
  164. (INDIGO, 'Indigo'),
  165. (PURPLE, 'Purple'),
  166. (PINK, 'Pink'),
  167. (RED, 'Red'),
  168. (ORANGE, 'Orange'),
  169. (YELLOW, 'Yellow'),
  170. (GREEN, 'Green'),
  171. (TEAL, 'Teal'),
  172. (CYAN, 'Cyan'),
  173. (GRAY, 'Gray'),
  174. (BLACK, 'Black'),
  175. (WHITE, 'White'),
  176. )