source: ralphm-patches/pubsub-forms.patch @ 31:76642b424906

Last change on this file since 31:76642b424906 was 31:76642b424906, checked in by Ralph Meijer <ralphm@…>, 10 years ago

Don't render a type attribute for a field if the fieldType is None.

File size: 22.0 KB
  • wokkel/data_form.py

    Move makeFields to wokkel.data_form, add form type checking.
    
    diff -r c85ce41a75bc 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', None) 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.
     
    244246
    245247        field = domish.Element((NS_X_DATA, 'field'))
    246248
    247         if asForm or self.fieldType != 'text-single':
     249        if self.fieldType:
    248250            field['type'] = self.fieldType
    249251
    250252        if self.var is not None:
     
    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 C{None}. When the form
     467        is rendered using L{toElement}, these fields will have no C{'type'}
     468        attribute, and it is up to the receiving party to interpret the values
     469        properly (e.g. by knowing about the FORM_TYPE in L{formNamespace} and
     470        the field name).
     471
     472        @param values: Values to create fields from.
     473        @type values: C{dict}
     474
     475        @param fieldDefs: Field definitions as a dictionary. See
     476            L{wokkel.iwokkel.IPubSubService.getConfigurationOptions}
     477        @type fieldDefs: C{dict}
     478
     479        @param filterUnknown: If C{True}, ignore fields that are not in
     480            C{fieldDefs}.
     481        @type filterUnknown: C{bool}
     482        """
     483        for name, value in values.iteritems():
     484            fieldDict = {'var': name,
     485                         'type': None}
     486
     487            if fieldDefs is not None:
     488                if name in fieldDefs:
     489                    fieldDict.update(fieldDefs[name])
     490                elif filterUnknown:
     491                    continue
     492
     493            if isinstance(value, list):
     494                fieldDict['values'] = value
     495            else:
     496                fieldDict['value'] = value
     497
     498            self.addField(Field.fromDict(fieldDict))
     499
     500
    431501    def toElement(self):
     502        """
     503        Return the DOM representation of this Form.
     504
     505        @rtype: L{domish.Element}
     506        """
    432507        form = domish.Element((NS_X_DATA, 'x'))
    433508        form['type'] = self.formType
    434509
     
    516591            values[name] = value
    517592
    518593        return values
     594
     595
     596    def typeCheck(self, fieldDefs=None, filterUnknown=False):
     597        """
     598        Check values of fields according to the field definition.
     599
     600        This method walks all named fields to check their values against their
     601        type, and is typically used for forms received from other entities. The
     602        field definition in C{fieldDefs} is used to check the field type.
     603
     604        If C{filterUnknown} is C{True}, fields that are not present in
     605        C{fieldDefs} are removed from the form.
     606
     607        If the field type is C{None} (when not set by the sending entity),
     608        the type from the field definitition is used, or C{'text-single'} if
     609        that is not set.
     610
     611        If C{fieldDefs} is None, an empty dictionary is assumed. This is
     612        useful for coercing boolean and JID values on forms with type
     613        C{'form'}.
     614
     615        @param fieldDefs: Field definitions as a dictionary. See
     616            L{wokkel.iwokkel.IPubSubService.getConfigurationOptions}
     617        @type fieldDefs: C{dict}
     618
     619        @param filterUnknown: If C{True}, remove fields that are not in
     620            C{fieldDefs}.
     621        @type filterUnknown: C{bool}
     622        """
     623
     624        if fieldDefs is None:
     625            fieldDefs = {}
     626
     627        filtered = []
     628
     629        for name, field in self.fields.iteritems():
     630            if name in fieldDefs:
     631                fieldDef = fieldDefs[name]
     632                if 'type' not in fieldDef:
     633                    fieldDef['type'] = 'text-single'
     634
     635                if field.fieldType is None:
     636                    field.fieldType = fieldDef['type']
     637                elif field.fieldType != fieldDef['type']:
     638                    raise TypeError("Field type for %r is %r, expected %r" %
     639                                    (name,
     640                                     field.fieldType,
     641                                     fieldDef['type']))
     642                else:
     643                    # Field type is correct
     644                    pass
     645                field.typeCheck()
     646            elif filterUnknown:
     647                filtered.append(field)
     648            elif field.fieldType is not None:
     649                field.typeCheck()
     650            else:
     651                # Unknown field without type, no checking, no filtering
     652                pass
     653
     654        for field in filtered:
     655            self.removeField(field)
  • wokkel/pubsub.py

    diff -r c85ce41a75bc 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 c85ce41a75bc wokkel/test/test_data_form.py
    a b  
    134134        self.assertEquals([], element.children)
    135135
    136136
    137     def test_toElementTypeNotListSingle(self):
     137    def test_toElementTypeNotTextSingle(self):
    138138        """
    139         Always render the field type, if different from list-single.
     139        Always render the field type, if different from text-single.
    140140        """
    141141        field = data_form.Field('hidden', var='test')
    142142        element = field.toElement()
     
    144144        self.assertEquals('hidden', element.getAttribute('type'))
    145145
    146146
     147    def test_toElementSingleValue(self):
     148        """
     149        A single value should yield only one value element.
     150        """
     151        field = data_form.Field('list-multi', var='test', value='test')
     152        element = field.toElement()
     153
     154        children = list(element.elements())
     155        self.assertEqual(1, len(children))
     156
     157
     158    def test_toElementMultipleValues(self):
     159        """
     160        A field with no type and multiple values should render all values.
     161        """
     162        field = data_form.Field('list-multi', var='test',
     163                                values=['test', 'test2'])
     164        element = field.toElement()
     165
     166        children = list(element.elements())
     167        self.assertEqual(2, len(children))
     168
     169
    147170    def test_toElementAsForm(self):
    148171        """
    149172        Always render the field type, if asForm is True.
     
    242265        self.assertEqual(u'true', unicode(element.value))
    243266
    244267
     268    def test_toElementNoType(self):
     269        """
     270        A field with no type should not have a type attribute.
     271        """
     272        field = data_form.Field(None, var='test', value='test')
     273        element = field.toElement()
     274        self.assertFalse(element.hasAttribute('type'))
     275
     276
     277    def test_toElementNoTypeMultipleValues(self):
     278        """
     279        A field with no type and multiple values should render all values.
     280        """
     281        field = data_form.Field(None, var='test', values=['test', 'test2'])
     282        element = field.toElement()
     283
     284        self.assertFalse(element.hasAttribute('type'))
     285        children = list(element.elements())
     286        self.assertEqual(2, len(children))
     287
     288
    245289    def test_typeCheckNoFieldName(self):
    246290        """
    247291        A field not of type fixed must have a var.
     
    787831        self.assertRaises(data_form.Error, form.addField, field2)
    788832
    789833
     834    def test_removeField(self):
     835        """
     836        A removed field should not occur in fieldList.
     837        """
     838        form = data_form.Form('result')
     839        field = data_form.Field('fixed', value='Section 1')
     840        form.addField(field)
     841        form.removeField(field)
     842        self.assertNotIn(field, form.fieldList)
     843
     844
     845    def test_removeFieldNamed(self):
     846        """
     847        A removed named field should not occur in fields.
     848        """
     849        form = data_form.Form('result')
     850        field = data_form.Field(var='test', value='test1')
     851        form.addField(field)
     852        form.removeField(field)
     853        self.assertNotIn('test', form.fields)
     854
     855
     856    def test_makeField(self):
     857        """
     858        Fields can be created from a dict of values and a dict of field defs.
     859        """
     860        fieldDefs = {
     861                "pubsub#persist_items":
     862                    {"type": "boolean",
     863                     "label": "Persist items to storage"},
     864                "pubsub#deliver_payloads":
     865                    {"type": "boolean",
     866                     "label": "Deliver payloads with event notifications"},
     867                "pubsub#creator":
     868                    {"type": "jid-single",
     869                     "label": "The JID of the node creator"},
     870                "pubsub#description":
     871                    {"type": "text-single",
     872                     "label": "A description of the node"},
     873                "pubsub#owner":
     874                    {"type": "jid-single",
     875                     "label": "Owner of the node"},
     876                }
     877        values = {'pubsub#deliver_payloads': '0',
     878                  'pubsub#persist_items': True,
     879                  'pubsub#description': 'a great node',
     880                  'pubsub#owner': jid.JID('user@example.org'),
     881                  'x-myfield': ['a', 'b']}
     882
     883        form = data_form.Form('submit')
     884        form.makeFields(values, fieldDefs)
     885
     886        # Check that the expected fields have been created
     887        self.assertIn('pubsub#deliver_payloads', form.fields)
     888        self.assertIn('pubsub#persist_items', form.fields)
     889        self.assertIn('pubsub#description', form.fields)
     890        self.assertIn('pubsub#owner', form.fields)
     891
     892        # This field is not created because there is no value for it.
     893        self.assertNotIn('pubsub#creator', form.fields)
     894
     895        # This field is not created because it does not appear in fieldDefs
     896        # and filterUnknown defaults to True
     897        self.assertNotIn('x-myfield', form.fields)
     898
     899        # Check properties the created fields
     900        self.assertEqual('boolean',
     901                         form.fields['pubsub#deliver_payloads'].fieldType)
     902        self.assertEqual('0',
     903                         form.fields['pubsub#deliver_payloads'].value)
     904        self.assertEqual('Deliver payloads with event notifications',
     905                         form.fields['pubsub#deliver_payloads'].label)
     906        self.assertEqual(True,
     907                         form.fields['pubsub#persist_items'].value)
     908
     909
     910    def test_makeFieldNotFilterUnknown(self):
     911        """
     912        Fields can be created from a dict of values and a dict of field defs.
     913        """
     914        fieldDefs = {
     915                "pubsub#persist_items":
     916                    {"type": "boolean",
     917                     "label": "Persist items to storage"},
     918                }
     919        values = {'x-myfield': ['a', 'b']}
     920
     921        form = data_form.Form('submit')
     922        form.makeFields(values, fieldDefs, filterUnknown=False)
     923
     924        field = form.fields['x-myfield']
     925        self.assertEqual(None, field.fieldType)
     926        self.assertEqual(values, form.getValues())
     927
     928
     929    def test_makeFieldsUnknownTypeJID(self):
     930        """
     931        Without type, a single JID value sets field type jid-single.
     932        """
     933        values = {'pubsub#creator': jid.JID('user@example.org')}
     934        form = data_form.Form('result')
     935        form.makeFields(values)
     936
     937        field = form.fields['pubsub#creator']
     938        self.assertEqual(None, field.fieldType)
     939        self.assertEqual(values, form.getValues())
     940
     941
     942    def test_makeFieldsUnknownTypeJIDMulti(self):
     943        """
     944        Without type, multiple JID values sets field type jid-multi.
     945        """
     946        values = {'pubsub#contact': [jid.JID('user@example.org'),
     947                                     jid.JID('other@example.org')]}
     948        form = data_form.Form('result')
     949        form.makeFields(values)
     950
     951        field = form.fields['pubsub#contact']
     952        self.assertEqual(None, field.fieldType)
     953        self.assertEqual(values, form.getValues())
     954
     955
     956    def test_makeFieldsUnknownTypeBoolean(self):
     957        """
     958        Without type, a boolean value sets field type boolean.
     959        """
     960        values = {'pubsub#persist_items': True}
     961        form = data_form.Form('result')
     962        form.makeFields(values)
     963
     964        field = form.fields['pubsub#persist_items']
     965        self.assertEqual(None, field.fieldType)
     966        self.assertEqual(values, form.getValues())
     967
     968
     969    def test_makeFieldsUnknownTypeListMulti(self):
     970        """
     971        Without type, multiple values sets field type list-multi.
     972        """
     973        values = {'pubsub#show-values': ['chat', 'online', 'away']}
     974        form = data_form.Form('result')
     975        form.makeFields(values)
     976
     977        field = form.fields['pubsub#show-values']
     978        self.assertEqual(None, field.fieldType)
     979        self.assertEqual(values, form.getValues())
     980
     981
    790982    def test_getValues(self):
    791983        """
    792984        Each named field is represented in the values, keyed by name.
     
    8341026        form = data_form.Form('submit', fields=fields)
    8351027        values = form.getValues()
    8361028        self.assertEqual({'features': 'news'}, values)
     1029
     1030
     1031    def test_typeCheckKnownFieldChecked(self):
     1032        """
     1033        Known fields are type checked.
     1034        """
     1035        checked = []
     1036        fieldDefs = {"pubsub#description":
     1037                        {"type": "text-single",
     1038                         "label": "A description of the node"}}
     1039        form = data_form.Form('submit')
     1040        form.addField(data_form.Field(var='pubsub#description',
     1041                                      value='a node'))
     1042        field = form.fields['pubsub#description']
     1043        field.typeCheck = lambda : checked.append(None)
     1044        form.typeCheck(fieldDefs)
     1045
     1046        self.assertEqual([None], checked)
     1047
     1048
     1049    def test_typeCheckKnownFieldNoType(self):
     1050        """
     1051        Known fields without a type get the type of the field definition.
     1052        """
     1053        checked = []
     1054        fieldDefs = {"pubsub#description":
     1055                        {"type": "text-single",
     1056                         "label": "A description of the node"}}
     1057        form = data_form.Form('submit')
     1058        form.addField(data_form.Field(None, var='pubsub#description',
     1059                                            value='a node'))
     1060        field = form.fields['pubsub#description']
     1061        field.typeCheck = lambda : checked.append(None)
     1062        form.typeCheck(fieldDefs)
     1063
     1064        self.assertEqual('text-single', field.fieldType)
     1065        self.assertEqual([None], checked)
     1066
     1067
     1068    def test_typeCheckWrongFieldType(self):
     1069        """
     1070        A field should have the same type as the field definition.
     1071        """
     1072        checked = []
     1073        fieldDefs = {"pubsub#description":
     1074                        {"type": "text-single",
     1075                         "label": "A description of the node"}}
     1076        form = data_form.Form('submit')
     1077        form.addField(data_form.Field('list-single', var='pubsub#description',
     1078                                                     value='a node'))
     1079        field = form.fields['pubsub#description']
     1080        field.typeCheck = lambda : checked.append(None)
     1081
     1082        self.assertRaises(TypeError, form.typeCheck, fieldDefs)
     1083        self.assertEqual([], checked)
     1084
     1085
     1086    def test_typeCheckDefaultTextSingle(self):
     1087        """
     1088        If a field definition has no type, use text-single.
     1089        """
     1090        checked = []
     1091        fieldDefs = {"pubsub#description":
     1092                        {"label": "A description of the node"}}
     1093        form = data_form.Form('submit')
     1094        form.addField(data_form.Field('text-single', var='pubsub#description',
     1095                                                     value='a node'))
     1096        field = form.fields['pubsub#description']
     1097        field.typeCheck = lambda : checked.append(None)
     1098        form.typeCheck(fieldDefs)
     1099
     1100        self.assertEqual([None], checked)
     1101
     1102
     1103    def test_typeCheckUnknown(self):
     1104        """
     1105        Unknown fields are checked, not removed if filterUnknown False.
     1106        """
     1107        checked = []
     1108        fieldDefs = {}
     1109        form = data_form.Form('submit')
     1110        form.addField(data_form.Field('list-single', var='pubsub#description',
     1111                                                     value='a node'))
     1112        field = form.fields['pubsub#description']
     1113        field.typeCheck = lambda : checked.append(None)
     1114        form.typeCheck(fieldDefs, filterUnknown=False)
     1115
     1116        self.assertIn('pubsub#description', form.fields)
     1117        self.assertEqual([None], checked)
     1118
     1119
     1120    def test_typeCheckUnknownNoType(self):
     1121        """
     1122        Unknown fields without type are not checked.
     1123        """
     1124        checked = []
     1125        fieldDefs = {}
     1126        form = data_form.Form('submit')
     1127        form.addField(data_form.Field(None, var='pubsub#description',
     1128                                            value='a node'))
     1129        field = form.fields['pubsub#description']
     1130        field.typeCheck = lambda : checked.append(None)
     1131        form.typeCheck(fieldDefs, filterUnknown=False)
     1132
     1133        self.assertIn('pubsub#description', form.fields)
     1134        self.assertEqual([], checked)
     1135
     1136
     1137    def test_typeCheckUnknownRemoved(self):
     1138        """
     1139        Unknown fields are not checked, and removed if filterUnknown True.
     1140        """
     1141        checked = []
     1142        fieldDefs = {}
     1143        form = data_form.Form('submit')
     1144        form.addField(data_form.Field('list-single', var='pubsub#description',
     1145                                                     value='a node'))
     1146        field = form.fields['pubsub#description']
     1147        field.typeCheck = lambda : checked.append(None)
     1148        form.typeCheck(fieldDefs, filterUnknown=True)
     1149
     1150        self.assertNotIn('pubsub#description', form.fields)
     1151        self.assertEqual([], checked)
     1152
     1153
     1154    def test_typeCheckNoFieldDefs(self):
     1155        """
     1156        If there are no field defs, an empty dictionary is assumed.
     1157        """
     1158        checked = []
     1159        form = data_form.Form('submit')
     1160        form.addField(data_form.Field('list-single', var='pubsub#description',
     1161                                                     value='a node'))
     1162        field = form.fields['pubsub#description']
     1163        field.typeCheck = lambda : checked.append(None)
     1164        form.typeCheck()
     1165
     1166        self.assertIn('pubsub#description', form.fields)
     1167        self.assertEqual([None], checked)
Note: See TracBrowser for help on using the repository browser.