Merge pull request #395 from rerobins/refactor_forms

XEP_0004: Data Forms use register_stanza_plugin
This commit is contained in:
Mike Taylor 2015-09-18 15:11:47 -04:00
commit 5525ef2285
7 changed files with 181 additions and 26 deletions

View File

@ -13,8 +13,9 @@ class FormField(ElementBase):
namespace = 'jabber:x:data'
name = 'field'
plugin_attrib = 'field'
plugin_multi_attrib = 'fields'
interfaces = set(('answer', 'desc', 'required', 'value',
'options', 'label', 'type', 'var'))
'label', 'type', 'var'))
sub_interfaces = set(('desc',))
plugin_tag_map = {}
plugin_attrib_map = {}
@ -165,6 +166,7 @@ class FieldOption(ElementBase):
plugin_attrib = 'option'
interfaces = set(('label', 'value'))
sub_interfaces = set(('value',))
plugin_multi_attrib = 'options'
FormField.addOption = FormField.add_option

View File

@ -9,7 +9,7 @@
import copy
import logging
from sleekxmpp.thirdparty import OrderedDict
from sleekxmpp.thirdparty import OrderedDict, OrderedSet
from sleekxmpp.xmlstream import ElementBase, ET
from sleekxmpp.plugins.xep_0004.stanza import FormField
@ -22,8 +22,7 @@ class Form(ElementBase):
namespace = 'jabber:x:data'
name = 'x'
plugin_attrib = 'form'
interfaces = set(('fields', 'instructions', 'items',
'reported', 'title', 'type', 'values'))
interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', ))
sub_interfaces = set(('title',))
form_types = set(('cancel', 'form', 'result', 'submit'))
@ -43,12 +42,12 @@ class Form(ElementBase):
@property
def field(self):
return self['fields']
return self.get_fields()
def set_type(self, ftype):
self._set_attr('type', ftype)
if ftype == 'submit':
fields = self['fields']
fields = self.get_fields()
for var in fields:
field = fields[var]
del field['type']
@ -74,7 +73,8 @@ class Form(ElementBase):
field['desc'] = desc
field['required'] = required
if options is not None:
field['options'] = options
for option in options:
field.add_option(**option)
else:
del field['type']
self.append(field)
@ -169,7 +169,7 @@ class Form(ElementBase):
def get_reported(self):
fields = OrderedDict()
xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
FormField.namespace))
FormField.namespace))
for field in xml:
field = FormField(xml=field)
fields[field['var']] = field
@ -177,7 +177,7 @@ class Form(ElementBase):
def get_values(self):
values = OrderedDict()
fields = self['fields']
fields = self.get_fields()
for var in fields:
values[var] = fields[var]['value']
return values
@ -219,17 +219,33 @@ class Form(ElementBase):
self.add_item(item)
def set_reported(self, reported):
"""
This either needs a dictionary or dictionaries or a dictionary of form fields.
:param reported:
:return:
"""
for var in reported:
field = reported[var]
field['var'] = var
self.add_reported(var, **field)
if isinstance(field, dict):
self.add_reported(**field)
else:
reported = self.xml.find('{%s}reported' % self.namespace)
if reported is None:
reported = ET.Element('{%s}reported' % self.namespace)
self.xml.append(reported)
fieldXML = ET.Element('{%s}field' % FormField.namespace)
reported.append(fieldXML)
new_field = FormField(xml=fieldXML)
new_field.values = field.values
def set_values(self, values):
fields = self['fields']
fields = self.get_fields()
for field in values:
if field not in fields:
if field not in self.get_fields():
fields[field] = self.add_field(var=field)
fields[field]['value'] = values[field]
self.get_fields()[field]['value'] = values[field]
def merge(self, other):
new = copy.copy(self)

View File

@ -10,3 +10,4 @@ except:
from sleekxmpp.thirdparty import socks
from sleekxmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso
from sleekxmpp.thirdparty.orderedset import OrderedSet

89
sleekxmpp/thirdparty/orderedset.py vendored Normal file
View File

