Changeset 25:fd00a744a458 for wokkel/data_form.py
- Timestamp:
- Jul 7, 2008, 4:29:02 PM (14 years ago)
- Branch:
- default
- Convert:
- svn:b33ecbfc-034c-dc11-8662-000475d9059e/trunk@55
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
wokkel/data_form.py
r1 r25 1 # Copyright (c) 2003-2007 Ralph Meijer 1 # -*- test-case-name: wokkel.test.test_data_form -*- 2 # 3 # Copyright (c) 2003-2008 Ralph Meijer 2 4 # See LICENSE for details. 3 5 6 """ 7 Data Forms. 8 9 Support for Data Forms as described in 10 U{XEP-0004<http://www.xmpp.org/extensions/xep-0004.html>}, along with support 11 for Field Standardization for Data Forms as described in 12 U{XEP-0068<http://www.xmpp.org/extensions/xep-0068.html>}. 13 """ 14 15 from twisted.words.protocols.jabber.jid import JID 4 16 from twisted.words.xish import domish 5 17 6 18 NS_X_DATA = 'jabber:x:data' 7 19 8 class Field(domish.Element): 9 def __init__(self, type='text-single', var=None, label=None, 10 value=None, values=[], options={}): 11 domish.Element.__init__(self, (NS_X_DATA, 'field')) 12 self['type'] = type 13 if var is not None: 14 self['var'] = var 15 if label is not None: 16 self['label'] = label 20 21 22 class Error(Exception): 23 """ 24 Data Forms error. 25 """ 26 27 28 29 class FieldNameRequiredError(Error): 30 """ 31 A field name is required for this field type. 32 """ 33 34 35 36 class TooManyValuesError(Error): 37 """ 38 This field is single-value. 39 """ 40 41 42 43 class Option(object): 44 """ 45 Data Forms field option. 46 47 @ivar value: Value of this option. 48 @type value: C{unicode} 49 @ivar label: Optional label for this option. 50 @type label: C{unicode} or C{NoneType}. 51 """ 52 53 def __init__(self, value, label=None): 54 self.value = value 55 self.label = label 56 57 58 def __repr__(self): 59 r = ["Option(", repr(self.value)] 60 if self.label: 61 r.append(", ") 62 r.append(repr(self.label)) 63 r.append(")") 64 return u"".join(r) 65 66 67 def toElement(self): 68 """ 69 Return the DOM representation of this option. 70 71 @rtype L{domish.Element}. 72 """ 73 option = domish.Element((NS_X_DATA, 'option')) 74 option.addElement('value', content=self.value) 75 if self.label: 76 option['label'] = self.label 77 return option 78 79 @staticmethod 80 def fromElement(element): 81 valueElements = list(domish.generateElementsQNamed(element.children, 82 'value', NS_X_DATA)) 83 if not valueElements: 84 raise Error("Option has no value") 85 86 label = element.getAttribute('label') 87 return Option(unicode(valueElements[0]), label) 88 89 90 class Field(object): 91 """ 92 Data Forms field. 93 94 @ivar fieldType: Type of this field. One of C{'boolean'}, C{'fixed'}, 95 C{'hidden'}, C{'jid-multi'}, C{'jid-single'}, 96 C{'list-multi'}, {'list-single'}, C{'text-multi'}, 97 C{'text-private'}, C{'text-single'}. 98 99 The default is C{'text-single'}. 100 @type fieldType: C{str} 101 @ivar var: Field name. Optional if L{fieldType} is C{'fixed'}. 102 @type var: C{str} 103 @ivar label: Human readable label for this field. 104 @type label: C{unicode} 105 @ivar values: The values for this field, for multi-valued field 106 types, as a list of C{bool}, C{unicode} or L{JID}. 107 @type values: C{list} 108 @ivar options: List of possible values to choose from in a response 109 to this form as a list of L{Option}s. 110 @type options: C{list}. 111 @ivar desc: Human readable description for this field. 112 @type desc: C{unicode} 113 @ivar required: Whether the field is required to be provided in a 114 response to this form. 115 @type required: C{bool}. 116 """ 117 118 def __init__(self, fieldType='text-single', var=None, value=None, 119 values=None, options=None, label=None, desc=None, 120 required=False): 121 """ 122 Initialize this field. 123 124 See the identically named instance variables for descriptions. 125 126 If C{value} is not C{None}, it overrides C{values}, setting the 127 given value as the only value for this field. 128 """ 129 130 self.fieldType = fieldType 131 self.var = var 17 132 if value is not None: 18 self. set_value(value)133 self.value = value 19 134 else: 20 self.set_values(values) 21 if type in ['list-single', 'list-multi']: 22 for value, label in options.iteritems(): 23 self.addChild(Option(value, label)) 24 25 def set_value(self, value): 26 if self['type'] == 'boolean': 27 value = str(int(bool(value))) 135 self.values = values or [] 136 137 try: 138 self.options = [Option(value, label) 139 for value, label in options.iteritems()] 140 except AttributeError: 141 self.options = options or [] 142 143 self.label = label 144 self.desc = desc 145 self.required = required 146 147 148 def __repr__(self): 149 r = ["Field(fieldType=", repr(self.fieldType)] 150 if self.var: 151 r.append(", var=") 152 r.append(repr(self.var)) 153 if self.label: 154 r.append(", label=") 155 r.append(repr(self.label)) 156 if self.desc: 157 r.append(", desc=") 158 r.append(repr(self.desc)) 159 if self.required: 160 r.append(", required=") 161 r.append(repr(self.required)) 162 if self.values: 163 r.append(", values=") 164 r.append(repr(self.values)) 165 if self.options: 166 r.append(", options=") 167 r.append(repr(self.options)) 168 r.append(")") 169 return u"".join(r) 170 171 172 def __value_set(self, value): 173 """ 174 Setter of value property. 175 176 Sets C{value} as the only element of L{values}. 177 178 @type value: C{bool}, C{unicode} or L{JID} 179 """ 180 self.values = [value] 181 182 183 def __value_get(self): 184 """ 185 Getter of value property. 186 187 Returns the first element of L{values}, if present, or C{None}. 188 """ 189 190 if self.values: 191 return self.values[0] 28 192 else: 29 value = str(value) 30 31 value_element = self.value or self.addElement('value') 32 value_element.children = [] 33 value_element.addContent(value) 34 35 def set_values(self, values): 36 for value in values: 37 value = str(value) 38 self.addElement('value', content=value) 39 40 class Option(domish.Element): 41 def __init__(self, value, label=None): 42 domish.Element.__init__(self, (NS_X_DATA, 'option')) 43 if label is not None: 44 self['label'] = label 45 self.addElement('value', content=value) 46 47 class Form(domish.Element): 48 def __init__(self, type, form_type): 49 domish.Element.__init__(self, (NS_X_DATA, 'x'), 50 attribs={'type': type}) 51 self.add_field(type='hidden', var='FORM_TYPE', values=[form_type]) 52 53 def add_field(self, type='text-single', var=None, label=None, 54 value=None, values=[], options={}): 55 self.addChild(Field(type, var, label, value, values, options)) 193 return None 194 195 196 value = property(__value_get, __value_set, doc=""" 197 The value for this field, for single-valued field types. 198 199 This is a special property accessing L{values}. Writing to this 200 property empties L{values} and then sets the given value as the 201 only element of L{values}. Reading from this propery returns the 202 first element of L{values}. 203 """) 204 205 206 def toElement(self): 207 """ 208 Return the DOM representation of this Field. 209 210 @rtype L{domish.Element}. 211 """ 212 if self.var is None and self.fieldType != 'fixed': 213 raise FieldNameRequiredError() 214 215 field = domish.Element((NS_X_DATA, 'field')) 216 field['type'] = self.fieldType 217 218 if self.var is not None: 219 field['var'] = self.var 220 221 if self.values: 222 if (self.fieldType not in ('hidden', 'jid-multi', 'list-multi', 223 'text-multi') and 224 len(self.values) > 1): 225 raise TooManyValuesError() 226 227 for value in self.values: 228 if self.fieldType == 'boolean': 229 # We send out the textual representation of boolean values 230 value = unicode(bool(value)).lower() 231 elif self.fieldType in ('jid-single', 'jid-multi'): 232 value = value.full() 233 234 field.addElement('value', content=value) 235 236 if self.fieldType in ('list-single', 'list-multi'): 237 for option in self.options: 238 field.addChild(option.toElement()) 239 240 if self.label is not None: 241 field['label'] = self.label 242 243 if self.desc is not None: 244 field.addElement('desc', content=self.desc) 245 246 if self.required: 247 field.addElement('required') 248 249 return field 250 251 252 @staticmethod 253 def _parse_desc(field, element): 254 desc = unicode(element) 255 if desc: 256 field.desc = desc 257 258 259 @staticmethod 260 def _parse_option(field, element): 261 field.options.append(Option.fromElement(element)) 262 263 264 @staticmethod 265 def _parse_required(field, element): 266 field.required = True 267 268 269 @staticmethod 270 def _parse_value(field, element): 271 value = unicode(element) 272 if field.fieldType == 'boolean': 273 value = value.lower() in ('1', 'true') 274 elif field.fieldType in ('jid-multi', 'jid-single'): 275 value = JID(value) 276 field.values.append(value) 277 278 279 @staticmethod 280 def fromElement(element): 281 field = Field(None) 282 283 for eAttr, fAttr in {'type': 'fieldType', 284 'var': 'var', 285 'label': 'label'}.iteritems(): 286 value = element.getAttribute(eAttr) 287 if value: 288 setattr(field, fAttr, value) 289 290 291 for child in element.elements(): 292 if child.uri != NS_X_DATA: 293 continue 294 295 func = getattr(Field, '_parse_' + child.name, None) 296 if func: 297 func(field, child) 298 299 return field 300 301 302 @staticmethod 303 def fromDict(dictionary): 304 if 'type' in dictionary: 305 dictionary['fieldType'] = dictionary['type'] 306 del dictionary['type'] 307 if 'options' in dictionary: 308 options = [] 309 for value, label in dictionary['options'].iteritems(): 310 options.append(Option(value, label)) 311 dictionary['options'] = options 312 return Field(**dictionary) 313 314 315 316 class Form(object): 317 """ 318 Data Form. 319 320 There are two similarly named properties of forms. The L{formType} is the 321 the so-called type of the form, and is set as the C{'type'} attribute 322 on the form's root element. 323 324 The Field Standardization specification in XEP-0068, defines a way to 325 provide a context for the field names used in this form, by setting a 326 special hidden field named C{'FORM_TYPE'}, to put the names of all 327 other fields in the namespace of the value of that field. This namespace 328 is recorded in the L{formNamespace} instance variable. 329 330 @ivar formType: Type of form. One of C{'form'}, C{'submit'}, {'cancel'}, 331 or {'result'}. 332 @type formType: C{str}. 333 @ivar formNamespace: The optional namespace of the field names for this 334 form. This goes in the special field named 335 C{'FORM_TYPE'}, if set. 336 @type formNamespace: C{str}. 337 """ 338 339 def __init__(self, formType, title=None, instructions=None, 340 formNamespace=None, fields=None): 341 self.formType = formType 342 self.title = title 343 self.instructions = instructions or [] 344 self.formNamespace = formNamespace 345 self.fields = fields or [] 346 347 348 def __repr__(self): 349 r = ["Form(formType=", repr(self.formType)] 350 351 if self.title: 352 r.append(", title=") 353 r.append(repr(self.title)) 354 if self.instructions: 355 r.append(", instructions=") 356 r.append(repr(self.instructions)) 357 if self.formNamespace: 358 r.append(", formNamespace=") 359 r.append(repr(self.formNamespace)) 360 if self.fields: 361 r.append(", fields=") 362 r.append(repr(self.fields)) 363 r.append(")") 364 return u"".join(r) 365 366 367 def toElement(self): 368 form = domish.Element((NS_X_DATA, 'x')) 369 form['type'] = self.formType 370 371 if self.title: 372 form.addElement('title', content=self.title) 373 374 for instruction in self.instructions: 375 form.addElement('instruction', content=instruction) 376 377 if self.formNamespace is not None: 378 field = Field('hidden', 'FORM_TYPE', self.formNamespace) 379 form.addChild(field.toElement()) 380 381 for field in self.fields: 382 form.addChild(field.toElement()) 383 384 return form 385 386 387 @staticmethod 388 def _parse_title(form, element): 389 title = unicode(element) 390 if title: 391 form.title = title 392 393 394 @staticmethod 395 def _parse_instructions(form, element): 396 instructions = unicode(element) 397 if instructions: 398 form.instructions.append(instructions) 399 400 401 @staticmethod 402 def _parse_field(form, element): 403 field = Field.fromElement(element) 404 if (field.var == "FORM_TYPE" and 405 field.fieldType == 'hidden' and 406 field.value): 407 form.formNamespace = field.value 408 else: 409 form.fields.append(field) 410 411 @staticmethod 412 def fromElement(element): 413 if (element.uri, element.name) != ((NS_X_DATA, 'x')): 414 raise Error("Element provided is not a Data Form") 415 416 form = Form(element.getAttribute("type")) 417 418 for child in element.elements(): 419 if child.uri != NS_X_DATA: 420 continue 421 422 func = getattr(Form, '_parse_' + child.name, None) 423 if func: 424 func(form, child) 425 426 return form 427 428 def getValues(self): 429 values = {} 430 431 for field in self.fields: 432 if len(field.values) > 1: 433 value = field.values 434 else: 435 value = field.value 436 437 if field.var: 438 values[field.var] = value 439 440 return values
Note: See TracChangeset
for help on using the changeset viewer.