source: ralphm-patches/pubsub-forms.patch @ 28:4692e23155a3

Last change on this file since 28:4692e23155a3 was 28:4692e23155a3, checked in by Ralph Meijer <ralphm@…>, 10 years ago

Add PubSubClient?.getOptions, allow form type checking without field defs.

File size: 19.3 KB
  • wokkel/data_form.py

    Move makeFields to wokkel.data_form, add form type checking.
    
    diff -r ba58db23e8b9 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.
     
    322324
    323325
    324326    @staticmethod
    325     def fromDict(dictionary):
    326         kwargs = dictionary.copy()
     327    def fromDict(fieldDict):
     328        """
     329        Create a field from a dictionary.
    327330
    328         if 'type' in dictionary:
    329             kwargs['fieldType'] = dictionary['type']
     331        This is a short hand for passing arguments directly on Field object
     332        creation. The field type is represented by the C{'type'} key. For
     333        C{'options'} the value is not a list of L{Option}s, but a dictionary
     334        keyed by value, with an optional label as value.
     335        """
     336        kwargs = fieldDict.copy()
     337
     338        if 'type' in fieldDict:
     339            kwargs['fieldType'] = fieldDict['type']
    330340            del kwargs['type']
    331341
    332         if 'options' in dictionary:
     342        if 'options' in fieldDict:
    333343            options = []
    334             for value, label in dictionary['options'].iteritems():
     344            for value, label in fieldDict['options'].iteritems():
    335345                options.append(Option(value, label))
    336346            kwargs['options'] = options
    337347
     
    415425        """
    416426        Add a field to this form.
    417427
    418         Fields are added in order, and L{fields} is a dictionary of the
     428        Fields are added in order, and C{fields} is a dictionary of the
    419429        named fields, that is kept in sync only if this method is used for
    420430        adding new fields. Multiple fields with the same name are disallowed.
    421431        """
     
    428438        self.fieldList.append(field)
    429439
    430440
     441    def removeField(self, field):
     442        """
     443        Remove a field from this form.
     444        """
     445        self.fieldList.remove(field)
     446
     447        if field.var is not None:
     448            del self.fields[field.var]
     449
     450
     451    def makeFields(self, values, fieldDefs=None, filterUnknown=True):
     452        """
     453        Create fields from values and add them to this form.
     454
     455        This creates fields from a mapping of name to value(s) and adds them to
     456        this form. It is typically used for generating outgoing forms.
     457
     458        If C{fieldDefs} is not C{None}, this is used to fill in
     459        additional properties of fields, like the field types, labels and
     460        possible options.
     461
     462        If C{filterUnknown} is C{True} and C{fieldDefs} is not C{None}, fields
     463        will only be created from C{values} with a corresponding entry in
     464        C{fieldDefs}.
     465
     466        If the field type is unknown, the field type is derived from the field
     467        value. For single values, if the value is a C{bool}, C{'boolean'} s
     468        used, for L{JID} C{'jid-single'}, and C{'text-single'} otherwise. For
     469        multiple values, if the first item is a L{JID} C{'jid-multi'} is used.
     470        Otherwise C{'list-multi'}.
     471
     472        Note that the values are used as given, no type coersion or checking
     473        is performed. Use L{typeCheck} to check types and coerce values of
     474        fields added to a form.
     475
     476        @param values: Values to create fields from.
     477        @type values: C{dict}
     478
     479        @param fieldDefs: Field definitions as a dictionary. See
     480            L{wokkel.iwokkel.IPubSubService.getConfigurationOptions}
     481        @type fieldDefs: C{dict}
     482
     483        @param filterUnknown: If C{True}, ignore fields that are not in
     484            C{fieldDefs}.
     485        @type filterUnknown: C{bool}
     486        """
     487        for name, value in values.iteritems():
     488            fieldDict = {'var': name}
     489
     490            if fieldDefs is not None:
     491                if name in fieldDefs:
     492                    fieldDict.update(fieldDefs[name])
     493                elif filterUnknown:
     494                    continue
     495
     496            if isinstance(value, list):
     497                fieldDict['values'] = value
     498                if 'type' not in fieldDict:
     499                    if value and hasattr(value[0], 'full'):
     500                        fieldDict['type'] = 'jid-multi'
     501                    else:
     502                        fieldDict['type'] = 'list-multi'
     503            else:
     504                fieldDict['value'] = value
     505                if 'type' not in fieldDict:
     506                    if hasattr(value, 'full'):
     507                        fieldDict['type'] = 'jid-single'
     508                    elif isinstance(value, bool):
     509                        fieldDict['type'] = 'boolean'
     510
     511            self.addField(Field.fromDict(fieldDict))
     512
     513
    431514    def toElement(self):
     515        """
     516        Return the DOM representation of this Form.
     517
     518        @rtype: L{domish.Element}
     519        """
    432520        form = domish.Element((NS_X_DATA, 'x'))
    433521        form['type'] = self.formType
    434522
     
    516604            values[name] = value
    517605
    518606        return values
     607
     608
     609    def typeCheck(self, fieldDefs=None, filterUnknown=False):
     610        """
     611        Check values of fields according to the field definition.
     612
     613        This method walks all named fields to check their values against their
     614        type, and is typically used for forms received from other entities. The
     615        field definition in C{fieldDefs} is used to check the field type.
     616
     617        If C{filterUnknown} is C{True}, fields that are not present in
     618        C{fieldDefs} are removed from the form.
     619
     620        If the field type is C{None} (when not set by the sending entity),
     621        the type from the field definitition is used, or C{'text-single'} if
     622        that is not set.
     623
     624        If C{fieldDefs} is None, an empty dictionary is assumed. This is
     625        useful for coercing boolean and JID values on forms with type
     626        C{'form'}.
     627
     628        @param fieldDefs: Field definitions as a dictionary. See
     629            L{wokkel.iwokkel.IPubSubService.getConfigurationOptions}
     630        @type fieldDefs: C{dict}
     631
     632        @param filterUnknown: If C{True}, remove fields that are not in
     633            C{fieldDefs}.
     634        @type filterUnknown: C{bool}
     635        """
     636
     637        if fieldDefs is None:
     638            fieldDefs = {}
     639
     640        filtered = []
     641
     642        for name, field in self.fields.iteritems():
     643            if name in fieldDefs:
     644                fieldDef = fieldDefs[name]
     645                if 'type' not in fieldDef:
     646                    fieldDef['type'] = 'text-single'
     647
     648                if field.fieldType is None:
     649                    field.fieldType = fieldDef['type']
     650                elif field.fieldType != fieldDef['type']:
     651                    raise TypeError("Field type for %r is %r, expected %r" %
     652                                    (name,
     653                                     field.fieldType,
     654                                     fieldDef['type']))
     655                else:
     656                    # Field type is correct
     657                    pass
     658                field.typeCheck()
     659            elif filterUnknown:
     660                filtered.append(field)
     661            elif field.fieldType is not None:
     662                field.typeCheck()
     663            else:
     664                # Unknown field without type, no checking, no filtering
     665                pass
     666
     667        for field in filtered:
     668            self.removeField(field)
  • wokkel/pubsub.py

    diff -r ba58db23e8b9 wokkel/pubsub.py
    a b  
    11# -*- test-case-name: wokkel.test.test_pubsub -*-
    22#
    3 # Copyright (c) 2003-2009 Ralph Meijer
     3# Copyright (c) 2003-2010 Ralph Meijer
    44# See LICENSE for details.
    55
    66"""
     
    10121012            return None
    10131013
    10141014
    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 
    10311015    def _formFromConfiguration(self, resource, values):
    1032         options = resource.getConfigurationOptions()
    1033         fields = self._makeFields(options, values)
     1016        fieldDefs = resource.getConfigurationOptions()
    10341017        form = data_form.Form(formType="form",
    1035                               formNamespace=NS_PUBSUB_NODE_CONFIG,
    1036                               fields=fields)
    1037 
     1018                              formNamespace=NS_PUBSUB_NODE_CONFIG)
     1019        form.makeFields(values, fieldDefs)
    10381020        return form
    10391021
    10401022
  • wokkel/test/test_data_form.py

    diff -r ba58db23e8b9 wokkel/test/test_data_form.py
    a b  
    787787        self.assertRaises(data_form.Error, form.addField, field2)
    788788
    789789
     790    def test_removeField(self):
     791        """
     792        A removed field should not occur in fieldList.
     793        """
     794        form = data_form.Form('result')
     795        field = data_form.Field('fixed', value='Section 1')
     796        form.addField(field)
     797        form.removeField(field)
     798        self.assertNotIn(field, form.fieldList)
     799
     800
     801    def test_removeFieldNamed(self):
     802        """
     803        A removed named field should not occur in fields.
     804        """
     805        form = data_form.Form('result')
     806        field = data_form.Field(var='test', value='test1')
     807        form.addField(field)
     808        form.removeField(field)
     809        self.assertNotIn('test', form.fields)
     810
     811
     812    def test_makeField(self):
     813        """
     814        Fields can be created from a dict of values and a dict of field defs.
     815        """
     816        fieldDefs = {
     817                "pubsub#persist_items":
     818                    {"type": "boolean",
     819                     "label": "Persist items to storage"},
     820                "pubsub#deliver_payloads":
     821                    {"type": "boolean",
     822                     "label": "Deliver payloads with event notifications"},
     823                "pubsub#creator":
     824                    {"type": "jid-single",
     825                     "label": "The JID of the node creator"},
     826                "pubsub#description":
     827                    {"type": "text-single",
     828                     "label": "A description of the node"},
     829                "pubsub#owner":
     830                    {"type": "jid-single",
     831                     "label": "Owner of the node"},
     832                }
     833        values = {'pubsub#deliver_payloads': '0',
     834                  'pubsub#persist_items': True,
     835                  'pubsub#description': 'a great node',
     836                  'pubsub#owner': jid.JID('user@example.org'),
     837                  'x-myfield': ['a', 'b']}
     838
     839        form = data_form.Form('submit')
     840        form.makeFields(values, fieldDefs)
     841
     842        # Check that the expected fields have been created
     843        self.assertIn('pubsub#deliver_payloads', form.fields)
     844        self.assertIn('pubsub#persist_items', form.fields)
     845        self.assertIn('pubsub#description', form.fields)
     846        self.assertIn('pubsub#owner', form.fields)
     847
     848        # This field is not created because there is no value for it.
     849        self.assertNotIn('pubsub#creator', form.fields)
     850
     851        # This field is not created because it does not appear in fieldDefs
     852        self.assertNotIn('x-myfield', form.fields)
     853
     854        # Check properties the created fields
     855        self.assertEqual('boolean',
     856                         form.fields['pubsub#deliver_payloads'].fieldType)
     857        self.assertEqual('0',
     858                         form.fields['pubsub#deliver_payloads'].value)
     859        self.assertEqual('Deliver payloads with event notifications',
     860                         form.fields['pubsub#deliver_payloads'].label)
     861        self.assertEqual(True,
     862                         form.fields['pubsub#persist_items'].value)
     863
     864
     865
     866
     867    def test_makeFieldsUnknownTypeJID(self):
     868        """
     869        Without type, a single JID value sets field type jid-single.
     870        """
     871        values = {'pubsub#creator': jid.JID('user@example.org')}
     872        form = data_form.Form('result')
     873        form.makeFields(values)
     874        field = form.fields['pubsub#creator']
     875        self.assertEqual('jid-single', field.fieldType)
     876
     877
     878    def test_makeFieldsUnknownTypeJIDMulti(self):
     879        """
     880        Without type, multiple JID values sets field type jid-multi.
     881        """
     882        values = {'pubsub#contact': [jid.JID('user@example.org'),
     883                                     jid.JID('other@example.org')]}
     884        form = data_form.Form('result')
     885        form.makeFields(values)
     886        field = form.fields['pubsub#contact']
     887        self.assertEqual('jid-multi', field.fieldType)
     888
     889
     890    def test_makeFieldsUnknownTypeBoolean(self):
     891        """
     892        Without type, a boolean value sets field type boolean.
     893        """
     894        values = {'pubsub#persist_items': True}
     895        form = data_form.Form('result')
     896        form.makeFields(values)
     897        field = form.fields['pubsub#persist_items']
     898        self.assertEqual('boolean', field.fieldType)
     899
     900
     901    def test_makeFieldsUnknownTypeListMulti(self):
     902        """
     903        Without type, multiple values sets field type list-multi.
     904        """
     905        values = {'pubsub#show-values': ['chat', 'online', 'away']}
     906        form = data_form.Form('result')
     907        form.makeFields(values)
     908        field = form.fields['pubsub#show-values']
     909        self.assertEqual('list-multi', field.fieldType)
     910
     911
    790912    def test_getValues(self):
    791913        """
    792914        Each named field is represented in the values, keyed by name.
     
    834956        form = data_form.Form('submit', fields=fields)
    835957        values = form.getValues()
    836958        self.assertEqual({'features': 'news'}, values)
     959
     960
     961    def test_typeCheckKnownFieldChecked(self):
     962        """
     963        Known fields are type checked.
     964        """
     965        checked = []
     966        fieldDefs = {"pubsub#description":
     967                        {"type": "text-single",
     968                         "label": "A description of the node"}}
     969        form = data_form.Form('submit')
     970        form.addField(data_form.Field(var='pubsub#description',
     971                                      value='a node'))
     972        field = form.fields['pubsub#description']
     973        field.typeCheck = lambda : checked.append(None)
     974        form.typeCheck(fieldDefs)
     975
     976        self.assertEqual([None], checked)
     977
     978
     979    def test_typeCheckKnownFieldNoType(self):
     980        """
     981        Known fields without a type get the type of the field definition.
     982        """
     983        checked = []
     984        fieldDefs = {"pubsub#description":
     985                        {"type": "text-single",
     986                         "label": "A description of the node"}}
     987        form = data_form.Form('submit')
     988        form.addField(data_form.Field(None, var='pubsub#description',
     989                                            value='a node'))
     990        field = form.fields['pubsub#description']
     991        field.typeCheck = lambda : checked.append(None)
     992        form.typeCheck(fieldDefs)
     993
     994        self.assertEqual('text-single', field.fieldType)
     995        self.assertEqual([None], checked)
     996
     997
     998    def test_typeCheckWrongFieldType(self):
     999        """
     1000        A field should have the same type as the field definition.
     1001        """
     1002        checked = []
     1003        fieldDefs = {"pubsub#description":
     1004                        {"type": "text-single",
     1005                         "label": "A description of the node"}}
     1006        form = data_form.Form('submit')
     1007        form.addField(data_form.Field('list-single', var='pubsub#description',
     1008                                                     value='a node'))
     1009        field = form.fields['pubsub#description']
     1010        field.typeCheck = lambda : checked.append(None)
     1011
     1012        self.assertRaises(TypeError, form.typeCheck, fieldDefs)
     1013        self.assertEqual([], checked)
     1014
     1015
     1016    def test_typeCheckDefaultTextSingle(self):
     1017        """
     1018        If a field definition has no type, use text-single.
     1019        """
     1020        checked = []
     1021        fieldDefs = {"pubsub#description":
     1022                        {"label": "A description of the node"}}
     1023        form = data_form.Form('submit')
     1024        form.addField(data_form.Field('text-single', var='pubsub#description',
     1025                                                     value='a node'))
     1026        field = form.fields['pubsub#description']
     1027        field.typeCheck = lambda : checked.append(None)
     1028        form.typeCheck(fieldDefs)
     1029
     1030        self.assertEqual([None], checked)
     1031
     1032
     1033    def test_typeCheckUnknown(self):
     1034        """
     1035        Unknown fields are checked, not removed if filterUnknown False.
     1036        """
     1037        checked = []
     1038        fieldDefs = {}
     1039        form = data_form.Form('submit')
     1040        form.addField(data_form.Field('list-single', var='pubsub#description',
     1041                                                     value='a node'))
     1042        field = form.fields['pubsub#description']
     1043        field.typeCheck = lambda : checked.append(None)
     1044        form.typeCheck(fieldDefs, filterUnknown=False)
     1045
     1046        self.assertIn('pubsub#description', form.fields)
     1047        self.assertEqual([None], checked)
     1048
     1049
     1050    def test_typeCheckUnknownNoType(self):
     1051        """
     1052        Unknown fields without type are not checked.
     1053        """
     1054        checked = []
     1055        fieldDefs = {}
     1056        form = data_form.Form('submit')
     1057        form.addField(data_form.Field(None, var='pubsub#description',
     1058                                            value='a node'))
     1059        field = form.fields['pubsub#description']
     1060        field.typeCheck = lambda : checked.append(None)
     1061        form.typeCheck(fieldDefs, filterUnknown=False)
     1062
     1063        self.assertIn('pubsub#description', form.fields)
     1064        self.assertEqual([], checked)
     1065
     1066
     1067    def test_typeCheckUnknownRemoved(self):
     1068        """
     1069        Unknown fields are not checked, and removed if filterUnknown True.
     1070        """
     1071        checked = []
     1072        fieldDefs = {}
     1073        form = data_form.Form('submit')
     1074        form.addField(data_form.Field('list-single', var='pubsub#description',
     1075                                                     value='a node'))
     1076        field = form.fields['pubsub#description']
     1077        field.typeCheck = lambda : checked.append(None)
     1078        form.typeCheck(fieldDefs, filterUnknown=True)
     1079
     1080        self.assertNotIn('pubsub#description', form.fields)
     1081        self.assertEqual([], checked)
     1082
     1083
     1084    def test_typeCheckNoFieldDefs(self):
     1085        """
     1086        If there are no field defs, an empty dictionary is assumed.
     1087        """
     1088        checked = []
     1089        form = data_form.Form('submit')
     1090        form.addField(data_form.Field('list-single', var='pubsub#description',
     1091                                                     value='a node'))
     1092        field = form.fields['pubsub#description']
     1093        field.typeCheck = lambda : checked.append(None)
     1094        form.typeCheck()
     1095
     1096        self.assertIn('pubsub#description', form.fields)
     1097        self.assertEqual([None], checked)
Note: See TracBrowser for help on using the repository browser.