Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wtforms/fields/choices.py: 29%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

122 statements  

1import itertools 

2 

3from wtforms import widgets 

4from wtforms.fields.core import Field 

5from wtforms.validators import ValidationError 

6 

7__all__ = ( 

8 "SelectField", 

9 "SelectMultipleField", 

10 "RadioField", 

11) 

12 

13 

14class SelectFieldBase(Field): 

15 option_widget = widgets.Option() 

16 

17 """ 

18 Base class for fields which can be iterated to produce options. 

19 

20 This isn't a field, but an abstract base class for fields which want to 

21 provide this functionality. 

22 """ 

23 

24 def __init__(self, label=None, validators=None, option_widget=None, **kwargs): 

25 super().__init__(label, validators, **kwargs) 

26 

27 if option_widget is not None: 

28 self.option_widget = option_widget 

29 

30 def iter_choices(self): 

31 """ 

32 Provides data for choice widget rendering. Must return a sequence or 

33 iterable of (value, label, selected, render_kw) tuples. 

34 """ 

35 raise NotImplementedError() 

36 

37 def has_groups(self): 

38 return False 

39 

40 def iter_groups(self): 

41 raise NotImplementedError() 

42 

43 def __iter__(self): 

44 opts = dict( 

45 widget=self.option_widget, 

46 validators=self.validators, 

47 name=self.name, 

48 render_kw=self.render_kw, 

49 _form=None, 

50 _meta=self.meta, 

51 ) 

52 for i, choice in enumerate(self.iter_choices()): 

53 if len(choice) == 4: 

54 value, label, checked, render_kw = choice 

55 else: 

56 value, label, checked = choice 

57 render_kw = {} 

58 

59 opt = self._Option( 

60 label=label, id="%s-%d" % (self.id, i), **opts, **render_kw 

61 ) 

62 opt.process(None, value) 

63 opt.checked = checked 

64 yield opt 

65 

66 class _Option(Field): 

67 checked = False 

68 

69 def _value(self): 

70 return str(self.data) 

71 

72 

73class SelectField(SelectFieldBase): 

74 widget = widgets.Select() 

75 

76 def __init__( 

77 self, 

78 label=None, 

79 validators=None, 

80 coerce=str, 

81 choices=None, 

82 validate_choice=True, 

83 **kwargs, 

84 ): 

85 super().__init__(label, validators, **kwargs) 

86 self.coerce = coerce 

87 if callable(choices): 

88 choices = choices() 

89 if choices is not None: 

90 self.choices = choices if isinstance(choices, dict) else list(choices) 

91 else: 

92 self.choices = None 

93 self.validate_choice = validate_choice 

94 

95 def iter_choices(self): 

96 if not self.choices: 

97 choices = [] 

98 elif isinstance(self.choices, dict): 

99 choices = list(itertools.chain.from_iterable(self.choices.values())) 

100 else: 

101 choices = self.choices 

102 

103 return self._choices_generator(choices) 

104 

105 def has_groups(self): 

106 return isinstance(self.choices, dict) 

107 

108 def iter_groups(self): 

109 if isinstance(self.choices, dict): 

110 for label, choices in self.choices.items(): 

111 yield (label, self._choices_generator(choices)) 

112 

113 def _choices_generator(self, choices): 

114 if not choices: 

115 _choices = [] 

116 

117 elif isinstance(choices[0], (list, tuple)): 

118 _choices = choices 

119 

120 else: 

121 _choices = zip(choices, choices) 

122 

123 for value, label, *other_args in _choices: 

124 selected = self.coerce(value) == self.data 

125 render_kw = other_args[0] if len(other_args) else {} 

126 yield (value, label, selected, render_kw) 

127 

128 def process_data(self, value): 

129 try: 

130 # If value is None, don't coerce to a value 

131 self.data = self.coerce(value) if value is not None else None 

132 except (ValueError, TypeError): 

133 self.data = None 

134 

135 def process_formdata(self, valuelist): 

136 if not valuelist: 

137 return 

138 

139 try: 

140 self.data = self.coerce(valuelist[0]) 

141 except ValueError as exc: 

142 raise ValueError(self.gettext("Invalid Choice: could not coerce.")) from exc 

143 

144 def pre_validate(self, form): 

145 if not self.validate_choice: 

146 return 

147 

148 if self.choices is None: 

149 raise TypeError(self.gettext("Choices cannot be None.")) 

150 

151 for _, _, match, *_ in self.iter_choices(): 

152 if match: 

153 break 

154 else: 

155 raise ValidationError(self.gettext("Not a valid choice.")) 

156 

157 

158class SelectMultipleField(SelectField): 

159 """ 

160 No different from a normal select field, except this one can take (and 

161 validate) multiple choices. You'll need to specify the HTML `size` 

162 attribute to the select field when rendering. 

163 """ 

164 

165 widget = widgets.Select(multiple=True) 

166 

167 def _choices_generator(self, choices): 

168 if not choices: 

169 _choices = [] 

170 

171 elif isinstance(choices[0], (list, tuple)): 

172 _choices = choices 

173 

174 else: 

175 _choices = zip(choices, choices) 

176 

177 for value, label, *other_args in _choices: 

178 selected = self.data is not None and self.coerce(value) in self.data 

179 render_kw = other_args[0] if len(other_args) else {} 

180 yield (value, label, selected, render_kw) 

181 

182 def process_data(self, value): 

183 try: 

184 self.data = list(self.coerce(v) for v in value) 

185 except (ValueError, TypeError): 

186 self.data = None 

187 

188 def process_formdata(self, valuelist): 

189 try: 

190 self.data = list(self.coerce(x) for x in valuelist) 

191 except ValueError as exc: 

192 raise ValueError( 

193 self.gettext( 

194 "Invalid choice(s): one or more data inputs could not be coerced." 

195 ) 

196 ) from exc 

197 

198 def pre_validate(self, form): 

199 if not self.validate_choice or not self.data: 

200 return 

201 

202 if self.choices is None: 

203 raise TypeError(self.gettext("Choices cannot be None.")) 

204 

205 acceptable = [self.coerce(choice[0]) for choice in self.iter_choices()] 

206 if any(data not in acceptable for data in self.data): 

207 unacceptable = [ 

208 str(data) for data in set(self.data) if data not in acceptable 

209 ] 

210 raise ValidationError( 

211 self.ngettext( 

212 "'%(value)s' is not a valid choice for this field.", 

213 "'%(value)s' are not valid choices for this field.", 

214 len(unacceptable), 

215 ) 

216 % dict(value="', '".join(unacceptable)) 

217 ) 

218 

219 

220class RadioField(SelectField): 

221 """ 

222 Like a SelectField, except displays a list of radio buttons. 

223 

224 Iterating the field will produce subfields (each containing a label as 

225 well) in order to allow custom rendering of the individual radio fields. 

226 """ 

227 

228 widget = widgets.ListWidget(prefix_label=False) 

229 option_widget = widgets.RadioInput()