@ -0,0 +1,89 @@
# Copyright (c) 2009 Raymond Hettinger
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
import collections
class OrderedSet(collections.MutableSet):
def __init__(self, iterable=None):
self.end = end = []
end += [None, end, end] # sentinel node for doubly linked list
self.map = {} # key --> [key, prev, next]
if iterable is not None:
self |= iterable
def __len__(self):
return len(self.map)
def __contains__(self, key):
return key in self.map
def add(self, key):
if key not in self.map:
end = self.end
curr = end[1]
curr[2] = end[1] = self.map[key] = [key, curr, end]
def discard(self, key):
if key in self.map:
key, prev, next = self.map.pop(key)
prev[2] = next
next[1] = prev
def __iter__(self):
end = self.end
curr = end[2]
while curr is not end:
yield curr[0]
curr = curr[2]
def __reversed__(self):
end = self.end
curr = end[1]
while curr is not end:
yield curr[0]
curr = curr[1]
def pop(self, last=True):
if not self:
raise KeyError('set is empty')
key = self.end[1][0] if last else self.end[2][0]
self.discard(key)
return key
def __repr__(self):
if not self:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, list(self))
def __eq__(self, other):
if isinstance(other, OrderedSet):
return len(self) == len(other) and list(self) == list(other)
return set(self) == set(other)
if __name__ == '__main__':
s = OrderedSet('abracadaba')
t = OrderedSet('simsalabim')
print(s | t)
print(s & t)
print(s - t)

View File

@ -563,7 +563,7 @@ class ElementBase(object):
.. versionadded:: 1.0-Beta1
"""
values = {}
values = OrderedDict()
values['lang'] = self['lang']
for interface in self.interfaces:
if isinstance(self[interface], JID):

View File

@ -11,8 +11,8 @@ class TestDataForms(SleekTest):
def setUp(self):
register_stanza_plugin(Message, xep_0004.Form)
register_stanza_plugin(xep_0004.Form, xep_0004.FormField)
register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption)
register_stanza_plugin(xep_0004.Form, xep_0004.FormField, iterable=True)
register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption, iterable=True)
def testMultipleInstructions(self):
"""Testing using multiple instructions elements in a data form."""
@ -68,7 +68,7 @@ class TestDataForms(SleekTest):
'value': 'cool'},
{'label': 'Urgh!',
'value': 'urgh'}]}
form['fields'] = fields
form.set_fields(fields)
self.check(msg, """
@ -141,13 +141,13 @@ class TestDataForms(SleekTest):
'value': 'cool'},
{'label': 'Urgh!',
'value': 'urgh'}]}
form['fields'] = fields
form.set_fields(fields)
form['type'] = 'submit'
form['values'] = {'f1': 'username',
form.set_values({'f1': 'username',
'f2': 'hunter2',
'f3': 'A long\nmultiline\nmessage',
'f4': 'cool'}
'f4': 'cool'})
self.check(form, """
<x xmlns="jabber:x:data" type="submit">
@ -189,7 +189,7 @@ class TestDataForms(SleekTest):
'value': 'cool'},
{'label': 'Urgh!',
'value': 'urgh'}]}
form['fields'] = fields
form.set_fields(fields)
form['type'] = 'cancel'
@ -197,5 +197,52 @@ class TestDataForms(SleekTest):
<x xmlns="jabber:x:data" type="cancel" />
""")
def testReported(self):
msg = self.Message()
form = msg['form']
form['type'] = 'result'
form.add_reported(var='f1', ftype='text-single', label='Username')
form.add_item({'f1': 'username@example.org'})
self.check(msg, """
<message>
<x xmlns="jabber:x:data" type="result">
<reported>
<field var="f1" type="text-single" label="Username" />
</reported>
<item>
<field var="f1">
<value>username@example.org</value>
</field>
</item>
</x>
</message>
""")
def testSetReported(self):
msg = self.Message()
form = msg['form']
form['type'] = 'result'
reported = {'f1': {
'var': 'f1',
'type': 'text-single',
'label': 'Username'
}}
form.set_reported(reported)
self.check(msg, """
<message>
<x xmlns="jabber:x:data" type="result">
<reported>
<field var="f1" type="text-single" label="Username" />
</reported>
</x>
</message>
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestDataForms)

View File

@ -119,7 +119,7 @@ class TestAdHocCommands(SleekTest):
def handle_command(iq, session):
def handle_form(form, session):
results.append(form['values']['foo'])
results.append(form.get_values()['foo'])
form = self.xmpp['xep_0004'].makeForm('form')
form.addField(var='foo', ftype='text-single', label='Foo')
@ -191,10 +191,10 @@ class TestAdHocCommands(SleekTest):
def handle_command(iq, session):
def handle_step2(form, session):
results.append(form['values']['bar'])
results.append(form.get_values()['bar'])
def handle_step1(form, session):
results.append(form['values']['foo'])
results.append(form.get_values()['foo'])
form = self.xmpp['xep_0004'].makeForm('form')
form.addField(var='bar', ftype='text-single', label='Bar')
@ -426,7 +426,7 @@ class TestAdHocCommands(SleekTest):
def handle_form(forms, session):
for form in forms:
results.append(form['values']['FORM_TYPE'])
results.append(form.get_values()['FORM_TYPE'])
form1 = self.xmpp['xep_0004'].makeForm('form')
form1.addField(var='FORM_TYPE', ftype='hidden', value='form_1')