source: ralphm-patches/pubsub-subscription-options.patch @ 19:48bd45f41903

Last change on this file since 19:48bd45f41903 was 19:48bd45f41903, checked in by Ralph Meijer <ralphm@…>, 11 years ago

Further adjustments on type checking, a first test for parsing subscription
options.

File size: 13.4 KB
  • wokkel/data_form.py

    diff -r 2919e60d3588 wokkel/data_form.py
    a b  
    7676            option['label'] = self.label
    7777        return option
    7878
     79
    7980    @staticmethod
    8081    def fromElement(element):
    8182        valueElements = list(domish.generateElementsQNamed(element.children,
     
    212213
    213214        if self.values:
    214215            if (self.fieldType not in ('hidden', 'jid-multi', 'list-multi',
    215                                  'text-multi') and
     216                                       'text-multi') and
    216217                len(self.values) > 1):
    217218                raise TooManyValuesError()
    218219
     
    233234
    234235            self.values = newValues
    235236
     237
    236238    def toElement(self, asForm=False):
    237239        """
    238240        Return the DOM representation of this Field.
     
    256258            elif self.fieldType in ('jid-single', 'jid-multi'):
    257259                value = value.full()
    258260
    259             field.addElement('value', content=value)
     261            field.addElement('value', content=unicode(value))
    260262
    261263        if asForm:
    262264            if self.fieldType in ('list-single', 'list-multi'):
     
    322324
    323325
    324326    @staticmethod
    325     def fromDict(dictionary):
    326         kwargs = dictionary.copy()
     327    def fromDict(fieldDict):
     328        kwargs = fieldDict.copy()
    327329
    328         if 'type' in dictionary:
    329             kwargs['fieldType'] = dictionary['type']
     330        if 'type' in fieldDict:
     331            kwargs['fieldType'] = fieldDict['type']
    330332            del kwargs['type']
    331333
    332         if 'options' in dictionary:
     334        if 'options' in fieldDict:
    333335            options = []
    334             for value, label in dictionary['options'].iteritems():
     336            for value, label in fieldDict['options'].iteritems():
    335337                options.append(Option(value, label))
    336338            kwargs['options'] = options
    337339
     
    416418        self.fieldList.append(field)
    417419
    418420
     421    def makeFields(self, values, fieldDefs=None, filterUnknown=True):
     422        """
     423        Create fields from values and add them to this form.
     424
     425        This creates fields from a mapping of name to value(s) and adds them to
     426        this form. If C{fieldDefs} is not C{None}, this is used to fill in
     427        additional properties of fields, like the field types, labels and
     428        possible options. If C{filterUnknown} is C{True} and C{fieldDefs} is
     429        not C{None}, fields will only be created from C{values} with a
     430        corresponding entry in C{fieldDefs}.
     431        """
     432        for name, value in values.iteritems():
     433            fieldDict = {'var': name}
     434
     435            if fieldDefs is not None:
     436                if name in fieldDefs:
     437                    fieldDict.update(fieldDefs[name])
     438                elif filterUnknown:
     439                    continue
     440
     441            if isinstance(value, list):
     442                fieldDict['values'] = value
     443                if 'type' not in fieldDict:
     444                    if value and hasattr(value[0], 'full'):
     445                        fieldDict['type'] = 'jid-multi'
     446                    else:
     447                        fieldDict['type'] = 'text-multi'
     448            else:
     449                fieldDict['value'] = value
     450                if 'type' not in fieldDict:
     451                    if hasattr(value, 'full'):
     452                        fieldDict['type'] = 'jid-single'
     453                    elif isinstance(value, bool):
     454                        fieldDict['type'] = 'boolean'
     455
     456            self.addField(Field.fromDict(fieldDict))
     457
     458
    419459    def toElement(self):
    420460        form = domish.Element((NS_X_DATA, 'x'))
    421461        form['type'] = self.formType
     
    489529            values[name] = value
    490530
    491531        return values
     532
     533
     534    def typeCheck(self, fieldDefs):
     535        """
     536        Check values of fields according to the field definition.
     537
     538        This method walks all named fields to check their values against their
     539        type. The field definition in C{fieldDefs} is used to check the field
     540        type. Unknown fields are ignored.
     541
     542        If the field type is C{None} (when not set by the receiving entity),
     543        the field definition is used to set the field's type before checking
     544        the values.
     545
     546        @param fieldDefs: Field definitions as a dictionary. See
     547            L{wokkel.iwokkel.IPubSubService.getConfigurationOptions}
     548        @type fieldDefs: C{dict}.
     549        """
     550
     551        for name, field in self.fields.iteritems():
     552            if name in fieldDefs:
     553                if field.fieldType is None:
     554                    field.fieldType = fieldDefs[name]['type']
     555                elif field.fieldType != fieldDefs[name]['type']:
     556                    raise TypeError("Field type for %r is %r, expected %r" %
     557                                    (name,
     558                                     field.fieldType,
     559                                     fieldDefs[name]['type']))
     560                else:
     561                    # Field type is correct
     562                    pass
     563                field.typeCheck()
     564            else:
     565                # Unknown field, ignoring
     566                pass
  • wokkel/pubsub.py

    diff -r 2919e60d3588 wokkel/pubsub.py
    a b  
    228228    # Map request verb to parameter handler names
    229229    _parameters = {
    230230        'publish': ['node', 'items'],
    231         'subscribe': ['nodeOrEmpty', 'jid'],
     231        'subscribe': ['nodeOrEmpty', 'jid', 'optionsWithSubscribe'],
    232232        'unsubscribe': ['nodeOrEmpty', 'jid'],
    233233        'optionsGet': ['nodeOrEmpty', 'jid'],
    234234        'optionsSet': ['nodeOrEmpty', 'jid', 'options'],
     
    445445                self.options = {}
    446446            else:
    447447                raise BadRequest(text="Unexpected form type %r" % form.formType)
    448         else:
     448        elif self.verb == 'optionsSet':
    449449            raise BadRequest(text="Missing options form")
    450450
     451
     452    def _parse_optionsWithSubscribe(self, verbElement):
     453        for element in verbElement.parent.elements():
     454            if element.name == 'options' and element.uri == NS_PUBSUB:
     455                form = PubSubRequest._findForm(element,
     456                                               NS_PUBSUB_SUBSCRIBE_OPTIONS)
     457                if form:
     458                    self.optionsForm = form
     459                    if form.formType == 'submit':
     460                        self.options = form.getValues()
     461                    else:
     462                        BadRequest(text="Unexpected form type %r" % form.formType)
     463
     464
     465    def _render_options(self, verbElement):
     466        form = data_form.Form(formType='submit',
     467                              formNamespace=NS_PUBSUB_SUBSCRIBE_OPTIONS)
     468        form.makeFields(values=self.options)
     469        verbElement.addChild(form.toElement())
     470
     471
     472    def _render_optionsWithSubscribe(self, verbElement):
     473        if self.options:
     474            optionsElement = verbElement.parent.addElement('options')
     475            self._render_options(optionsElement)
     476
     477
    451478    def parseElement(self, element):
    452479        """
    453480        Parse the publish-subscribe verb and parameters out of a request.
    454481        """
    455482        generic.Stanza.parseElement(self, element)
    456483
     484        verbs = []
     485        children = []
    457486        for child in element.pubsub.elements():
    458487            key = (self.stanzaType, child.uri, child.name)
    459488            try:
    460489                verb = self._requestVerbMap[key]
    461490            except KeyError:
    462491                continue
    463             else:
    464                 self.verb = verb
    465                 break
    466492
    467         if not self.verb:
     493            verbs.append(verb)
     494            children.append(child)
     495
     496        if not verbs:
    468497            raise NotImplementedError()
    469498
    470         for parameter in self._parameters[verb]:
     499        if len(verbs) > 1:
     500            if 'optionsSet' in verbs and 'subscribe' in verbs:
     501                self.verb = 'subscribe'
     502                child = children[verbs.index('subscribe')]
     503            else:
     504                raise NotImplementedError()
     505        else:
     506            self.verb = verbs[0]
     507
     508        for parameter in self._parameters[self.verb]:
    471509            getattr(self, '_parse_%s' % parameter)(child)
    472510
    473511
     512
    474513    def send(self, xs):
    475514        """
    476515        Send this request to its recipient.
     
    674713        return request.send(self.xmlstream)
    675714
    676715
    677     def subscribe(self, service, nodeIdentifier, subscriber, sender=None):
     716    def subscribe(self, service, nodeIdentifier, subscriber,
     717                        options=None, sender=None):
    678718        """
    679719        Subscribe to a publish subscribe node.
    680720
     
    685725        @param subscriber: The entity to subscribe to the node. This entity
    686726                           will get notifications of new published items.
    687727        @type subscriber: L{JID}
     728        @param options: Subscription options.
     729        @type options: C{dict}.
    688730        """
    689731        request = PubSubRequest('subscribe')
    690732        request.recipient = service
    691733        request.nodeIdentifier = nodeIdentifier
    692734        request.subscriber = subscriber
     735        request.options = options
    693736        request.sender = sender
    694737
    695738        def cb(iq):
     
    10121055            return None
    10131056
    10141057
    1015     def _makeFields(self, options, values):
    1016         fields = []
    1017         for name, value in values.iteritems():
    1018             if name not in options:
    1019                 continue
    1020 
    1021             option = {'var': name}
    1022             option.update(options[name])
    1023             if isinstance(value, list):
    1024                 option['values'] = value
    1025             else:
    1026                 option['value'] = value
    1027             fields.append(data_form.Field.fromDict(option))
    1028         return fields
    1029 
    1030 
    10311058    def _formFromConfiguration(self, resource, values):
    1032         options = resource.getConfigurationOptions()
    1033         fields = self._makeFields(options, values)
     1059        fieldDefs = resource.getConfigurationOptions()
    10341060        form = data_form.Form(formType="form",
    1035                               formNamespace=NS_PUBSUB_NODE_CONFIG,
    1036                               fields=fields)
    1037 
     1061                              formNamespace=NS_PUBSUB_NODE_CONFIG)
     1062        form.makeFields(values, fieldDefs)
    10381063        return form
    10391064
    10401065
  • wokkel/test/test_pubsub.py

    diff -r 2919e60d3588 wokkel/test/test_pubsub.py
    a b  
    447447        return d
    448448
    449449
     450    def test_subscribeWithOptions(self):
     451        options = {'pubsub#deliver': False}
     452
     453        d = self.protocol.subscribe(JID('pubsub.example.org'), 'test',
     454                                    JID('user@example.org'),
     455                                    options=options)
     456        iq = self.stub.output[-1]
     457
     458        # Check options present
     459        childNames = []
     460        for element in iq.pubsub.elements():
     461            if element.uri == NS_PUBSUB:
     462                childNames.append(element.name)
     463
     464        self.assertEqual(['subscribe', 'options'], childNames)
     465        form = data_form.Form.fromElement(iq.pubsub.options.x)
     466        form.typeCheck({'pubsub#deliver': {'type': 'boolean'}})
     467        self.assertEqual(options, form.getValues())
     468
     469        # Send response
     470        response = toResponse(iq, 'result')
     471        pubsub = response.addElement((NS_PUBSUB, 'pubsub'))
     472        subscription = pubsub.addElement('subscription')
     473        subscription['node'] = 'test'
     474        subscription['jid'] = 'user@example.org'
     475        subscription['subscription'] = 'subscribed'
     476        self.stub.send(response)
     477
     478        return d
     479
     480
    450481    def test_subscribeWithSender(self):
    451482        """
    452483        Test sending subscription request from a specific JID.
     
    718749        self.assertEqual(NS_PUBSUB_ERRORS, err.appCondition.uri)
    719750        self.assertEqual('jid-required', err.appCondition.name)
    720751
     752
     753    def test_fromElementSubscribeWithOptions(self):
     754        """
     755        Test parsing a subscription request.
     756        """
     757
     758        xml = """
     759        <iq type='set' to='pubsub.example.org'
     760                       from='user@example.org'>
     761          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     762            <subscribe node='test' jid='user@example.org/Home'/>
     763            <options>
     764              <x xmlns="jabber:x:data" type='submit'>
     765                <field var='FORM_TYPE' type='hidden'>
     766                  <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
     767                </field>
     768                <field var='pubsub#deliver' type='boolean'
     769                       label='Enable delivery?'>
     770                  <value>1</value>
     771                </field>
     772              </x>
     773            </options>
     774          </pubsub>
     775        </iq>
     776        """
     777
     778        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
     779        self.assertEqual('subscribe', request.verb)
     780        request.optionsForm.typeCheck({'pubsub#deliver': {'type': 'boolean'}})
     781        request.options = request.optionsForm.getValues()
     782        self.assertEqual({'pubsub#deliver': True}, request.options)
     783
     784
    721785    def test_fromElementUnsubscribe(self):
    722786        """
    723787        Test parsing an unsubscription request.
     
    18951959            return defer.succeed({'pubsub#deliver_payloads': '0',
    18961960                                  'pubsub#persist_items': '1',
    18971961                                  'pubsub#owner': JID('user@example.org'),
    1898                                   'x-myfield': ['a', 'b']})
     1962                                  'x-myfield': 'a'})
    18991963
    19001964        def cb(element):
    19011965            self.assertEqual('pubsub', element.name)
Note: See TracBrowser for help on using the repository browser.