comparison src/plugins/plugin_misc_tickets.py @ 2471:544c4d2fec45

plugins schema, merge_requests, tickets*: factorisation Dode common in plugins using schema have been factorised in pubsub schema plugin, and filters users in tickets handling have been renamed in a more generic way and put there too. "reporter*" fields in tickets have been renamed to "author*" as it is a more generic term which can be used elsewhere. The use of new utils.partial function make easy the creation of simple plugins using schema.
author Goffi <goffi@goffi.org>
date Fri, 12 Jan 2018 15:58:54 +0100
parents 9e692f09f367
children 3f0a3a0ed290
comparison
equal deleted inserted replaced
2470:8084066ac95b 2471:544c4d2fec45
17 # You should have received a copy of the GNU Affero General Public License 17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 from sat.core.i18n import _ 20 from sat.core.i18n import _
21 from sat.core.constants import Const as C 21 from sat.core.constants import Const as C
22 from sat.core import exceptions
23 from twisted.words.protocols.jabber import jid
24 from twisted.internet import defer 22 from twisted.internet import defer
25 from wokkel import generic 23 from sat.tools.common import uri
26 from sat.tools import utils 24 from sat.tools import utils
27 from sat.tools.common import uri 25 import shortuuid
28 from sat.core.log import getLogger 26 from sat.core.log import getLogger
29 import shortuuid
30 from wokkel import data_form
31 log = getLogger(__name__) 27 log = getLogger(__name__)
32 28
33 NS_TICKETS = 'org.salut-a-toi.tickets:0' 29 NS_TICKETS = 'org.salut-a-toi.tickets:0'
34 30
35 PLUGIN_INFO = { 31 PLUGIN_INFO = {
51 self.host = host 47 self.host = host
52 host.registerNamespace('tickets', NS_TICKETS) 48 host.registerNamespace('tickets', NS_TICKETS)
53 self._p = self.host.plugins["XEP-0060"] 49 self._p = self.host.plugins["XEP-0060"]
54 self._s = self.host.plugins["PUBSUB_SCHEMA"] 50 self._s = self.host.plugins["PUBSUB_SCHEMA"]
55 self._m = self.host.plugins["XEP-0277"] 51 self._m = self.host.plugins["XEP-0277"]
56 self._i = self.host.plugins["IDENTITY"]
57 host.bridge.addMethod("ticketsGet", ".plugin", 52 host.bridge.addMethod("ticketsGet", ".plugin",
58 in_sign='ssiassa{ss}s', out_sign='(asa{ss})', 53 in_sign='ssiassa{ss}s', out_sign='(asa{ss})',
59 method=self._get, 54 method=utils.partial(
55 self._s._get,
56 default_node=NS_TICKETS,
57 form_ns=NS_TICKETS,
58 filters = {u'author': self._s.valueOrPublisherFilter,
59 u'labels': self._s.textbox2ListFilter,
60 u'created': self._s.dateFilter,
61 u'updated': self._s.dateFilter,
62 }),
63
60 async=True 64 async=True
61 ) 65 )
62 host.bridge.addMethod("ticketSet", ".plugin", 66 host.bridge.addMethod("ticketSet", ".plugin",
63 in_sign='ssa{sas}ssa{ss}s', out_sign='s', 67 in_sign='ssa{sas}ssa{ss}s', out_sign='s',
64 method=self._set, 68 method=self._set,
65 async=True) 69 async=True)
66 host.bridge.addMethod("ticketsSchemaGet", ".plugin", 70 host.bridge.addMethod("ticketsSchemaGet", ".plugin",
67 in_sign='sss', out_sign='s', 71 in_sign='sss', out_sign='s',
68 method=self._getSchema, 72 method=utils.partial(self._s._getUISchema, default_node=NS_TICKETS),
69 async=True) 73 async=True)
70 74
71 def _reporterFilter(self, client, form_xmlui, widget_type, args, kwargs):
72 if not args[0]:
73 # if reporter is not filled, we use user part of publisher
74 # (if we have it)
75 try:
76 publisher = jid.JID(form_xmlui.named_widgets['publisher'].value)
77 except (KeyError, RuntimeError):
78 pass
79 else:
80 args[0] = publisher.user.capitalize()
81 return widget_type, args, kwargs
82
83 def _labelsFilter(self, form_xmlui, widget_type, args, kwargs):
84 if widget_type != u'textbox':
85 return widget_type, args, kwargs
86 widget_type = u'list'
87 options = [o for o in args.pop(0).split(u'\n') if o]
88 kwargs = {'options': options,
89 'name': kwargs.get('name'),
90 'styles': (u'noselect', u'extensible', u'reducible')}
91 return widget_type, args, kwargs
92
93 def _dateFilter(self, form_xmlui, widget_type, args, kwargs):
94 if widget_type != u'string' or not args[0]:
95 return widget_type, args, kwargs
96 # we convert XMPP date to timestamp
97 try:
98 args[0] = unicode(utils.date_parse(args[0]))
99 except Exception as e:
100 log.warning(_(u"Can't parse date field: {msg}").format(msg=e))
101 return widget_type, args, kwargs
102
103 def _get(self, service='', node='', max_items=10, item_ids=None, sub_id=None, extra_dict=None, profile_key=C.PROF_KEY_NONE):
104 client = self.host.getClient(profile_key)
105 service = jid.JID(service) if service else None
106 max_items = None if max_items == C.NO_LIMIT else max_items
107 extra = self._p.parseExtra(extra_dict)
108 d = self.get(client, service, node or None, max_items, item_ids, sub_id or None, extra.rsm_request, extra.extra)
109 d.addCallback(self._p.serItemsData)
110 return d
111
112 @defer.inlineCallbacks
113 def get(self, client, service=None, node=None, max_items=None, item_ids=None, sub_id=None, rsm_request=None, extra=None, form_ns=NS_TICKETS):
114 """Retrieve tickets and convert them to XMLUI
115
116 @param service(None, jid.JID): Pubsub service to use
117 @param node(unicode, None): PubSub node to use
118 if None, default ticket node will be used
119 other parameters as the same as for [XEP_0060.getItems]
120 @return (tuple(list[unicode], dict[unicode, object])):
121 - XMLUI of the tickets
122 - metadata dict
123 """
124 if not node:
125 node = NS_TICKETS
126 filters = {u'reporter': lambda *args: self._reporterFilter(client, *args),
127 u'labels': self._labelsFilter,
128 u'created': self._dateFilter,
129 u'updated': self._dateFilter,
130 }
131 tickets, metadata = yield self._s.getDataFormItems(
132 client,
133 form_ns,
134 service,
135 node,
136 max_items = max_items,
137 item_ids = item_ids,
138 sub_id = sub_id,
139 rsm_request = rsm_request,
140 extra = extra,
141 filters = filters,
142 )
143
144 defer.returnValue((tickets, metadata))
145
146 def _set(self, service, node, values, schema=None, item_id=None, extra=None, profile_key=C.PROF_KEY_NONE): 75 def _set(self, service, node, values, schema=None, item_id=None, extra=None, profile_key=C.PROF_KEY_NONE):
147 client = self.host.getClient(profile_key) 76 client, service, schema, extra = self._s.prepareBridgeSet(service, node, schema, item_id, extra)
148 service = None if not service else jid.JID(service)
149 if schema:
150 schema = generic.parseXml(schema.encode('utf-8'))
151 else:
152 schema = None
153 if extra and u'update' in extra:
154 extra[u'update'] = C.bool(extra[u'update'])
155 d = self.set(client, service, node or None, values, schema, item_id or None, extra, deserialise=True) 77 d = self.set(client, service, node or None, values, schema, item_id or None, extra, deserialise=True)
156 d.addCallback(lambda ret: ret or u'') 78 d.addCallback(lambda ret: ret or u'')
157 return d 79 return d
158 80
159 @defer.inlineCallbacks 81 @defer.inlineCallbacks
173 other arguments are same as for [self._s.sendDataFormItem] 95 other arguments are same as for [self._s.sendDataFormItem]
174 @return (unicode): id of the created item 96 @return (unicode): id of the created item
175 """ 97 """
176 if not node: 98 if not node:
177 node = NS_TICKETS 99 node = NS_TICKETS
178 now = utils.xmpp_date()
179 if not item_id: 100 if not item_id:
180 values['created'] = now
181 comments_service = yield self._m.getCommentsService(client, service) 101 comments_service = yield self._m.getCommentsService(client, service)
182 102
183 # we need to use uuid for comments node, because we don't know item id in advance 103 # we need to use uuid for comments node, because we don't know item id in advance
184 # (we don't want to set it ourselves to let the server choose, so we can have 104 # (we don't want to set it ourselves to let the server choose, so we can have
185 # a nicer id if serial ids is activated) 105 # a nicer id if serial ids is activated)
191 self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, 111 self._p.OPT_SEND_ITEM_SUBSCRIBE: 1,
192 self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN, 112 self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN,
193 } 113 }
194 yield self._p.createNode(client, comments_service, comments_node, options) 114 yield self._p.createNode(client, comments_service, comments_node, options)
195 values['comments_uri'] = uri.buildXMPPUri(u'pubsub', subtype='microblog', path=comments_service.full(), node=comments_node) 115 values['comments_uri'] = uri.buildXMPPUri(u'pubsub', subtype='microblog', path=comments_service.full(), node=comments_node)
196 elif extra.get(u'update', False): 116 item_id = yield self._s.set(client, service, node, values, schema, item_id, extra, deserialise, form_ns)
197 if item_id is None:
198 raise exceptions.DataError(_(u'if extra["update"] is set, item_id must be set too'))
199 try:
200 # we get previous item
201 items_data = yield self._p.getItems(client, service, node, item_ids=[item_id])
202 item_elt = items_data[0][0]
203 except Exception as e:
204 log.warning(_(u"Can't get previous item, update ignored: {reason}").format(
205 reason = e))
206 else:
207 # and parse it
208 form = data_form.findForm(item_elt, form_ns)
209 if form is None:
210 log.warning(_(u"Can't parse previous item, update ignored: data form not found").format(
211 reason = e))
212 else:
213 for name, field in form.fields.iteritems():
214 if name not in values:
215 values[name] = u'\n'.join(unicode(v) for v in field.values)
216
217 values['updated'] = now
218 if not values.get('reporter'):
219 identity = yield self._i.getIdentity(client, client.jid)
220 values['reporter'] = identity['nick']
221 if not values.get('reporter_jid'):
222 values['reporter_jid'] = client.jid.full()
223 item_id = yield self._s.sendDataFormItem(client, service, node, values, schema, item_id, extra, deserialise)
224 defer.returnValue(item_id) 117 defer.returnValue(item_id)
225
226 def _getSchema(self, service, node, profile_key=C.PROF_KEY_NONE):
227 if not node:
228 node = NS_TICKETS
229 return self._s._getUISchema(service, node, profile_key)