diff --git a/pingen/pingen.py b/pingen/pingen.py index 66f4af4..0d84856 100644 --- a/pingen/pingen.py +++ b/pingen/pingen.py @@ -28,6 +28,14 @@ from requests.packages.urllib3.filepost import encode_multipart_formdata _logger = logging.getLogger(__name__) +POST_SENDING_STATUS = { + 100: 'Ready/Pending', + 101: 'Processing', + 102: 'Waiting for confirmation', + 200: 'Sent', + 300: 'Some error occured and object wasn\'t sent', + 400: 'Sending cancelled', +} class PingenException(RuntimeError): """There was an ambiguous exception that occurred while handling your @@ -42,7 +50,6 @@ class APIError(PingenException): """An Error occured with the pingen API""" - class Pingen(object): """ Interface to pingen.com API """ @@ -90,7 +97,9 @@ class Pingen(object): response = method(complete_url, **kwargs) if not response.ok: - raise ConnectionError(response.error) + raise ConnectionError( + "%s: %s" % (response.json['errorcode'], + response.json['errormessage'])) if response.json['error']: raise APIError( @@ -167,3 +176,24 @@ class Pingen(object): return response.json['id'] + def post_infos(self, post_id): + """ Return the information of a post + + :param int post_id: id of the document to send + :return: dict of infos of the post + """ + response = self._send( + requests.get, + 'post/get', + params={'id': post_id}) + + return response.json['item'] + + @staticmethod + def is_posted(post_infos): + """ return True if the post has been sent + + :param dict post_infos: post infos returned by `post_infos` + """ + return post_infos['status'] == 200 + diff --git a/pingen/pingen_task.py b/pingen/pingen_task.py index 02afde7..3166fac 100644 --- a/pingen/pingen_task.py +++ b/pingen/pingen_task.py @@ -20,17 +20,17 @@ ############################################################################## import logging -import datetime from cStringIO import StringIO from openerp.osv import osv, orm, fields from openerp import tools from openerp.tools.translate import _ -from .pingen import Pingen, APIError, ConnectionError +from .pingen import Pingen, APIError, ConnectionError, POST_SENDING_STATUS # TODO should be configurable TOKEN = '6bc041af6f02854461ef31c2121ef853' +STAGING = True _logger = logging.getLogger(__name__) @@ -58,15 +58,28 @@ class pingen_task(orm.Model): ('pingen_error', 'Pingen Error'), ('canceled', 'Canceled')], string='State', readonly=True, required=True), - 'date': fields.datetime('Creation Date'), - 'push_date': fields.datetime('Push Date'), + 'date': fields.datetime('Creation Date', readonly=True), + 'push_date': fields.datetime('Push Date', readonly=True), + + # for `error` and `pingen_error` states when we push 'last_error_message': fields.text('Error Message', readonly=True), + + # pingen IDs 'pingen_id': fields.integer( - 'Pingen ID', + 'Pingen ID', readonly=True, help="ID of the document in the Pingen Documents"), 'post_id': fields.integer( - 'Pingen Post ID', + 'Pingen Post ID', readonly=True, help="ID of the document in the Pingen Sendcenter"), + + # sendcenter infos + 'post_status': fields.char('Post Status', size=128, readonly=True), + 'parsed_address': fields.text('Parsed Address', readonly=True), + 'cost': fields.float('Cost', readonly=True), + 'currency_id': fields.many2one('res.currency', 'Currency', readonly=True), + 'country_id': fields.many2one('res.country', 'Country', readonly=True), + 'send_date': fields.datetime('Date of sending', readonly=True), + 'pages': fields.integer('Pages', readonly=True), } _defaults = { @@ -79,6 +92,12 @@ class pingen_task(orm.Model): 'Only one Pingen task is allowed per attachment.'), ] + def _pingen(self, cr, uid, ids, context=None): + """ Return a Pingen instance to work on """ + assert not ids, "ids is there by convention, should not be used" + # TODO parameterize + return Pingen(TOKEN, staging=STAGING) + def _push_to_pingen(self, cr, uid, task, context=None): """ Push a document to pingen.com """ attachment_obj = self.pool.get('ir.attachment') @@ -88,31 +107,32 @@ class pingen_task(orm.Model): pingen = self._pingen(cr, uid, [], context=context) try: - doc_id, post_id, __ = pingen.push_document( + doc_id, post_id, infos = pingen.push_document( task.datas_fname, StringIO(decoded_document), task.pingen_send, task.pingen_speed, task.pingen_color) except ConnectionError as e: - _logger.exception('Connection Error when pushing Pingen Task %s to %s.' % + _logger.exception( + 'Connection Error when pushing Pingen Task %s to %s.' % (task.id, pingen.url)) raise except APIError as e: - _logger.error('API Error when pushing Pingen Task %s to %s.' % + _logger.error( + 'API Error when pushing Pingen Task %s to %s.' % (task.id, pingen.url)) raise - now = datetime.datetime.now().strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) task.write( {'last_error_message': False, 'state': 'sendcenter' if post_id else 'pushed', - 'push_date': now, + 'push_date': infos['date'], 'pingen_id': doc_id, 'post_id': post_id}, context=context) - _logger.info('Pingen Task %s pushed to %s' % (task.id, pingen.url)) + _logger.info('Pingen Task %s: pushed to %s' % (task.id, pingen.url)) def push_to_pingen(self, cr, uid, ids, context=None): """ Push a document to pingen.com @@ -137,26 +157,29 @@ class pingen_task(orm.Model): '\n%s') % (task.name, e)) return True - def _push_to_pingen_with_logs(self, cr, uid, ids, context=None): + def _push_and_send_to_pingen_silent(self, cr, uid, ids, context=None): """ Push a document to pingen.com Instead of raising, store the error in the pingen.document """ + if not ids: + ids = self.search( + cr, uid, + # do not retry pingen_error, they should be treated manually + [('state', 'in', ['pending', 'pushed', 'error'])], + context=context) for task in self.browse(cr, uid, ids, context=context): try: - self._push_to_pingen(cr, uid, task, context=context) + if not task.pingen_id: + self._push_to_pingen(cr, uid, task, context=context) + if not task.post_id and task.pingen_send: + self._ask_pingen_send(cr, uid, task, context=context) except ConnectionError as e: task.write({'last_error_message': e, 'state': 'error'}, context=context) except APIError as e: task.write({'last_error_message': e, 'state': 'pingen_error'}, context=context) - return True - def _pingen(self, cr, uid, ids, context=None): - """ - """ - assert not ids, "ids is there by convention, should not be used" - # TODO parameterize - return Pingen(TOKEN, staging=True) + return True def _ask_pingen_send(self, cr, uid, task, context=None): """ For a document already pushed to pingen, ask to send it. @@ -186,7 +209,7 @@ class pingen_task(orm.Model): 'state': 'sendcenter', 'post_id': post_id}, context=context) - _logger.info('Pingen Task %s asked for sending to %s' % (task.id, pingen.url)) + _logger.info('Pingen Task %s: asked for sending to %s' % (task.id, pingen.url)) return True @@ -211,3 +234,84 @@ class pingen_task(orm.Model): '\n%s') % (task.name, e)) return True + def _update_post_infos(self, cr, uid, task, context=None): + """ Update the informations from pingen of a document in the Sendcenter + """ + if not task.post_id: + return + + pingen = self._pingen(cr, uid, [], context=context) + try: + post_infos = pingen.post_infos(task.post_id) + except ConnectionError as e: + _logger.exception('Connection Error when asking for sending Pingen Task %s to %s.' % + (task.id, pingen.url)) + raise + except APIError as e: + _logger.exception('API Error when asking for sending Pingen Task %s to %s.' % + (task.id, pingen.url)) + raise + + currency_ids = self.pool.get('res.currency').search( + cr, uid, [('name', '=', post_infos['currency'])], context=context) + country_ids = self.pool.get('res.country').search( + cr, uid, [('code', '=', post_infos['country'])], context=context) + vals = { + 'post_status': POST_SENDING_STATUS[post_infos['status']], + 'cost': post_infos['cost'], + 'currency_id': currency_ids[0] if currency_ids else False, + 'parsed_address': post_infos['address'], + 'country_id': country_ids[0] if country_ids else False, + 'send_date': post_infos['date'], + 'pages': post_infos['pages'], + } + if pingen.is_posted(post_infos): + vals['state'] = 'sent' + + task.write(vals, context=context) + _logger.info('Pingen Task %s: status updated' % task.id) + + def _update_post_infos_silent(self, cr, uid, ids, context=None): + """ Update the informations from pingen of a document in the Sendcenter + + Do not raise errors, only skip the update of the record. + """ + if not ids: + ids = self.search( + cr, uid, + [('state', '=', 'sendcenter')], + context=context) + + for task in self.browse(cr, uid, ids, context=context): + try: + self._update_post_infos(cr, uid, task, context=context) + except (ConnectionError, APIError): + # Intended silented exception, we can consider that it's not + # important if the update not worked, that's + # only informative, and it will be retried the next time + # In any case, the error has been by _update_post_infos + pass + return True + + def update_post_infos(self, cr, uid, ids, context=None): + """ Update the informations from pingen of a document in the Sendcenter + + Wrapper method for multiple ids (when triggered from button for + instance) for public interface. + """ + for task in self.browse(cr, uid, ids, context=context): + try: + self._update_post_infos(cr, uid, task, context=context) + except ConnectionError as e: + raise osv.except_osv( + _('Pingen Connection Error'), + _('Connection Error when updating the status of Document %s' + ' from Pingen') % task.name) + + except APIError as e: + raise osv.except_osv( + _('Pingen Error'), + _('Error when updating the status of Document %s from Pingen: ' + '\n%s') % (task.name, e)) + return True + diff --git a/pingen/pingen_task_view.xml b/pingen/pingen_task_view.xml index 5f59421..f036d4e 100644 --- a/pingen/pingen_task_view.xml +++ b/pingen/pingen_task_view.xml @@ -46,15 +46,33 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + -