22Useful form fields for use with SQLAlchemy ORM.
33"""
44import operator
5+ from collections import defaultdict
56
67from wtforms import widgets
78from wtforms .fields import SelectFieldBase
@@ -48,6 +49,14 @@ class QuerySelectField(SelectFieldBase):
4849 model instance and expected to return the label text. Otherwise, the model
4950 object's `__str__` will be used.
5051
52+ Specify `get_group` to allow `option` elements to be grouped into `optgroup`
53+ sections. If a string, this is the name of an attribute on the model
54+ containing the group name. If a one-argument callable, this callable will
55+ be passed the model instance and expected to return a group name. Otherwise,
56+ the `option` elements will not be grouped. Note: the result of `get_group`
57+ will be used as both the grouping key and the display label in the `select`
58+ options.
59+
5160 If `allow_blank` is set to `True`, then a blank choice will be added to the
5261 top of the list. Selecting this choice will result in the `data` property
5362 being `None`. The label for this blank choice can be set by specifying the
@@ -63,6 +72,7 @@ def __init__(
6372 query_factory = None ,
6473 get_pk = None ,
6574 get_label = None ,
75+ get_group = None ,
6676 allow_blank = False ,
6777 blank_text = "" ,
6878 ** kwargs
@@ -86,6 +96,15 @@ def __init__(
8696 else :
8797 self .get_label = get_label
8898
99+ if get_group is None :
100+ self ._has_groups = False
101+ else :
102+ self ._has_groups = True
103+ if isinstance (get_group , str ):
104+ self .get_group = operator .attrgetter (get_group )
105+ else :
106+ self .get_group = get_group
107+
89108 self .allow_blank = allow_blank
90109 self .blank_text = blank_text
91110 self .query = None
@@ -119,6 +138,28 @@ def iter_choices(self):
119138 for pk , obj in self ._get_object_list ():
120139 yield (pk , self .get_label (obj ), obj == self .data )
121140
141+ def has_groups (self ):
142+ return self ._has_groups
143+
144+ def iter_groups (self ):
145+ if self .has_groups ():
146+ groups = defaultdict (list )
147+ for pk , obj in self ._get_object_list ():
148+ groups [self .get_group (obj )].append ((pk , obj ))
149+ for group , choices in groups .items ():
150+ yield (group , self ._choices_generator (choices ))
151+
152+ def _choices_generator (self , choices ):
153+ if not choices :
154+ _choices = []
155+ else :
156+ _choices = choices
157+
158+ for pk , obj in _choices :
159+ yield (pk , self .get_label (obj ), obj == self .data , self .get_render_kw (obj ))
160+
161+
162+
122163 def process_formdata (self , valuelist ):
123164 if valuelist :
124165 if self .allow_blank and valuelist [0 ] == "__None" :
0 commit comments