Changeset 25:fd00a744a458 for wokkel
- Timestamp:
- Jul 7, 2008, 4:29:02 PM (14 years ago)
- Branch:
- default
- Convert:
- svn:b33ecbfc-034c-dc11-8662-000475d9059e/trunk@55
- Location:
- wokkel
- Files:
-
- 4 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 -
wokkel/iwokkel.py
r24 r25 390 390 """ 391 391 392 def getConfigurationOptions(): 393 """ 394 Retrieve all known node configuration options. 395 396 The returned dictionary holds the possible node configuration options 397 by option name. The value of each entry represents the specifics for 398 that option in a dictionary: 399 400 - C{'type'} (C{str}): The option's type (see 401 L{Field<wokkel.data_form.Field>}'s doc string for possible values). 402 - C{'label'} (C{unicode}): A human readable label for this option. 403 - C{'options'} (C{dict}): Optional list of possible values for this 404 option. 405 406 Example:: 407 408 { 409 "pubsub#persist_items": 410 {"type": "boolean", 411 "label": "Persist items to storage"}, 412 "pubsub#deliver_payloads": 413 {"type": "boolean", 414 "label": "Deliver payloads with event notifications"}, 415 "pubsub#send_last_published_item": 416 {"type": "list-single", 417 "label": "When to send the last published item", 418 "options": { 419 "never": "Never", 420 "on_sub": "When a new subscription is processed"} 421 } 422 } 423 424 @rtype: C{dict}. 425 """ 426 392 427 def getDefaultConfiguration(requestor, service): 393 428 """ -
wokkel/pubsub.py
r24 r25 174 174 self.command = self.pubsub.addElement(verb) 175 175 176 176 177 def send(self, to): 177 178 """ … … 199 200 self.xmlstream.addObserver('/message/event[@xmlns="%s"]' % 200 201 NS_PUBSUB_EVENT, self._onEvent) 202 201 203 202 204 def _onEvent(self, message): … … 271 273 return request.send(service).addCallback(cb) 272 274 275 273 276 def deleteNode(self, service, nodeIdentifier): 274 277 """ … … 283 286 request.command['node'] = nodeIdentifier 284 287 return request.send(service) 288 285 289 286 290 def subscribe(self, service, nodeIdentifier, subscriber): … … 315 319 return request.send(service).addCallback(cb) 316 320 321 317 322 def unsubscribe(self, service, nodeIdentifier, subscriber): 318 323 """ … … 331 336 return request.send(service) 332 337 338 333 339 def publish(self, service, nodeIdentifier, items=None): 334 340 """ … … 349 355 350 356 return request.send(service) 357 351 358 352 359 def items(self, service, nodeIdentifier, maxItems=None): … … 431 438 } 432 439 440 433 441 def __init__(self): 434 442 self.discoIdentity = {'category': 'pubsub', … … 437 445 438 446 self.pubSubFeatures = [] 447 439 448 440 449 def connectionMade(self): … … 444 453 self.xmlstream.addObserver(PUBSUB_OWNER_SET, self.handleRequest) 445 454 455 446 456 def getDiscoInfo(self, requestor, target, nodeIdentifier): 447 457 info = [] … … 454 464 for feature in self.pubSubFeatures]) 455 465 456 return defer.succeed(info) 457 else: 458 def toInfo(nodeInfo): 459 if not nodeInfo: 460 return [] 461 462 (nodeType, metaData) = nodeInfo['type'], nodeInfo['meta-data'] 463 info.append(disco.DiscoIdentity('pubsub', nodeType)) 464 if metaData: 465 form = data_form.Form(type="result", 466 form_type=NS_PUBSUB_META_DATA) 467 form.add_field("text-single", 468 "pubsub#node_type", 469 "The type of node (collection or leaf)", 470 nodeType) 471 472 for metaDatum in metaData: 473 form.add_field(**metaDatum) 474 475 info.append(form) 476 return info 477 478 d = self.getNodeInfo(requestor, target, nodeIdentifier) 479 d.addCallback(toInfo) 480 return d 466 def toInfo(nodeInfo): 467 if not nodeInfo: 468 return 469 470 (nodeType, metaData) = nodeInfo['type'], nodeInfo['meta-data'] 471 info.append(disco.DiscoIdentity('pubsub', nodeType)) 472 if metaData: 473 form = data_form.Form(formType="result", 474 formNamespace=NS_PUBSUB_META_DATA) 475 form.fields.append( 476 data_form.Field( 477 var='pubsub#node_type', 478 value=nodeType, 479 label='The type of node (collection or leaf)' 480 ) 481 ) 482 483 for metaDatum in metaData: 484 form.fields.append(data_form.Field.fromDict(metaDatum)) 485 486 info.append(form.toElement()) 487 488 d = self.getNodeInfo(requestor, target, nodeIdentifier or '') 489 d.addCallback(toInfo) 490 d.addBoth(lambda result: info) 491 return d 492 481 493 482 494 def getDiscoItems(self, requestor, target, nodeIdentifier): … … 489 501 return d 490 502 503 504 def _findForm(self, element, formNamespace): 505 if not element: 506 return None 507 508 form = None 509 for child in element.elements(): 510 try: 511 form = data_form.Form.fromElement(child) 512 except data_form.Error: 513 continue 514 515 if form.formNamespace != NS_PUBSUB_NODE_CONFIG: 516 continue 517 518 return form 519 520 491 521 def _onPublish(self, iq): 492 522 requestor = jid.internJID(iq["from"]).userhostJID() … … 504 534 505 535 return self.publish(requestor, service, nodeIdentifier, items) 536 506 537 507 538 def _onSubscribe(self, iq): … … 528 559 return d 529 560 561 530 562 def _onUnsubscribe(self, iq): 531 563 requestor = jid.internJID(iq["from"]).userhostJID() … … 540 572 return self.unsubscribe(requestor, service, nodeIdentifier, subscriber) 541 573 574 542 575 def _onOptionsGet(self, iq): 543 576 raise Unsupported('subscription-options-unavailable') 544 577 578 545 579 def _onOptionsSet(self, iq): 546 580 raise Unsupported('subscription-options-unavailable') 581 547 582 548 583 def _onSubscriptions(self, iq): … … 564 599 return d 565 600 601 566 602 def _onAffiliations(self, iq): 567 603 requestor = jid.internJID(iq["from"]).userhostJID() … … 582 618 d.addCallback(toResponse) 583 619 return d 620 584 621 585 622 def _onCreate(self, iq): … … 601 638 return d 602 639 603 def _formFromConfiguration(self, options): 604 form = data_form.Form(type="form", form_type=NS_PUBSUB_NODE_CONFIG) 605 606 for option in options: 607 form.add_field(**option) 640 641 def _formFromConfiguration(self, values): 642 options = self.getConfigurationOptions() 643 form = data_form.Form(formType="form", 644 formNamespace=NS_PUBSUB_NODE_CONFIG) 645 646 for name, value in values.iteritems(): 647 if name in options: 648 option = {'var': name} 649 option.update(options[name]) 650 if isinstance(value, list): 651 option['values'] = value 652 else: 653 option['value'] = value 654 form.fields.append(data_form.Field.fromDict(option)) 608 655 609 656 return form 657 610 658 611 659 def _onDefault(self, iq): … … 616 664 response = domish.Element((NS_PUBSUB_OWNER, "pubsub")) 617 665 default = response.addElement("default") 618 default.addChild(self._formFromConfiguration(options) )666 default.addChild(self._formFromConfiguration(options).toElement()) 619 667 return response 620 668 … … 623 671 return d 624 672 673 625 674 def _onConfigureGet(self, iq): 626 675 requestor = jid.internJID(iq["from"]).userhostJID() … … 631 680 response = domish.Element((NS_PUBSUB_OWNER, "pubsub")) 632 681 configure = response.addElement("configure") 633 configure.addChild(self._formFromConfiguration(options) )682 configure.addChild(self._formFromConfiguration(options).toElement()) 634 683 635 684 if nodeIdentifier: … … 642 691 return d 643 692 693 644 694 def _onConfigureSet(self, iq): 645 695 requestor = jid.internJID(iq["from"]).userhostJID() … … 647 697 nodeIdentifier = iq.pubsub.configure["node"] 648 698 649 def getFormOptions(form):650 options = {}651 652 for element in form.elements():653 if element.name == 'field' and \654 element.uri == data_form.NS_X_DATA:655 try:656 options[element["var"]] = str(element.value)657 except (KeyError, AttributeError):658 raise BadRequest659 660 return options661 662 699 # Search configuration form with correct FORM_TYPE and process it 663 700 664 for element in iq.pubsub.configure.elements(): 665 if element.name != 'x' or element.uri != data_form.NS_X_DATA: 666 continue 667 668 type = element.getAttribute("type") 669 if type == "cancel": 670 return None 671 elif type != "submit": 672 continue 673 674 options = getFormOptions(element) 675 676 if options["FORM_TYPE"] == NS_PUBSUB + "#node_config": 677 del options["FORM_TYPE"] 701 form = self._findForm(iq.pubsub.configure, NS_PUBSUB_NODE_CONFIG) 702 703 if form: 704 if form.formType == 'submit': 705 options = form.getValues() 706 678 707 return self.setConfiguration(requestor, service, 679 708 nodeIdentifier, options) 680 681 raise BadRequest 709 elif form.formType == 'cancel': 710 return None 711 712 raise BadRequest() 713 682 714 683 715 def _onItems(self, iq): … … 721 753 return d 722 754 755 723 756 def _onRetract(self, iq): 724 757 requestor = jid.internJID(iq["from"]).userhostJID() … … 741 774 itemIdentifiers) 742 775 776 743 777 def _onPurge(self, iq): 744 778 requestor = jid.internJID(iq["from"]).userhostJID() … … 752 786 return self.purge(requestor, service, nodeIdentifier) 753 787 788 754 789 def _onDelete(self, iq): 755 790 requestor = jid.internJID(iq["from"]).userhostJID() … … 763 798 return self.delete(requestor, service, nodeIdentifier) 764 799 800 765 801 def _onAffiliationsGet(self, iq): 766 802 raise Unsupported('modify-affiliations') 767 803 804 768 805 def _onAffiliationsSet(self, iq): 769 806 raise Unsupported('modify-affiliations') 770 807 808 771 809 def _onSubscriptionsGet(self, iq): 772 810 raise Unsupported('manage-subscriptions') 811 773 812 774 813 def _onSubscriptionsSet(self, iq): … … 788 827 self.send(message) 789 828 829 790 830 def notifyDelete(self, service, nodeIdentifier, recipients): 791 831 for recipient in recipients: … … 798 838 self.send(message) 799 839 840 800 841 def getNodeInfo(self, requestor, service, nodeIdentifier): 801 842 return None 802 843 844 803 845 def getNodes(self, requestor, service): 804 846 return [] 805 847 848 806 849 def publish(self, requestor, service, nodeIdentifier, items): 807 850 raise Unsupported('publish') 808 851 852 809 853 def subscribe(self, requestor, service, nodeIdentifier, subscriber): 810 854 raise Unsupported('subscribe') 811 855 856 812 857 def unsubscribe(self, requestor, service, nodeIdentifier, subscriber): 813 858 raise Unsupported('subscribe') 814 859 860 815 861 def subscriptions(self, requestor, service): 816 862 raise Unsupported('retrieve-subscriptions') 817 863 864 818 865 def affiliations(self, requestor, service): 819 866 raise Unsupported('retrieve-affiliations') 820 867 868 821 869 def create(self, requestor, service, nodeIdentifier): 822 870 raise Unsupported('create-nodes') 823 871 872 873 def getConfigurationOptions(self): 874 return {} 875 876 824 877 def getDefaultConfiguration(self, requestor, service): 825 878 raise Unsupported('retrieve-default') 826 879 880 827 881 def getConfiguration(self, requestor, service, nodeIdentifier): 828 882 raise Unsupported('config-node') 829 883 884 830 885 def setConfiguration(self, requestor, service, nodeIdentifier, options): 831 886 raise Unsupported('config-node') 887 832 888 833 889 def items(self, requestor, service, nodeIdentifier, maxItems, … … 835 891 raise Unsupported('retrieve-items') 836 892 893 837 894 def retract(self, requestor, service, nodeIdentifier, itemIdentifiers): 838 895 raise Unsupported('retract-items') 839 896 897 840 898 def purge(self, requestor, service, nodeIdentifier): 841 899 raise Unsupported('purge-nodes') 842 900 901 843 902 def delete(self, requestor, service, nodeIdentifier): 844 903 raise Unsupported('delete-nodes') -
wokkel/test/test_pubsub.py
r24 r25 5 5 Tests for L{wokkel.pubsub} 6 6 """ 7 7 8 from zope.interface import verify 8 9 … … 13 14 from twisted.words.protocols.jabber.jid import JID 14 15 15 from wokkel import iwokkel, pubsub16 from wokkel import data_form, iwokkel, pubsub 16 17 from wokkel.generic import parseXml 17 18 from wokkel.test.helpers import XmlStreamStub … … 23 24 24 25 NS_PUBSUB = 'http://jabber.org/protocol/pubsub' 26 NS_PUBSUB_CONFIG = 'http://jabber.org/protocol/pubsub#node_config' 25 27 NS_PUBSUB_ERRORS = 'http://jabber.org/protocol/pubsub#errors' 26 28 NS_PUBSUB_EVENT = 'http://jabber.org/protocol/pubsub#event' 29 NS_PUBSUB_OWNER = 'http://jabber.org/protocol/pubsub#owner' 27 30 28 31 def calledAsync(fn): … … 445 448 """ 446 449 447 def handleRequest(self, handler, iq): 450 def setUp(self): 451 self.service = pubsub.PubSubService() 452 453 def handleRequest(self, xml): 448 454 """ 449 455 Find a handler and call it directly 450 456 """ 451 for queryString, method in handler.iqHandlers.iteritems(): 457 handler = None 458 iq = parseXml(xml) 459 for queryString, method in self.service.iqHandlers.iteritems(): 452 460 if xpath.internQuery(queryString).matches(iq): 453 handler = getattr( handler, method)461 handler = getattr(self.service, method) 454 462 455 463 if handler: … … 465 473 Do instances of L{pubsub.PubSubService} provide L{iwokkel.IPubSubService}? 466 474 """ 467 verify.verifyObject(iwokkel.IPubSubService, pubsub.PubSubService())475 verify.verifyObject(iwokkel.IPubSubService, self.service) 468 476 469 477 … … 484 492 self.assertEquals('bad-request', result.condition) 485 493 486 handler = pubsub.PubSubService() 487 d = self.handleRequest(handler, parseXml(xml)) 494 d = self.handleRequest(xml) 488 495 self.assertFailure(d, error.StanzaError) 489 496 d.addCallback(cb) 490 497 return d 498 491 499 492 500 def test_onPublish(self): … … 495 503 called. 496 504 """ 497 class Handler(pubsub.PubSubService):498 def publish(self, requestor, service, nodeIdentifier, items):499 return defer.succeed((requestor, service,nodeIdentifier,500 items))501 505 502 506 xml = """ … … 509 513 """ 510 514 511 def cb(result): 512 self.assertEqual((JID('user@example.org'), 513 JID('pubsub.example.org'),'test', []), result) 514 515 handler = Handler() 516 d = self.handleRequest(handler, parseXml(xml)) 517 d.addCallback(cb) 518 return d 515 def publish(requestor, service, nodeIdentifier, items): 516 self.assertEqual(JID('user@example.org'), requestor) 517 self.assertEqual(JID('pubsub.example.org'), service) 518 self.assertEqual('test', nodeIdentifier) 519 self.assertEqual([], items) 520 return defer.succeed(None) 521 522 self.service.publish = publish 523 return self.handleRequest(xml) 519 524 520 525 … … 524 529 """ 525 530 526 handler = pubsub.PubSubService()527 531 xml = """ 528 532 <iq type='get' to='pubsub.example.org' … … 539 543 self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) 540 544 541 handler = pubsub.PubSubService() 542 d = self.handleRequest(handler, parseXml(xml)) 545 d = self.handleRequest(xml) 543 546 self.assertFailure(d, error.StanzaError) 544 547 d.addCallback(cb) … … 546 549 547 550 551 def test_onDefault(self): 552 """ 553 A default request should result in 554 L{PubSubService.getDefaultConfiguration} being called. 555 """ 556 557 xml = """ 558 <iq type='get' to='pubsub.example.org' 559 from='user@example.org'> 560 <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'> 561 <default/> 562 </pubsub> 563 </iq> 564 """ 565 566 def getConfigurationOptions(): 567 return { 568 "pubsub#persist_items": 569 {"type": "boolean", 570 "label": "Persist items to storage"}, 571 "pubsub#deliver_payloads": 572 {"type": "boolean", 573 "label": "Deliver payloads with event notifications"} 574 } 575 576 def getDefaultConfiguration(requestor, service): 577 self.assertEqual(JID('user@example.org'), requestor) 578 self.assertEqual(JID('pubsub.example.org'), service) 579 return defer.succeed({}) 580 581 def cb(element): 582 self.assertEqual('pubsub', element.name) 583 self.assertEqual(NS_PUBSUB_OWNER, element.uri) 584 self.assertEqual(NS_PUBSUB_OWNER, element.default.uri) 585 form = data_form.Form.fromElement(element.default.x) 586 self.assertEqual(NS_PUBSUB_CONFIG, form.formNamespace) 587 588 self.service.getConfigurationOptions = getConfigurationOptions 589 self.service.getDefaultConfiguration = getDefaultConfiguration 590 d = self.handleRequest(xml) 591 d.addCallback(cb) 592 return d 593 594 595 def test_onConfigureGet(self): 596 """ 597 On a node configuration get request L{PubSubService.getConfiguration} 598 is called and results in a data form with the configuration. 599 """ 600 601 xml = """ 602 <iq type='get' to='pubsub.example.org' 603 from='user@example.org'> 604 <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'> 605 <configure node='test'/> 606 </pubsub> 607 </iq> 608 """ 609 610 def getConfigurationOptions(): 611 return { 612 "pubsub#persist_items": 613 {"type": "boolean", 614 "label": "Persist items to storage"}, 615 "pubsub#deliver_payloads": 616 {"type": "boolean", 617 "label": "Deliver payloads with event notifications"} 618 } 619 620 def getConfiguration(requestor, service, nodeIdentifier): 621 self.assertEqual(JID('user@example.org'), requestor) 622 self.assertEqual(JID('pubsub.example.org'), service) 623 self.assertEqual('test', nodeIdentifier) 624 625 return defer.succeed({'pubsub#deliver_payloads': '0', 626 'pubsub#persist_items': '1'}) 627 628 def cb(element): 629 self.assertEqual('pubsub', element.name) 630 self.assertEqual(NS_PUBSUB_OWNER, element.uri) 631 self.assertEqual(NS_PUBSUB_OWNER, element.configure.uri) 632 form = data_form.Form.fromElement(element.configure.x) 633 self.assertEqual(NS_PUBSUB_CONFIG, form.formNamespace) 634 fields = dict([(field.var, field) for field in form.fields]) 635 636 self.assertIn('pubsub#deliver_payloads', fields) 637 field = fields['pubsub#deliver_payloads'] 638 self.assertEqual('boolean', field.fieldType) 639 self.assertEqual(True, field.value) 640 641 self.assertIn('pubsub#persist_items', fields) 642 field = fields['pubsub#persist_items'] 643 self.assertEqual('boolean', field.fieldType) 644 self.assertEqual(True, field.value) 645 646 self.service.getConfigurationOptions = getConfigurationOptions 647 self.service.getConfiguration = getConfiguration 648 d = self.handleRequest(xml) 649 d.addCallback(cb) 650 return d 651 652 653 def test_onConfigureSet(self): 654 """ 655 On a node configuration set request the Data Form is parsed and 656 L{PubSubService.setConfiguration} is called with the passed options. 657 """ 658 659 xml = """ 660 <iq type='set' to='pubsub.example.org' 661 from='user@example.org'> 662 <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'> 663 <configure node='test'> 664 <x xmlns='jabber:x:data' type='submit'> 665 <field var='FORM_TYPE' type='hidden'> 666 <value>http://jabber.org/protocol/pubsub#node_config</value> 667 </field> 668 <field var='pubsub#deliver_payloads'><value>0</value></field> 669 <field var='pubsub#persist_items'><value>1</value></field> 670 </x> 671 </configure> 672 </pubsub> 673 </iq> 674 """ 675 676 def setConfiguration(requestor, service, nodeIdentifier, options): 677 self.assertEqual(JID('user@example.org'), requestor) 678 self.assertEqual(JID('pubsub.example.org'), service) 679 self.assertEqual('test', nodeIdentifier) 680 self.assertEqual({'pubsub#deliver_payloads': '0', 681 'pubsub#persist_items': '1'}, options) 682 return defer.succeed(None) 683 684 self.service.setConfiguration = setConfiguration 685 return self.handleRequest(xml) 686 687 688 def test_onConfigureSetCancel(self): 689 """ 690 The node configuration is cancelled, L{PubSubService.setConfiguration} 691 not called. 692 """ 693 694 xml = """ 695 <iq type='set' to='pubsub.example.org' 696 from='user@example.org'> 697 <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'> 698 <configure node='test'> 699 <x xmlns='jabber:x:data' type='cancel'> 700 <field var='FORM_TYPE' type='hidden'> 701 <value>http://jabber.org/protocol/pubsub#node_config</value> 702 </field> 703 </x> 704 </configure> 705 </pubsub> 706 </iq> 707 """ 708 709 def setConfiguration(requestor, service, nodeIdentifier, options): 710 self.fail("Unexpected call to setConfiguration") 711 712 self.service.setConfiguration = setConfiguration 713 return self.handleRequest(xml) 714 715 548 716 def test_onItems(self): 549 717 """ 550 718 On a items request, return all items for the given node. 551 719 """ 552 class Handler(pubsub.PubSubService):553 def items(self, *args, **kwargs):554 self.args = args555 self.kwargs = kwargs556 return defer.succeed([pubsub.Item('current')])557 558 720 xml = """ 559 721 <iq type='get' to='pubsub.example.org' … … 565 727 """ 566 728 729 def items(requestor, service, nodeIdentifier, maxItems, items): 730 self.assertEqual(JID('user@example.org'), requestor) 731 self.assertEqual(JID('pubsub.example.org'), service) 732 self.assertEqual('test', nodeIdentifier) 733 self.assertIdentical(None, maxItems) 734 self.assertEqual([], items) 735 return defer.succeed([pubsub.Item('current')]) 736 567 737 def cb(element): 568 self.assertEqual((JID('user@example.org'),569 JID('pubsub.example.org'), 'test', None, []),570 handler.args)571 572 738 self.assertEqual(NS_PUBSUB, element.uri) 573 739 self.assertEqual(NS_PUBSUB, element.items.uri) … … 579 745 self.assertEqual('current', item['id']) 580 746 581 handler = Handler()582 d = self.handleRequest( handler, parseXml(xml))583 d.addCallback(cb) 584 return d 747 self.service.items = items 748 d = self.handleRequest(xml) 749 d.addCallback(cb) 750 return d
Note: See TracChangeset
for help on using the changeset viewer.