# HG changeset patch # User Goffi # Date 1506853281 -7200 # Node ID 41c7717b52cddf132ca874f84432a84bbd2565f9 # Parent 3acbaf5c29f529314cab9c5837bb86a8adda318a plugin PubSub Schema: schema helper methods: 3 new methods are introduced by this patch: - psSchemaUIGet to get XMLUI from node schema - psItemsFormGet to get items as XMLUI when we know they are data forms - psItemFormSend to set item from values when we know it's a node with schema. For this method, schema can be specified, and if it is not it will be retrieved from node (which imply an additional XMPP request) diff -r 3acbaf5c29f5 -r 41c7717b52cd src/plugins/plugin_exp_pubsub_schema.py --- a/src/plugins/plugin_exp_pubsub_schema.py Sun Sep 24 16:39:36 2017 +0200 +++ b/src/plugins/plugin_exp_pubsub_schema.py Sun Oct 01 12:21:21 2017 +0200 @@ -20,8 +20,10 @@ from sat.core.i18n import _ from sat.core import exceptions from sat.core.constants import Const as C +from sat.tools import xml_tools from twisted.words.protocols.jabber import jid from twisted.words.protocols.jabber.xmlstream import XMPPHandler +from twisted.internet import defer from sat.core.log import getLogger log = getLogger(__name__) from wokkel import disco, iwokkel @@ -49,6 +51,7 @@ def __init__(self, host): log.info(_(u"PubSub Schema initialization")) self.host = host + self._p = self.host.plugins["XEP-0060"] host.bridge.addMethod("psSchemaGet", ".plugin", in_sign='sss', out_sign='s', method=self._getSchema, @@ -59,6 +62,19 @@ method=self._setSchema, async=True ) + host.bridge.addMethod("psSchemaUIGet", ".plugin", + in_sign='sss', out_sign='s', + method=self._getUISchema, + async=True + ) + host.bridge.addMethod("psItemsFormGet", ".plugin", + in_sign='ssssiassa{ss}s', out_sign='(asa{ss})', + method=self._getDataFormItems, + async=True) + host.bridge.addMethod("psItemFormSend", ".plugin", + in_sign='ssa{sas}ssa{ss}s', out_sign='s', + method=self._sendDataFormItem, + async=True) def getHandler(self, client): return SchemaHandler() @@ -108,6 +124,47 @@ d.addCallback(self._getSchemaCb) return d + @defer.inlineCallbacks + def getSchemaForm(self, client, service, nodeIdentifier, schema=None, form_type='form'): + """get data form from node's schema + + @param service(None, jid.JID): PubSub service + @param nodeIdentifier(unicode): node + @param schema(domish.Element, None): node schema + if None, it will be retrieved from node (imply one additional XMPP request) + @return(data_form.Form): data form + """ + if schema is None: + log.debug(_(u"unspecified schema, we need to request it")) + schema = yield self.getSchema(client, service, nodeIdentifier) + if schema is None: + raise exceptions.DataError(_(u"no schema specified, and this node has no schema either, we can't construct the data form")) + + try: + form = data_form.Form.fromElement(schema) + except data_form.Error as e: + raise exceptions.DataError(_(u"Invalid Schema: {msg}").format( + msg = e)) + form.formType = form_type + defer.returnValue(form) + + def schema2XMLUI(self, schema_elt): + form = data_form.Form.fromElement(schema_elt) + xmlui = xml_tools.dataForm2XMLUI(form, '') + return xmlui + + def _getUISchema(self, service, nodeIdentifier, profile_key=C.PROF_KEY_NONE): + client = self.host.getClient(profile_key) + service = None if not service else jid.JID(service) + d = self.getUISchema(client, service, nodeIdentifier) + d.addCallback(lambda xmlui: xmlui.toXml()) + return d + + def getUISchema(self, client, service, nodeIdentifier): + d = self.getSchema(client, service, nodeIdentifier) + d.addCallback(self.schema2XMLUI) + return d + def _setSchema(self, service, nodeIdentifier, schema, profile_key=C.PROF_KEY_NONE): client = self.host.getClient(profile_key) service = None if not service else jid.JID(service) @@ -130,6 +187,87 @@ schema_elt.addChild(schema) return iq_elt.send() + def _getDataFormItems(self, form_ns='', service='', node='', schema='', max_items=10, item_ids=None, sub_id=None, extra_dict=None, profile_key=C.PROF_KEY_NONE): + client = self.host.getClient(profile_key) + service = jid.JID(service) if service else None + if schema: + schema = generic.parseXml(schema.encode('utf-8')) + else: + schema = None + max_items = None if max_items == C.NO_LIMIT else max_items + extra = self._p.parseExtra(extra_dict) + d = self.getDataFormItems(client, form_ns or None, service, node or None, schema, max_items or None, item_ids, sub_id or None, extra.rsm_request, extra.extra) + d.addCallback(self._p.serItemsData) + return d + + @defer.inlineCallbacks + def getDataFormItems(self, client, form_ns, service, nodeIdentifier, schema=None, max_items=None, item_ids=None, sub_id=None, rsm_request=None, extra=None): + """Get items known as being data forms, and convert them to XMLUI + + @param form_ns (unicode, None): namespace of the form + None to accept everything, even if form has no namespace + @param schema(domish.Element, None): schema of the node if known + if None, it will be retrieved from node + other parameters as the same as for [getItems] + @return (list[unicode]): XMLUI of the forms + if an item is invalid (not corresponding to form_ns or not a data_form) + it will be skipped + """ + # we need the initial form to get options of fields when suitable + schema_form = yield self.getSchemaForm(client, service, nodeIdentifier, schema, form_type='result') + items_data = yield self._p.getItems(client, service, nodeIdentifier, max_items, item_ids, sub_id, rsm_request, extra) + items, metadata = items_data + items_xmlui = [] + for item_elt in items: + for x_elt in item_elt.elements((data_form.NS_X_DATA, u'x')): + form = data_form.Form.fromElement(x_elt) + if form_ns and form.formNamespace != form_ns: + continue + xmlui = xml_tools.dataFormResult2XMLUI(form, schema_form) + xmlui.addLabel('id') + xmlui.addText(item_elt['id'], name='_id') + items_xmlui.append(xmlui) + break + defer.returnValue((items_xmlui, metadata)) + + + def _sendDataFormItem(self, service, nodeIdentifier, values, schema=None, item_id=None, extra=None, profile_key=C.PROF_KEY_NONE): + client = self.host.getClient(profile_key) + service = None if not service else jid.JID(service) + if schema: + schema = generic.parseXml(schema.encode('utf-8')) + else: + schema = None + d = self.sendDataFormItem(client, service, nodeIdentifier, values, schema, item_id or None, extra) + d.addCallback(lambda ret: ret or u'') + return d + + @defer.inlineCallbacks + def sendDataFormItem(self, client, service, nodeIdentifier, values, schema=None, item_id=None, extra=None): + """Publish an item as a dataform when we know that there is a schema + + @param values(dict[unicode, list[unicode]]): values set for the form + @param schema(unicode, None): data schema + None to retrieve data schema from node (need to do a additional XMPP call) + Schema is need to construct data form to publish + other parameters as the same as for [self._p.sendItem] + """ + form = yield self.getSchemaForm(client, service, nodeIdentifier, schema, form_type='submit') + + for name, values_list in values.iteritems(): + try: + field = form.fields[name] + except KeyError: + log.warning(_(u"field {name} doesn't exist, ignoring it").format(name=name)) + continue + if field.fieldType == 'boolean': + values_list = [C.bool(v) for v in values_list] + elif 'jid' in field.fieldType: + values_list = [jid.JID(v) for v in values_list] + field.values = values_list + + yield self._p.sendItem(client, service, nodeIdentifier, form.toElement(), item_id, extra) + class SchemaHandler(XMPPHandler): implements(iwokkel.IDisco)