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

114 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:32 +0000

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) 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, (value, label, checked) in enumerate(self.iter_choices()): 

53 opt = self._Option(label=label, id="%s-%d" % (self.id, i), **opts) 

54 opt.process(None, value) 

55 opt.checked = checked 

56 yield opt 

57 

58 class _Option(Field): 

59 checked = False 

60 

61 def _value(self): 

62 return str(self.data) 

63 

64 

65class SelectField(SelectFieldBase): 

66 widget = widgets.Select() 

67 

68 def __init__( 

69 self, 

70 label=None, 

71 validators=None, 

72 coerce=str, 

73 choices=None, 

74 validate_choice=True, 

75 **kwargs, 

76 ): 

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

78 self.coerce = coerce 

79 if callable(choices): 

80 choices = choices() 

81 if choices is not None: 

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

83 else: 

84 self.choices = None 

85 self.validate_choice = validate_choice 

86 

87 def iter_choices(self): 

88 if not self.choices: 

89 choices = [] 

90 elif isinstance(self.choices, dict): 

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

92 else: 

93 choices = self.choices 

94 

95 return self._choices_generator(choices) 

96 

97 def has_groups(self): 

98 return isinstance(self.choices, dict) 

99 

100 def iter_groups(self): 

101 if isinstance(self.choices, dict): 

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

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

104 

105 def _choices_generator(self, choices): 

106 if not choices: 

107 _choices = [] 

108 

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

110 _choices = choices 

111 

112 else: 

113 _choices = zip(choices, choices) 

114 

115 for value, label in _choices: 

116 yield (value, label, self.coerce(value) == self.data) 

117 

118 def process_data(self, value): 

119 try: 

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

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

122 except (ValueError, TypeError): 

123 self.data = None 

124 

125 def process_formdata(self, valuelist): 

126 if not valuelist: 

127 return 

128 

129 try: 

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

131 except ValueError as exc: 

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

133 

134 def pre_validate(self, form): 

135 if self.choices is None: 

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

137 

138 if not self.validate_choice: 

139 return 

140 

141 for _, _, match in self.iter_choices(): 

142 if match: 

143 break 

144 else: 

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

146 

147 

148class SelectMultipleField(SelectField): 

149 """ 

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

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

152 attribute to the select field when rendering. 

153 """ 

154 

155 widget = widgets.Select(multiple=True) 

156 

157 def _choices_generator(self, choices): 

158 if choices: 

159 if isinstance(choices[0], (list, tuple)): 

160 _choices = choices 

161 else: 

162 _choices = zip(choices, choices) 

163 else: 

164 _choices = [] 

165 

166 for value, label in _choices: 

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

168 yield (value, label, selected) 

169 

170 def process_data(self, value): 

171 try: 

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

173 except (ValueError, TypeError): 

174 self.data = None 

175 

176 def process_formdata(self, valuelist): 

177 try: 

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

179 except ValueError as exc: 

180 raise ValueError( 

181 self.gettext( 

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

183 ) 

184 ) from exc 

185 

186 def pre_validate(self, form): 

187 if self.choices is None: 

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

189 

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

191 return 

192 

193 acceptable = {c[0] for c in self.iter_choices()} 

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

195 unacceptable = [str(d) for d in set(self.data) - acceptable] 

196 raise ValidationError( 

197 self.ngettext( 

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

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

200 len(unacceptable), 

201 ) 

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

203 ) 

204 

205 

206class RadioField(SelectField): 

207 """ 

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

209 

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

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

212 """ 

213 

214 widget = widgets.ListWidget(prefix_label=False) 

215 option_widget = widgets.RadioInput()