Package common :: Module dataforms
[hide private]
[frames] | no frames]

Source Code for Module common.dataforms

  1  # this will go to src/common/xmpp later, for now it is in src/common 
  2  # -*- coding:utf-8 -*- 
  3  ## src/common/dataforms.py 
  4  ## 
  5  ## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org> 
  6  ## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> 
  7  ## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de> 
  8  ## 
  9  ## This file is part of Gajim. 
 10  ## 
 11  ## Gajim is free software; you can redistribute it and/or modify 
 12  ## it under the terms of the GNU General Public License as published 
 13  ## by the Free Software Foundation; version 3 only. 
 14  ## 
 15  ## Gajim is distributed in the hope that it will be useful, 
 16  ## but WITHOUT ANY WARRANTY; without even the implied warranty of 
 17  ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 18  ## GNU General Public License for more details. 
 19  ## 
 20  ## You should have received a copy of the GNU General Public License 
 21  ## along with Gajim. If not, see <http://www.gnu.org/licenses/>. 
 22  ## 
 23   
 24  """ 
 25  This module contains wrappers for different parts of data forms (JEP 0004). For 
 26  information how to use them, read documentation 
 27  """ 
 28   
 29  import xmpp 
 30  import helpers 
31 32 # exceptions used in this module 33 # base class 34 -class Error(Exception): pass
35 # when we get xmpp.Node which we do not understand
36 -class UnknownDataForm(Error): pass
37 # when we get xmpp.Node which contains bad fields
38 -class WrongFieldValue(Error): pass
39
40 # helper class to change class of already existing object 41 -class ExtendedNode(xmpp.Node, object):
42 @classmethod
43 - def __new__(cls, *a, **b):
44 if 'extend' not in b.keys() or not b['extend']: 45 return object.__new__(cls) 46 47 extend = b['extend'] 48 assert issubclass(cls, extend.__class__) 49 extend.__class__ = cls 50 return extend
51
52 # helper decorator to create properties in cleaner way 53 -def nested_property(f):
54 ret = f() 55 p = {'doc': f.__doc__} 56 for v in ('fget', 'fset', 'fdel', 'doc'): 57 if v in ret.keys(): p[v]=ret[v] 58 return property(**p)
59
60 # helper to create fields from scratch 61 -def Field(typ, **attrs):
62 ''' Helper function to create a field of given type. ''' 63 f = { 64 'boolean': BooleanField, 65 'fixed': StringField, 66 'hidden': StringField, 67 'text-private': StringField, 68 'text-single': StringField, 69 'jid-multi': JidMultiField, 70 'jid-single': JidSingleField, 71 'list-multi': ListMultiField, 72 'list-single': ListSingleField, 73 'text-multi': TextMultiField, 74 }[typ](typ=typ, **attrs) 75 return f
76
77 -def ExtendField(node):
78 """ 79 Helper function to extend a node to field of appropriate type 80 """ 81 # when validation (XEP-122) will go in, we could have another classes 82 # like DateTimeField - so that dicts in Field() and ExtendField() will 83 # be different... 84 typ=node.getAttr('type') 85 f = { 86 'boolean': BooleanField, 87 'fixed': StringField, 88 'hidden': StringField, 89 'text-private': StringField, 90 'text-single': StringField, 91 'jid-multi': JidMultiField, 92 'jid-single': JidSingleField, 93 'list-multi': ListMultiField, 94 'list-single': ListSingleField, 95 'text-multi': TextMultiField, 96 } 97 if typ not in f: 98 typ = 'text-single' 99 return f[typ](extend=node)
100
101 -def ExtendForm(node):
102 """ 103 Helper function to extend a node to form of appropriate type 104 """ 105 if node.getTag('reported') is not None: 106 return MultipleDataForm(extend=node) 107 else: 108 return SimpleDataForm(extend=node)
109
110 -class DataField(ExtendedNode):
111 """ 112 Keeps data about one field - var, field type, labels, instructions... Base 113 class for different kinds of fields. Use Field() function to construct one 114 of these 115 """ 116
117 - def __init__(self, typ=None, var=None, value=None, label=None, desc=None, 118 required=False, options=None, extend=None):
119 120 if extend is None: 121 ExtendedNode.__init__(self, 'field') 122 123 self.type = typ 124 self.var = var 125 if value is not None: 126 self.value = value 127 if label is not None: 128 self.label = label 129 if desc is not None: 130 self.desc = desc 131 self.required = required 132 self.options = options
133 134 @nested_property
135 - def type():
136 """ 137 Type of field. Recognized values are: 'boolean', 'fixed', 'hidden', 138 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 139 'text-private', 'text-single'. If you set this to something different, 140 DataField will store given name, but treat all data as text-single 141 """ 142 def fget(self): 143 t = self.getAttr('type') 144 if t is None: 145 return 'text-single' 146 return t
147 148 def fset(self, value): 149 assert isinstance(value, basestring) 150 self.setAttr('type', value)
151 152 return locals() 153 154 @nested_property
155 - def var():
156 """ 157 Field identifier 158 """ 159 def fget(self): 160 return self.getAttr('var')
161 162 def fset(self, value): 163 assert isinstance(value, basestring) 164 self.setAttr('var', value) 165 166 def fdel(self): 167 self.delAttr('var') 168 169 return locals() 170 171 @nested_property
172 - def label():
173 """ 174 Human-readable field name 175 """ 176 def fget(self): 177 l = self.getAttr('label') 178 if not l: 179 l = self.var 180 return l
181 182 def fset(self, value): 183 assert isinstance(value, basestring) 184 self.setAttr('label', value) 185 186 def fdel(self): 187 if self.getAttr('label'): 188 self.delAttr('label') 189 190 return locals() 191 192 @nested_property
193 - def description():
194 """ 195 Human-readable description of field meaning 196 """ 197 def fget(self): 198 return self.getTagData('desc') or u''
199 200 def fset(self, value): 201 assert isinstance(value, basestring) 202 if value == '': 203 fdel(self) 204 else: 205 self.setTagData('desc', value) 206 207 def fdel(self): 208 t = self.getTag('desc') 209 if t is not None: 210 self.delChild(t) 211 212 return locals() 213 214 @nested_property
215 - def required():
216 """ 217 Controls whether this field required to fill. Boolean 218 """ 219 def fget(self): 220 return bool(self.getTag('required'))
221 222 def fset(self, value): 223 t = self.getTag('required') 224 if t and not value: 225 self.delChild(t) 226 elif not t and value: 227 self.addChild('required') 228 229 return locals() 230 231 @nested_property
232 - def media():
233 """ 234 Media data 235 """ 236 def fget(self): 237 media = self.getTag('media', namespace=xmpp.NS_DATA_MEDIA) 238 if media: 239 return Media(media)
240 241 def fset(self, value): 242 fdel(self) 243 self.addChild(node=value) 244 245 def fdel(self): 246 t = self.getTag('media') 247 if t is not None: 248 self.delChild(t) 249 250 return locals() 251
252 - def is_valid(self):
253 return True
254
255 -class Uri(xmpp.Node):
256 - def __init__(self, uri_tag):
257 xmpp.Node.__init__(self, node=uri_tag)
258 259 @nested_property
260 - def type_():
261 """ 262 uri type 263 """ 264 def fget(self): 265 return self.getAttr('type')
266 267 def fset(self, value): 268 self.setAttr('type', value)
269 270 def fdel(self): 271 self.delAttr('type') 272 273 return locals() 274 275 @nested_property
276 - def uri_data():
277 """ 278 uri data 279 """ 280 def fget(self): 281 return self.getData()
282 283 def fset(self, value): 284 self.setData(value) 285 286 def fdel(self): 287 self.setData(None) 288 289 return locals() 290
291 -class Media(xmpp.Node):
292 - def __init__(self, media_tag):
293 xmpp.Node.__init__(self, node=media_tag)
294 295 @nested_property
296 - def uris():
297 """ 298 URIs of the media element. 299 """ 300 def fget(self): 301 return map(Uri, self.getTags('uri'))
302 303 def fset(self, value): 304 fdel(self) 305 for uri in values: 306 self.addChild(node=uri)
307 308 def fdel(self, value): 309 for element in self.getTags('uri'): 310 self.delChild(element) 311 312 return locals() 313
314 -class BooleanField(DataField):
315 @nested_property
316 - def value():
317 """ 318 Value of field. May contain True, False or None 319 """ 320 def fget(self): 321 v = self.getTagData('value') 322 if v in ('0', 'false'): 323 return False 324 if v in ('1', 'true'): 325 return True 326 if v is None: 327 return False # default value is False 328 raise WrongFieldValue
329 330 def fset(self, value): 331 self.setTagData('value', value and '1' or '0')
332 333 def fdel(self, value): 334 t = self.getTag('value') 335 if t is not None: 336 self.delChild(t) 337 338 return locals() 339
340 -class StringField(DataField):
341 """ 342 Covers fields of types: fixed, hidden, text-private, text-single 343 """ 344 345 @nested_property
346 - def value():
347 """ 348 Value of field. May be any unicode string 349 """ 350 def fget(self): 351 return self.getTagData('value') or u''
352 353 def fset(self, value): 354 assert isinstance(value, basestring) 355 if value == '' and not self.required: 356 return fdel(self) 357 self.setTagData('value', value)
358 359 def fdel(self): 360 try: 361 self.delChild(self.getTag('value')) 362 except ValueError: # if there already were no value tag 363 pass 364 365 return locals() 366
367 -class ListField(DataField):
368 """ 369 Covers fields of types: jid-multi, jid-single, list-multi, list-single 370 """ 371 372 @nested_property
373 - def options():
374 """ 375 Options 376 """ 377 def fget(self): 378 options = [] 379 for element in self.getTags('option'): 380 v = element.getTagData('value') 381 if v is None: 382 raise WrongFieldValue 383 l = element.getAttr('label') 384 if not l: 385 l = v 386 options.append((l, v)) 387 return options
388 389 def fset(self, values): 390 fdel(self) 391 for value, label in values: 392 self.addChild('option', {'label': label}).setTagData('value', value)
393 394 def fdel(self): 395 for element in self.getTags('option'): 396 self.delChild(element) 397 398 return locals() 399
400 - def iter_options(self):
401 for element in self.iterTags('option'): 402 v = element.getTagData('value') 403 if v is None: 404 raise WrongFieldValue 405 l = element.getAttr('label') 406 if not l: 407 l = v 408 yield (v, l)
409
410 -class ListSingleField(ListField, StringField):
411 """ 412 Covers list-single field 413 """ 414 pass
415
416 -class JidSingleField(ListSingleField):
417 """ 418 Covers jid-single fields 419 """
420 - def is_valid(self):
421 if self.value: 422 try: 423 helpers.parse_jid(self.value) 424 return True 425 except: 426 return False 427 if self.required: 428 return False 429 return True
430
431 -class ListMultiField(ListField):
432 """ 433 Covers list-multi fields 434 """ 435 436 @nested_property
437 - def values():
438 """ 439 Values held in field 440 """ 441 def fget(self): 442 values = [] 443 for element in self.getTags('value'): 444 values.append(element.getData()) 445 return values
446 447 def fset(self, values): 448 fdel(self) 449 for value in values: 450 self.addChild('value').setData(value)
451 452 def fdel(self): 453 for element in self.getTags('value'): 454 self.delChild(element) 455 456 return locals() 457
458 - def iter_values(self):
459 for element in self.getTags('value'): 460 yield element.getData()
461
462 -class JidMultiField(ListMultiField):
463 """ 464 Covers jid-multi fields 465 """
466 - def is_valid(self):
467 if len(self.values): 468 for value in self.values: 469 try: 470 helpers.parse_jid(self.value) 471 except: 472 return False 473 return True 474 if self.required: 475 return False 476 return True
477
478 -class TextMultiField(DataField):
479 @nested_property
480 - def value():
481 """ 482 Value held in field 483 """ 484 def fget(self): 485 value = u'' 486 for element in self.iterTags('value'): 487 value += '\n' + element.getData() 488 return value[1:]
489 490 def fset(self, value): 491 fdel(self) 492 if value == '': 493 return 494 for line in value.split('\n'): 495 self.addChild('value').setData(line)
496 497 def fdel(self): 498 for element in self.getTags('value'): 499 self.delChild(element) 500 501 return locals() 502
503 -class DataRecord(ExtendedNode):
504 """ 505 The container for data fields - an xml element which has DataField elements 506 as children 507 """
508 - def __init__(self, fields=None, associated=None, extend=None):
509 self.associated = associated 510 self.vars = {} 511 if extend is None: 512 # we have to build this object from scratch 513 xmpp.Node.__init__(self) 514 515 if fields is not None: 516 self.fields = fields 517 else: 518 # we already have xmpp.Node inside - try to convert all 519 # fields into DataField objects 520 if fields is None: 521 for field in self.iterTags('field'): 522 if not isinstance(field, DataField): 523 ExtendField(field) 524 self.vars[field.var] = field 525 else: 526 for field in self.getTags('field'): 527 self.delChild(field) 528 self.fields = fields
529 530 @nested_property
531 - def fields():
532 """ 533 List of fields in this record 534 """ 535 def fget(self): 536 return self.getTags('field')
537 538 def fset(self, fields): 539 fdel(self) 540 for field in fields: 541 if not isinstance(field, DataField): 542 ExtendField(extend=field) 543 self.addChild(node=field)
544 545 def fdel(self): 546 for element in self.getTags('field'): 547 self.delChild(element) 548 549 return locals() 550
551 - def iter_fields(self):
552 """ 553 Iterate over fields in this record. Do not take associated into account 554 """ 555 for field in self.iterTags('field'): 556 yield field
557
558 - def iter_with_associated(self):
559 """ 560 Iterate over associated, yielding both our field and associated one 561 together 562 """ 563 for field in self.associated.iter_fields(): 564 yield self[field.var], field
565
566 - def __getitem__(self, item):
567 return self.vars[item]
568
569 - def is_valid(self):
570 for f in self.iter_fields(): 571 if not f.is_valid(): 572 return False 573 return True
574
575 -class DataForm(ExtendedNode):
576 - def __init__(self, type_=None, title=None, instructions=None, extend=None):
577 if extend is None: 578 # we have to build form from scratch 579 xmpp.Node.__init__(self, 'x', attrs={'xmlns': xmpp.NS_DATA}) 580 581 if type_ is not None: 582 self.type_=type_ 583 if title is not None: 584 self.title=title 585 if instructions is not None: 586 self.instructions=instructions
587 588 @nested_property
589 - def type():
590 """ 591 Type of the form. Must be one of: 'form', 'submit', 'cancel', 'result'. 592 'form' - this form is to be filled in; you will be able soon to do: 593 filledform = DataForm(replyto=thisform) 594 """ 595 def fget(self): 596 return self.getAttr('type')
597 598 def fset(self, type_): 599 assert type_ in ('form', 'submit', 'cancel', 'result') 600 self.setAttr('type', type_)
601 602 return locals() 603 604 @nested_property
605 - def title():
606 """ 607 Title of the form 608 609 Human-readable, should not contain any \\r\\n. 610 """ 611 def fget(self): 612 return self.getTagData('title')
613 614 def fset(self, title): 615 self.setTagData('title', title) 616 617 def fdel(self): 618 try: 619 self.delChild('title') 620 except ValueError: 621 pass 622 623 return locals() 624 625 @nested_property
626 - def instructions():
627 """ 628 Instructions for this form 629 630 Human-readable, may contain \\r\\n. 631 """ 632 # TODO: the same code is in TextMultiField. join them 633 def fget(self): 634 value = u'' 635 for valuenode in self.getTags('instructions'): 636 value += '\n' + valuenode.getData() 637 return value[1:]
638 639 def fset(self, value): 640 fdel(self) 641 if value == '': return 642 for line in value.split('\n'): 643 self.addChild('instructions').setData(line) 644 645 def fdel(self): 646 for value in self.getTags('instructions'): 647 self.delChild(value) 648 649 return locals() 650
651 -class SimpleDataForm(DataForm, DataRecord):
652 - def __init__(self, type_=None, title=None, instructions=None, fields=None, \ 653 extend=None):
654 DataForm.__init__(self, type_=type_, title=title, 655 instructions=instructions, extend=extend) 656 DataRecord.__init__(self, fields=fields, extend=self, associated=self)
657
658 - def get_purged(self):
659 c = SimpleDataForm(extend=self) 660 del c.title 661 c.instructions = '' 662 to_be_removed = [] 663 for f in c.iter_fields(): 664 if f.required: 665 # add <value> if there is not 666 if hasattr(f, 'value') and not f.value: 667 f.value = '' 668 if hasattr(f, 'values') and not f.values: 669 f.values = [''] 670 # Keep all required fields 671 continue 672 if (hasattr(f, 'value') and not f.value) or (hasattr(f, 'values') \ 673 and len(f.values) == 0): 674 to_be_removed.append(f) 675 else: 676 del f.label 677 del f.description 678 del f.media 679 for f in to_be_removed: 680 c.delChild(f) 681 return c
682
683 -class MultipleDataForm(DataForm):
684 - def __init__(self, type_=None, title=None, instructions=None, items=None, 685 extend=None):
686 DataForm.__init__(self, type_=type_, title=title, 687 instructions=instructions, extend=extend) 688 # all records, recorded into DataRecords 689 if extend is None: 690 if items is not None: 691 self.items = items 692 else: 693 # we already have xmpp.Node inside - try to convert all 694 # fields into DataField objects 695 if items is None: 696 self.items = list(self.iterTags('item')) 697 else: 698 for item in self.getTags('item'): 699 self.delChild(item) 700 self.items = items 701 reported_tag = self.getTag('reported') 702 self.reported = DataRecord(extend=reported_tag)
703 704 @nested_property
705 - def items():
706 """ 707 A list of all records 708 """ 709 def fget(self): 710 return list(self.iter_records())
711 712 def fset(self, records): 713 fdel(self) 714 for record in records: 715 if not isinstance(record, DataRecord): 716 DataRecord(extend=record) 717 self.addChild(node=record)
718 719 def fdel(self): 720 for record in self.getTags('item'): 721 self.delChild(record) 722 723 return locals() 724
725 - def iter_records(self):
726 for record in self.getTags('item'): 727 yield record
728 729 # @nested_property 730 # def reported(): 731 # """ 732 # DataRecord that contains descriptions of fields in records 733 # """ 734 # def fget(self): 735 # return self.getTag('reported') 736 # def fset(self, record): 737 # try: 738 # self.delChild('reported') 739 # except: 740 # pass 741 # 742 # record.setName('reported') 743 # self.addChild(node=record) 744 # return locals() 745