comparison sat/plugins/plugin_tickets_import.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents 003b8b4b56a7
children 9d0df638c8b4
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SàT plugin for import external ticketss 4 # SàT plugin for import external ticketss
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 6
34 C.PI_TYPE: C.PLUG_TYPE_IMPORT, 34 C.PI_TYPE: C.PLUG_TYPE_IMPORT,
35 C.PI_DEPENDENCIES: ["IMPORT", "XEP-0060", "XEP-0277", "PUBSUB_SCHEMA"], 35 C.PI_DEPENDENCIES: ["IMPORT", "XEP-0060", "XEP-0277", "PUBSUB_SCHEMA"],
36 C.PI_MAIN: "TicketsImportPlugin", 36 C.PI_MAIN: "TicketsImportPlugin",
37 C.PI_HANDLER: "no", 37 C.PI_HANDLER: "no",
38 C.PI_DESCRIPTION: _( 38 C.PI_DESCRIPTION: _(
39 u"""Tickets import management: 39 """Tickets import management:
40 This plugin manage the different tickets importers which can register to it, and handle generic importing tasks.""" 40 This plugin manage the different tickets importers which can register to it, and handle generic importing tasks."""
41 ), 41 ),
42 } 42 }
43 43
44 OPT_MAPPING = "mapping" 44 OPT_MAPPING = "mapping"
45 FIELDS_LIST = (u"labels", u"cc_emails") # fields which must have a list as value 45 FIELDS_LIST = ("labels", "cc_emails") # fields which must have a list as value
46 FIELDS_DATE = (u"created", u"updated") 46 FIELDS_DATE = ("created", "updated")
47 47
48 NS_TICKETS = "org.salut-a-toi.tickets:0" 48 NS_TICKETS = "org.salut-a-toi.tickets:0"
49 49
50 50
51 class TicketsImportPlugin(object): 51 class TicketsImportPlugin(object):
58 self.host = host 58 self.host = host
59 self._importers = {} 59 self._importers = {}
60 self._p = host.plugins["XEP-0060"] 60 self._p = host.plugins["XEP-0060"]
61 self._m = host.plugins["XEP-0277"] 61 self._m = host.plugins["XEP-0277"]
62 self._s = host.plugins["PUBSUB_SCHEMA"] 62 self._s = host.plugins["PUBSUB_SCHEMA"]
63 host.plugins["IMPORT"].initialize(self, u"tickets") 63 host.plugins["IMPORT"].initialize(self, "tickets")
64 64
65 @defer.inlineCallbacks 65 @defer.inlineCallbacks
66 def importItem( 66 def importItem(
67 self, client, item_import_data, session, options, return_data, service, node 67 self, client, item_import_data, session, options, return_data, service, node
68 ): 68 ):
106 If you specify several import ticket key to the same dest key, 106 If you specify several import ticket key to the same dest key,
107 the values will be joined with line feeds 107 the values will be joined with line feeds
108 """ 108 """
109 if "comments_uri" in item_import_data: 109 if "comments_uri" in item_import_data:
110 raise exceptions.DataError( 110 raise exceptions.DataError(
111 _(u"comments_uri key will be generated and must not be used by importer") 111 _("comments_uri key will be generated and must not be used by importer")
112 ) 112 )
113 for key in FIELDS_LIST: 113 for key in FIELDS_LIST:
114 if not isinstance(item_import_data.get(key, []), list): 114 if not isinstance(item_import_data.get(key, []), list):
115 raise exceptions.DataError(_(u"{key} must be a list").format(key=key)) 115 raise exceptions.DataError(_("{key} must be a list").format(key=key))
116 for key in FIELDS_DATE: 116 for key in FIELDS_DATE:
117 try: 117 try:
118 item_import_data[key] = utils.xmpp_date(item_import_data[key]) 118 item_import_data[key] = utils.xmpp_date(item_import_data[key])
119 except KeyError: 119 except KeyError:
120 continue 120 continue
121 if session[u"root_node"] is None: 121 if session["root_node"] is None:
122 session[u"root_node"] = NS_TICKETS 122 session["root_node"] = NS_TICKETS
123 if not "schema" in session: 123 if not "schema" in session:
124 session["schema"] = yield self._s.getSchemaForm( 124 session["schema"] = yield self._s.getSchemaForm(
125 client, service, node or session[u"root_node"] 125 client, service, node or session["root_node"]
126 ) 126 )
127 defer.returnValue(item_import_data) 127 defer.returnValue(item_import_data)
128 128
129 @defer.inlineCallbacks 129 @defer.inlineCallbacks
130 def importSubItems(self, client, item_import_data, ticket_data, session, options): 130 def importSubItems(self, client, item_import_data, ticket_data, session, options):
131 # TODO: force "open" permission (except if private, check below) 131 # TODO: force "open" permission (except if private, check below)
132 # TODO: handle "private" metadata, to have non public access for node 132 # TODO: handle "private" metadata, to have non public access for node
133 # TODO: node access/publish model should be customisable 133 # TODO: node access/publish model should be customisable
134 comments = ticket_data.get("comments", []) 134 comments = ticket_data.get("comments", [])
135 service = yield self._m.getCommentsService(client) 135 service = yield self._m.getCommentsService(client)
136 node = self._m.getCommentsNode(session["root_node"] + u"_" + ticket_data["id"]) 136 node = self._m.getCommentsNode(session["root_node"] + "_" + ticket_data["id"])
137 node_options = { 137 node_options = {
138 self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN, 138 self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN,
139 self._p.OPT_PERSIST_ITEMS: 1, 139 self._p.OPT_PERSIST_ITEMS: 1,
140 self._p.OPT_MAX_ITEMS: -1, 140 self._p.OPT_MAX_ITEMS: -1,
141 self._p.OPT_DELIVER_PAYLOADS: 1, 141 self._p.OPT_DELIVER_PAYLOADS: 1,
142 self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, 142 self._p.OPT_SEND_ITEM_SUBSCRIBE: 1,
143 self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN, 143 self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN,
144 } 144 }
145 yield self._p.createIfNewNode(client, service, node, options=node_options) 145 yield self._p.createIfNewNode(client, service, node, options=node_options)
146 ticket_data["comments_uri"] = uri.buildXMPPUri( 146 ticket_data["comments_uri"] = uri.buildXMPPUri(
147 u"pubsub", subtype="microblog", path=service.full(), node=node 147 "pubsub", subtype="microblog", path=service.full(), node=node
148 ) 148 )
149 for comment in comments: 149 for comment in comments:
150 if "updated" not in comment and "published" in comment: 150 if "updated" not in comment and "published" in comment:
151 # we don't want an automatic update date 151 # we don't want an automatic update date
152 comment["updated"] = comment["published"] 152 comment["updated"] = comment["published"]
155 def publishItem(self, client, ticket_data, service, node, session): 155 def publishItem(self, client, ticket_data, service, node, session):
156 if node is None: 156 if node is None:
157 node = NS_TICKETS 157 node = NS_TICKETS
158 id_ = ticket_data.pop("id", None) 158 id_ = ticket_data.pop("id", None)
159 log.debug( 159 log.debug(
160 u"uploading item [{id}]: {title}".format( 160 "uploading item [{id}]: {title}".format(
161 id=id_, title=ticket_data.get("title", "") 161 id=id_, title=ticket_data.get("title", "")
162 ) 162 )
163 ) 163 )
164 return self._s.sendDataFormItem( 164 return self._s.sendDataFormItem(
165 client, service, node, ticket_data, session["schema"], id_ 165 client, service, node, ticket_data, session["schema"], id_
167 167
168 def itemFilters(self, client, ticket_data, session, options): 168 def itemFilters(self, client, ticket_data, session, options):
169 mapping = options.get(OPT_MAPPING) 169 mapping = options.get(OPT_MAPPING)
170 if mapping is not None: 170 if mapping is not None:
171 if not isinstance(mapping, dict): 171 if not isinstance(mapping, dict):
172 raise exceptions.DataError(_(u"mapping option must be a dictionary")) 172 raise exceptions.DataError(_("mapping option must be a dictionary"))
173 173
174 for source, dest in mapping.iteritems(): 174 for source, dest in mapping.items():
175 if not isinstance(source, unicode) or not isinstance(dest, unicode): 175 if not isinstance(source, str) or not isinstance(dest, str):
176 raise exceptions.DataError( 176 raise exceptions.DataError(
177 _( 177 _(
178 u"keys and values of mapping must be sources and destinations ticket fields" 178 "keys and values of mapping must be sources and destinations ticket fields"
179 ) 179 )
180 ) 180 )
181 if source in ticket_data: 181 if source in ticket_data:
182 value = ticket_data.pop(source) 182 value = ticket_data.pop(source)
183 if dest in FIELDS_LIST: 183 if dest in FIELDS_LIST:
184 values = ticket_data[dest] = ticket_data.get(dest, []) 184 values = ticket_data[dest] = ticket_data.get(dest, [])
185 values.append(value) 185 values.append(value)
186 else: 186 else:
187 if dest in ticket_data: 187 if dest in ticket_data:
188 ticket_data[dest] = ticket_data[dest] + u"\n" + value 188 ticket_data[dest] = ticket_data[dest] + "\n" + value
189 else: 189 else:
190 ticket_data[dest] = value 190 ticket_data[dest] = value