-
Notifications
You must be signed in to change notification settings - Fork 3
/
choices.py
154 lines (109 loc) · 3.9 KB
/
choices.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
'''
Usage
------------------------------------------------------------------------------
Create a :py:class:`Choices` class and add :py:class:`Choice` objects to the
class to define your choices.
Example:
The normal Django version:
.. code-block:: python
class Human(models.Model):
GENDER = (
('m', 'Male'),
('f', 'Female'),
('o', 'Other'),
)
gender = models.CharField(max_length=1, choices=GENDER)
The Django Utils Choices version:
.. code-block:: python
from django_utils import choices
class Human(models.Model):
class Gender(choices.Choices):
Male = choices.Choice('m')
Female = choices.Choice('f')
Other = choices.Choice('o')
gender = models.CharField(max_length=1, choices=Gender.choices)
To reference these properties:
.. code-block:: python
Human.create(gender=Human.Gender.Male)
'''
import collections
class ChoicesDict(object):
'''The choices dict is an object that stores a sorted representation of
the values by key and database value'''
def __init__(self):
self._by_value = collections.OrderedDict()
self._by_key = collections.OrderedDict()
# Reset the choice creation counter since this will only be accessed
# after processing the choices
Choice.order = 0
def __getitem__(self, key):
if key in self._by_value:
return self._by_value[key]
elif key in self._by_key:
return self._by_key[key]
else:
raise KeyError('Key %r does not exist' % key)
def __setitem__(self, key, value):
self._by_key[key] = value
self._by_value[value.value] = value
def __iter__(self):
for k, v in self._by_value.iteritems():
yield k, v
def items(self):
return list(self)
def __repr__(self):
return repr(self._by_key)
def __str__(self):
return str(self._by_key)
class Choice(object):
'''The choice object has an optional label and value. If the value is not
given an autoincrementing id (starting from 1) will be used'''
order = 0
def __init__(self, value=None, label=None):
Choice.order += 1
self.value = value
self.label = label
self.order = Choice.order
def __repr__(self):
return (u'<%s[%d]:%s>' % (
self.__class__.__name__,
self.order,
self.label,
)).encode('utf-8', 'replace')
def __str__(self):
return unicode(self).encode('utf-8', 'replace')
def __unicode__(self):
value = self.value
if isinstance(value, str):
return unicode(value, 'utf-8', 'replace')
else:
return unicode(value)
class ChoicesMeta(type):
'''The choices metaclass is where all the magic happens, this
automatically creates a ChoicesDict to get a sorted list of keys and
values'''
def __new__(cls, name, bases, attrs):
choices = list()
has_values = False
for key, value in attrs.iteritems():
if isinstance(value, Choice):
if value.value:
has_values = True
if not value.label:
value.label = key
choices.append((key, value))
attrs['choices'] = ChoicesDict()
i = 0
for key, value in sorted(choices, key=lambda c: c[1].order):
if has_values:
assert value.value, ('Cannot mix choices with and without '
'values')
else:
value.value = i
i += 1
attrs[key] = value.value
attrs['choices'][key] = value
return super(ChoicesMeta, cls).__new__(cls, name, bases, attrs)
class Choices(object):
'''The choices class is what you should inherit in your Django models'''
__metaclass__ = ChoicesMeta