OLD | NEW |
| (Empty) |
1 # -*- test-case-name: twisted.test.test_formmethod -*- | |
2 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
3 # See LICENSE for details. | |
4 | |
5 | |
6 """ | |
7 Form-based method objects. | |
8 | |
9 This module contains support for descriptive method signatures that can be used | |
10 to format methods. Currently this is only used by woven. | |
11 """ | |
12 | |
13 import calendar | |
14 | |
15 class FormException(Exception): | |
16 """An error occurred calling the form method. | |
17 """ | |
18 def __init__(self, *args, **kwargs): | |
19 Exception.__init__(self, *args) | |
20 self.descriptions = kwargs | |
21 | |
22 | |
23 class InputError(FormException): | |
24 """ | |
25 An error occurred with some input. | |
26 """ | |
27 | |
28 | |
29 class Argument: | |
30 """Base class for form arguments.""" | |
31 | |
32 # default value for argument, if no other default is given | |
33 defaultDefault = None | |
34 | |
35 def __init__(self, name, default=None, shortDesc=None, | |
36 longDesc=None, hints=None, allowNone=1): | |
37 self.name = name | |
38 self.allowNone = allowNone | |
39 if default is None: | |
40 default = self.defaultDefault | |
41 self.default = default | |
42 self.shortDesc = shortDesc | |
43 self.longDesc = longDesc | |
44 if not hints: | |
45 hints = {} | |
46 self.hints = hints | |
47 | |
48 def addHints(self, **kwargs): | |
49 self.hints.update(kwargs) | |
50 | |
51 def getHint(self, name, default=None): | |
52 return self.hints.get(name, default) | |
53 | |
54 def getShortDescription(self): | |
55 return self.shortDesc or self.name.capitalize() | |
56 | |
57 def getLongDescription(self): | |
58 return self.longDesc or '' #self.shortDesc or "The %s." % self.name | |
59 | |
60 def coerce(self, val): | |
61 """Convert the value to the correct format.""" | |
62 raise NotImplementedError, "implement in subclass" | |
63 | |
64 | |
65 class String(Argument): | |
66 """A single string. | |
67 """ | |
68 defaultDefault = '' | |
69 min = 0 | |
70 max = None | |
71 | |
72 def __init__(self, name, default=None, shortDesc=None, | |
73 longDesc=None, hints=None, allowNone=1, min=0, max=None): | |
74 Argument.__init__(self, name, default=default, shortDesc=shortDesc, | |
75 longDesc=longDesc, hints=hints, allowNone=allowNone) | |
76 self.min = min | |
77 self.max = max | |
78 | |
79 def coerce(self, val): | |
80 s = str(val) | |
81 if len(s) < self.min: | |
82 raise InputError, "Value must be at least %s characters long" % self
.min | |
83 if self.max != None and len(s) > self.max: | |
84 raise InputError, "Value must be at most %s characters long" % self.
max | |
85 return str(val) | |
86 | |
87 | |
88 class Text(String): | |
89 """A long string. | |
90 """ | |
91 | |
92 | |
93 class Password(String): | |
94 """A string which should be obscured when input. | |
95 """ | |
96 | |
97 | |
98 class VerifiedPassword(String): | |
99 """A string that should be obscured when input and needs verification.""" | |
100 | |
101 def coerce(self, vals): | |
102 if len(vals) != 2 or vals[0] != vals[1]: | |
103 raise InputError, "Please enter the same password twice." | |
104 s = str(vals[0]) | |
105 if len(s) < self.min: | |
106 raise InputError, "Value must be at least %s characters long" % self
.min | |
107 if self.max != None and len(s) > self.max: | |
108 raise InputError, "Value must be at most %s characters long" % self.
max | |
109 return s | |
110 | |
111 | |
112 class Hidden(String): | |
113 """A string which is not displayed. | |
114 | |
115 The passed default is used as the value. | |
116 """ | |
117 | |
118 | |
119 class Integer(Argument): | |
120 """A single integer. | |
121 """ | |
122 defaultDefault = None | |
123 | |
124 def __init__(self, name, allowNone=1, default=None, shortDesc=None, | |
125 longDesc=None, hints=None): | |
126 #although Argument now has allowNone, that was recently added, and | |
127 #putting it at the end kept things which relied on argument order | |
128 #from breaking. However, allowNone originally was in here, so | |
129 #I have to keep the same order, to prevent breaking code that | |
130 #depends on argument order only | |
131 Argument.__init__(self, name, default, shortDesc, longDesc, hints, | |
132 allowNone) | |
133 | |
134 def coerce(self, val): | |
135 if not val.strip() and self.allowNone: | |
136 return None | |
137 try: | |
138 return int(val) | |
139 except ValueError: | |
140 raise InputError, "%s is not valid, please enter a whole number, e.g
. 10" % val | |
141 | |
142 | |
143 class IntegerRange(Integer): | |
144 | |
145 def __init__(self, name, min, max, allowNone=1, default=None, shortDesc=None
, | |
146 longDesc=None, hints=None): | |
147 self.min = min | |
148 self.max = max | |
149 Integer.__init__(self, name, allowNone=allowNone, default=default, short
Desc=shortDesc, | |
150 longDesc=longDesc, hints=hints) | |
151 | |
152 def coerce(self, val): | |
153 result = Integer.coerce(self, val) | |
154 if self.allowNone and result == None: | |
155 return result | |
156 if result < self.min: | |
157 raise InputError, "Value %s is too small, it should be at least %s"
% (result, self.min) | |
158 if result > self.max: | |
159 raise InputError, "Value %s is too large, it should be at most %s" %
(result, self.max) | |
160 return result | |
161 | |
162 | |
163 class Float(Argument): | |
164 | |
165 defaultDefault = None | |
166 | |
167 def __init__(self, name, allowNone=1, default=None, shortDesc=None, | |
168 longDesc=None, hints=None): | |
169 #although Argument now has allowNone, that was recently added, and | |
170 #putting it at the end kept things which relied on argument order | |
171 #from breaking. However, allowNone originally was in here, so | |
172 #I have to keep the same order, to prevent breaking code that | |
173 #depends on argument order only | |
174 Argument.__init__(self, name, default, shortDesc, longDesc, hints, | |
175 allowNone) | |
176 | |
177 | |
178 def coerce(self, val): | |
179 if not val.strip() and self.allowNone: | |
180 return None | |
181 try: | |
182 return float(val) | |
183 except ValueError: | |
184 raise InputError, "Invalid float: %s" % val | |
185 | |
186 | |
187 class Choice(Argument): | |
188 """ | |
189 The result of a choice between enumerated types. The choices should | |
190 be a list of tuples of tag, value, and description. The tag will be | |
191 the value returned if the user hits "Submit", and the description | |
192 is the bale for the enumerated type. default is a list of all the | |
193 values (seconds element in choices). If no defaults are specified, | |
194 initially the first item will be selected. Only one item can (should) | |
195 be selected at once. | |
196 """ | |
197 def __init__(self, name, choices=[], default=[], shortDesc=None, | |
198 longDesc=None, hints=None, allowNone=1): | |
199 self.choices = choices | |
200 if choices and not default: | |
201 default.append(choices[0][1]) | |
202 Argument.__init__(self, name, default, shortDesc, longDesc, hints, allow
None=allowNone) | |
203 | |
204 def coerce(self, inIdent): | |
205 for ident, val, desc in self.choices: | |
206 if ident == inIdent: | |
207 return val | |
208 else: | |
209 raise InputError("Invalid Choice: %s" % inIdent) | |
210 | |
211 | |
212 class Flags(Argument): | |
213 """ | |
214 The result of a checkbox group or multi-menu. The flags should be a | |
215 list of tuples of tag, value, and description. The tag will be | |
216 the value returned if the user hits "Submit", and the description | |
217 is the bale for the enumerated type. default is a list of all the | |
218 values (second elements in flags). If no defaults are specified, | |
219 initially nothing will be selected. Several items may be selected at | |
220 once. | |
221 """ | |
222 def __init__(self, name, flags=(), default=(), shortDesc=None, | |
223 longDesc=None, hints=None, allowNone=1): | |
224 self.flags = flags | |
225 Argument.__init__(self, name, default, shortDesc, longDesc, hints, allow
None=allowNone) | |
226 | |
227 def coerce(self, inFlagKeys): | |
228 if not inFlagKeys: | |
229 return [] | |
230 outFlags = [] | |
231 for inFlagKey in inFlagKeys: | |
232 for flagKey, flagVal, flagDesc in self.flags: | |
233 if inFlagKey == flagKey: | |
234 outFlags.append(flagVal) | |
235 break | |
236 else: | |
237 raise InputError("Invalid Flag: %s" % inFlagKey) | |
238 return outFlags | |
239 | |
240 | |
241 class CheckGroup(Flags): | |
242 pass | |
243 | |
244 | |
245 class RadioGroup(Choice): | |
246 pass | |
247 | |
248 | |
249 class Boolean(Argument): | |
250 def coerce(self, inVal): | |
251 if not inVal: | |
252 return 0 | |
253 lInVal = str(inVal).lower() | |
254 if lInVal in ('no', 'n', 'f', 'false', '0'): | |
255 return 0 | |
256 return 1 | |
257 | |
258 class File(Argument): | |
259 def __init__(self, name, allowNone=1, shortDesc=None, longDesc=None, | |
260 hints=None): | |
261 self.allowNone = allowNone | |
262 Argument.__init__(self, name, None, shortDesc, longDesc, hints) | |
263 | |
264 def coerce(self, file): | |
265 if not file and self.allowNone: | |
266 return None | |
267 elif file: | |
268 return file | |
269 else: | |
270 raise InputError, "Invalid File" | |
271 | |
272 def positiveInt(x): | |
273 x = int(x) | |
274 if x <= 0: raise ValueError | |
275 return x | |
276 | |
277 class Date(Argument): | |
278 """A date -- (year, month, day) tuple.""" | |
279 | |
280 defaultDefault = None | |
281 | |
282 def __init__(self, name, allowNone=1, default=None, shortDesc=None, | |
283 longDesc=None, hints=None): | |
284 Argument.__init__(self, name, default, shortDesc, longDesc, hints) | |
285 self.allowNone = allowNone | |
286 if not allowNone: | |
287 self.defaultDefault = (1970, 1, 1) | |
288 | |
289 def coerce(self, args): | |
290 """Return tuple of ints (year, month, day).""" | |
291 if tuple(args) == ("", "", "") and self.allowNone: | |
292 return None | |
293 | |
294 try: | |
295 year, month, day = map(positiveInt, args) | |
296 except ValueError: | |
297 raise InputError, "Invalid date" | |
298 if (month, day) == (2, 29): | |
299 if not calendar.isleap(year): | |
300 raise InputError, "%d was not a leap year" % year | |
301 else: | |
302 return year, month, day | |
303 try: | |
304 mdays = calendar.mdays[month] | |
305 except IndexError: | |
306 raise InputError, "Invalid date" | |
307 if day > mdays: | |
308 raise InputError, "Invalid date" | |
309 return year, month, day | |
310 | |
311 | |
312 class Submit(Choice): | |
313 """Submit button or a reasonable facsimile thereof.""" | |
314 | |
315 def __init__(self, name, choices=[("Submit", "submit", "Submit form")], | |
316 reset=0, shortDesc=None, longDesc=None, allowNone=0, hints=None
): | |
317 Choice.__init__(self, name, choices=choices, shortDesc=shortDesc, | |
318 longDesc=longDesc, hints=hints) | |
319 self.allowNone = allowNone | |
320 self.reset = reset | |
321 | |
322 def coerce(self, value): | |
323 if self.allowNone and not value: | |
324 return None | |
325 else: | |
326 return Choice.coerce(self, value) | |
327 | |
328 | |
329 class PresentationHint: | |
330 """ | |
331 A hint to a particular system. | |
332 """ | |
333 | |
334 | |
335 class MethodSignature: | |
336 | |
337 def __init__(self, *sigList): | |
338 """ | |
339 """ | |
340 self.methodSignature = sigList | |
341 | |
342 def getArgument(self, name): | |
343 for a in self.methodSignature: | |
344 if a.name == name: | |
345 return a | |
346 | |
347 def method(self, callable, takesRequest=False): | |
348 return FormMethod(self, callable, takesRequest) | |
349 | |
350 | |
351 class FormMethod: | |
352 """A callable object with a signature.""" | |
353 | |
354 def __init__(self, signature, callable, takesRequest=False): | |
355 self.signature = signature | |
356 self.callable = callable | |
357 self.takesRequest = takesRequest | |
358 | |
359 def getArgs(self): | |
360 return tuple(self.signature.methodSignature) | |
361 | |
362 def call(self,*args,**kw): | |
363 return self.callable(*args,**kw) | |
OLD | NEW |