@@ -108,6 +108,53 @@ def set_value(dictionary, keys, value):
108
108
dictionary [keys [- 1 ]] = value
109
109
110
110
111
+ def to_choices_dict (choices ):
112
+ """
113
+ Convert choices into key/value dicts.
114
+
115
+ pairwise_choices([1]) -> {1: 1}
116
+ pairwise_choices([(1, '1st'), (2, '2nd')]) -> {1: '1st', 2: '2nd'}
117
+ pairwise_choices([('Group', ((1, '1st'), 2))]) -> {'Group': {1: '1st', 2: '2nd'}}
118
+ """
119
+ # Allow single, paired or grouped choices style:
120
+ # choices = [1, 2, 3]
121
+ # choices = [(1, 'First'), (2, 'Second'), (3, 'Third')]
122
+ # choices = [('Category', ((1, 'First'), (2, 'Second'))), (3, 'Third')]
123
+ ret = OrderedDict ()
124
+ for choice in choices :
125
+ if (not isinstance (choice , (list , tuple ))):
126
+ # single choice
127
+ ret [choice ] = choice
128
+ else :
129
+ key , value = choice
130
+ if isinstance (value , (list , tuple )):
131
+ # grouped choices (category, sub choices)
132
+ ret [key ] = to_choices_dict (value )
133
+ else :
134
+ # paired choice (key, display value)
135
+ ret [key ] = value
136
+ return ret
137
+
138
+
139
+ def flatten_choices_dict (choices ):
140
+ """
141
+ Convert a group choices dict into a flat dict of choices.
142
+
143
+ flatten_choices({1: '1st', 2: '2nd'}) -> {1: '1st', 2: '2nd'}
144
+ flatten_choices({'Group': {1: '1st', 2: '2nd'}}) -> {1: '1st', 2: '2nd'}
145
+ """
146
+ ret = OrderedDict ()
147
+ for key , value in choices .items ():
148
+ if isinstance (value , dict ):
149
+ # grouped choices (category, sub choices)
150
+ for sub_key , sub_value in value .items ():
151
+ ret [sub_key ] = sub_value
152
+ else :
153
+ # choice (key, display value)
154
+ ret [key ] = value
155
+ return ret
156
+
157
+
111
158
class CreateOnlyDefault (object ):
112
159
"""
113
160
This class may be used to provide default values that are only used
@@ -1111,17 +1158,8 @@ class ChoiceField(Field):
1111
1158
}
1112
1159
1113
1160
def __init__ (self , choices , ** kwargs ):
1114
- # Allow either single or paired choices style:
1115
- # choices = [1, 2, 3]
1116
- # choices = [(1, 'First'), (2, 'Second'), (3, 'Third')]
1117
- pairs = [
1118
- isinstance (item , (list , tuple )) and len (item ) == 2
1119
- for item in choices
1120
- ]
1121
- if all (pairs ):
1122
- self .choices = OrderedDict ([(key , display_value ) for key , display_value in choices ])
1123
- else :
1124
- self .choices = OrderedDict ([(item , item ) for item in choices ])
1161
+ self .grouped_choices = to_choices_dict (choices )
1162
+ self .choices = flatten_choices_dict (self .grouped_choices )
1125
1163
1126
1164
# Map the string representation of choices to the underlying value.
1127
1165
# Allows us to deal with eg. integer choices while supporting either
@@ -1148,6 +1186,38 @@ def to_representation(self, value):
1148
1186
return value
1149
1187
return self .choice_strings_to_values .get (six .text_type (value ), value )
1150
1188
1189
+ def iter_options (self ):
1190
+ """
1191
+ Helper method for use with templates rendering select widgets.
1192
+ """
1193
+ class StartOptionGroup (object ):
1194
+ start_option_group = True
1195
+ end_option_group = False
1196
+
1197
+ def __init__ (self , label ):
1198
+ self .label = label
1199
+
1200
+ class EndOptionGroup (object ):
1201
+ start_option_group = False
1202
+ end_option_group = True
1203
+
1204
+ class Option (object ):
1205
+ start_option_group = False
1206
+ end_option_group = False
1207
+
1208
+ def __init__ (self , value , display_text ):
1209
+ self .value = value
1210
+ self .display_text = display_text
1211
+
1212
+ for key , value in self .grouped_choices .items ():
1213
+ if isinstance (value , dict ):
1214
+ yield StartOptionGroup (label = key )
1215
+ for sub_key , sub_value in value .items ():
1216
+ yield Option (value = sub_key , display_text = sub_value )
1217
+ yield EndOptionGroup ()
1218
+ else :
1219
+ yield Option (value = key , display_text = value )
1220
+
1151
1221
1152
1222
class MultipleChoiceField (ChoiceField ):
1153
1223
default_error_messages = {
0 commit comments