comparison src/plugins/plugin_misc_tickets.py @ 2447:9e692f09f367

plugin tickets: handle "update" flag + various improvments: - ticketsSet renamed to ticketSet as only one ticket is set at a time - dateFilter is not crashing anymore if value can't be parsed: the value is then return unmodified - form_ns can be specified in get, so the method can be used by other plugins - new "update" flag: if set in extra, an previous ticket of the same id will be retrieved, and used as a basis for an update
author Goffi <goffi@goffi.org>
date Thu, 30 Nov 2017 20:34:41 +0100
parents 81a45e7886c9
children 544c4d2fec45
comparison
equal deleted inserted replaced
2446:bfd1e9d737c4 2447:9e692f09f367
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
22 from twisted.words.protocols.jabber import jid 23 from twisted.words.protocols.jabber import jid
23 from twisted.internet import defer 24 from twisted.internet import defer
24 from wokkel import generic 25 from wokkel import generic
25 from sat.tools import utils 26 from sat.tools import utils
26 from sat.tools.common import uri 27 from sat.tools.common import uri
27 from sat.core.log import getLogger 28 from sat.core.log import getLogger
28 import shortuuid 29 import shortuuid
30 from wokkel import data_form
29 log = getLogger(__name__) 31 log = getLogger(__name__)
30 32
31 NS_TICKETS = 'org.salut-a-toi.tickets:0' 33 NS_TICKETS = 'org.salut-a-toi.tickets:0'
32 34
33 PLUGIN_INFO = { 35 PLUGIN_INFO = {
55 host.bridge.addMethod("ticketsGet", ".plugin", 57 host.bridge.addMethod("ticketsGet", ".plugin",
56 in_sign='ssiassa{ss}s', out_sign='(asa{ss})', 58 in_sign='ssiassa{ss}s', out_sign='(asa{ss})',
57 method=self._get, 59 method=self._get,
58 async=True 60 async=True
59 ) 61 )
60 host.bridge.addMethod("ticketsSet", ".plugin", 62 host.bridge.addMethod("ticketSet", ".plugin",
61 in_sign='ssa{sas}ssa{ss}s', out_sign='s', 63 in_sign='ssa{sas}ssa{ss}s', out_sign='s',
62 method=self._set, 64 method=self._set,
63 async=True) 65 async=True)
64 host.bridge.addMethod("ticketsSchemaGet", ".plugin", 66 host.bridge.addMethod("ticketsSchemaGet", ".plugin",
65 in_sign='sss', out_sign='s', 67 in_sign='sss', out_sign='s',
87 'name': kwargs.get('name'), 89 'name': kwargs.get('name'),
88 'styles': (u'noselect', u'extensible', u'reducible')} 90 'styles': (u'noselect', u'extensible', u'reducible')}
89 return widget_type, args, kwargs 91 return widget_type, args, kwargs
90 92
91 def _dateFilter(self, form_xmlui, widget_type, args, kwargs): 93 def _dateFilter(self, form_xmlui, widget_type, args, kwargs):
92 if widget_type != u'string': 94 if widget_type != u'string' or not args[0]:
93 return widget_type, args, kwargs 95 return widget_type, args, kwargs
94 # we convert XMPP date to timestamp 96 # we convert XMPP date to timestamp
95 args[0] = unicode(utils.date_parse(args[0])) 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))
96 return widget_type, args, kwargs 101 return widget_type, args, kwargs
97 102
98 def _get(self, service='', node='', max_items=10, item_ids=None, sub_id=None, extra_dict=None, profile_key=C.PROF_KEY_NONE): 103 def _get(self, service='', node='', max_items=10, item_ids=None, sub_id=None, extra_dict=None, profile_key=C.PROF_KEY_NONE):
99 client = self.host.getClient(profile_key) 104 client = self.host.getClient(profile_key)
100 service = jid.JID(service) if service else None 105 service = jid.JID(service) if service else None
103 d = self.get(client, service, node or None, max_items, item_ids, sub_id or None, extra.rsm_request, extra.extra) 108 d = self.get(client, service, node or None, max_items, item_ids, sub_id or None, extra.rsm_request, extra.extra)
104 d.addCallback(self._p.serItemsData) 109 d.addCallback(self._p.serItemsData)
105 return d 110 return d
106 111
107 @defer.inlineCallbacks 112 @defer.inlineCallbacks
108 def get(self, client, service=None, node=None, max_items=None, item_ids=None, sub_id=None, rsm_request=None, extra=None): 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):
109 """Retrieve tickets and convert them to XMLUI 114 """Retrieve tickets and convert them to XMLUI
110 115
116 @param service(None, jid.JID): Pubsub service to use
111 @param node(unicode, None): PubSub node to use 117 @param node(unicode, None): PubSub node to use
112 if None, default ticket node will be used 118 if None, default ticket node will be used
113 other parameters as the same as for [XEP_0060.getItems] 119 other parameters as the same as for [XEP_0060.getItems]
114 @return (list[unicode]): XMLUI of the tickets 120 @return (tuple(list[unicode], dict[unicode, object])):
121 - XMLUI of the tickets
122 - metadata dict
115 """ 123 """
116 if not node: 124 if not node:
117 node = NS_TICKETS 125 node = NS_TICKETS
118 filters = {u'reporter': lambda *args: self._reporterFilter(client, *args), 126 filters = {u'reporter': lambda *args: self._reporterFilter(client, *args),
119 u'labels': self._labelsFilter, 127 u'labels': self._labelsFilter,
120 u'created': self._dateFilter, 128 u'created': self._dateFilter,
121 u'updated': self._dateFilter, 129 u'updated': self._dateFilter,
122 } 130 }
123 tickets, metadata = yield self._s.getDataFormItems( 131 tickets, metadata = yield self._s.getDataFormItems(
124 client, 132 client,
125 NS_TICKETS, 133 form_ns,
126 service, 134 service,
127 node, 135 node,
128 max_items = max_items, 136 max_items = max_items,
129 item_ids = item_ids, 137 item_ids = item_ids,
130 sub_id = sub_id, 138 sub_id = sub_id,
140 service = None if not service else jid.JID(service) 148 service = None if not service else jid.JID(service)
141 if schema: 149 if schema:
142 schema = generic.parseXml(schema.encode('utf-8')) 150 schema = generic.parseXml(schema.encode('utf-8'))
143 else: 151 else:
144 schema = None 152 schema = None
153 if extra and u'update' in extra:
154 extra[u'update'] = C.bool(extra[u'update'])
145 d = self.set(client, service, node or None, values, schema, item_id or None, extra, deserialise=True) 155 d = self.set(client, service, node or None, values, schema, item_id or None, extra, deserialise=True)
146 d.addCallback(lambda ret: ret or u'') 156 d.addCallback(lambda ret: ret or u'')
147 return d 157 return d
148 158
149 @defer.inlineCallbacks 159 @defer.inlineCallbacks
150 def set(self, client, service, node, values, schema=None, item_id=None, extra=None, deserialise=False): 160 def set(self, client, service, node, values, schema=None, item_id=None, extra=None, deserialise=False, form_ns=NS_TICKETS):
151 """Publish a tickets 161 """Publish a tickets
152 162
153 @param node(unicode, None): Pubsub node to use 163 @param node(unicode, None): Pubsub node to use
154 None to use default tickets node 164 None to use default tickets node
155 @param values(dict[key(unicode), [iterable[object], object]]): values of the ticket 165 @param values(dict[key(unicode), [iterable[object], object]]): values of the ticket
156 if not iterable, will be put in a list 166 if not iterable, will be put in a list
157 'created' and 'updated' will be forced to current time: 167 'created' and 'updated' will be forced to current time:
158 - 'created' is set if item_id is None, i.e. if it's a new ticket 168 - 'created' is set if item_id is None, i.e. if it's a new ticket
159 - 'updated' is set everytime 169 - 'updated' is set everytime
170 @param extra(dict, None): same as for [XEP-0060.sendItem] with additional keys:
171 - update(bool): if True, get previous item data to merge with current one
172 if True, item_id must be None
160 other arguments are same as for [self._s.sendDataFormItem] 173 other arguments are same as for [self._s.sendDataFormItem]
161 @return (unicode): id of the created item 174 @return (unicode): id of the created item
162 """ 175 """
163 if not node: 176 if not node:
164 node = NS_TICKETS 177 node = NS_TICKETS
178 self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, 191 self._p.OPT_SEND_ITEM_SUBSCRIBE: 1,
179 self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN, 192 self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN,
180 } 193 }
181 yield self._p.createNode(client, comments_service, comments_node, options) 194 yield self._p.createNode(client, comments_service, comments_node, options)
182 values['comments_uri'] = uri.buildXMPPUri(u'pubsub', subtype='microblog', path=comments_service.full(), node=comments_node) 195 values['comments_uri'] = uri.buildXMPPUri(u'pubsub', subtype='microblog', path=comments_service.full(), node=comments_node)
196 elif extra.get(u'update', False):
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)
183 216
184 values['updated'] = now 217 values['updated'] = now
185 if not values.get('reporter'): 218 if not values.get('reporter'):
186 identity = yield self._i.getIdentity(client, client.jid) 219 identity = yield self._i.getIdentity(client, client.jid)
187 values['reporter'] = identity['nick'] 220 values['reporter'] = identity['nick']