From d7b736451b97f0e13e35d728b28ceced6bd0696c Mon Sep 17 00:00:00 2001 From: "Guewen Baconnier @ Camptocamp" Date: Mon, 19 Nov 2012 11:55:37 +0100 Subject: [PATCH 01/20] [ADD] beginning of the pingen module --- pingen/__init__.py | 23 +++++++++ pingen/__openerp__.py | 57 ++++++++++++++++++++++ pingen/ir_attachment.py | 90 +++++++++++++++++++++++++++++++++++ pingen/ir_attachment_view.xml | 28 +++++++++++ pingen/pingen_task.py | 57 ++++++++++++++++++++++ pingen/pingen_task_view.xml | 62 ++++++++++++++++++++++++ 6 files changed, 317 insertions(+) create mode 100644 pingen/__init__.py create mode 100644 pingen/__openerp__.py create mode 100644 pingen/ir_attachment.py create mode 100644 pingen/ir_attachment_view.xml create mode 100644 pingen/pingen_task.py create mode 100644 pingen/pingen_task_view.xml diff --git a/pingen/__init__.py b/pingen/__init__.py new file mode 100644 index 0000000..e5f5183 --- /dev/null +++ b/pingen/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Guewen Baconnier +# Copyright 2012 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import ir_attachment +import pingen_task diff --git a/pingen/__openerp__.py b/pingen/__openerp__.py new file mode 100644 index 0000000..3b48d34 --- /dev/null +++ b/pingen/__openerp__.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Guewen Baconnier +# Copyright 2012 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +{'name' : 'pingen.com integration', + 'version' : '1.0', + 'author' : 'Camptocamp', + 'maintainer': 'Camptocamp', + 'license': 'AGPL-3', + 'category': 'category', + 'complexity': 'easy', + 'depends' : [], + 'description': """ +Integration with pingen.com +=========================== + +What is pingen.com +------------------ + +Pingen.com is a paid online service. +It send uploaded documents by letter post. + +Integration +----------- + +One can decide, per document / attachment, if it should be pushed +to pingen.com. The documents are pushed asynchronously. + + """, + 'website': 'http://www.camptocamp.com', + 'init_xml': [], + 'update_xml': [ + 'ir_attachment_view.xml', + 'pingen_task_view.xml', + ], + 'demo_xml': [], + 'tests': [], + 'installable': True, + 'auto_install': False, +} diff --git a/pingen/ir_attachment.py b/pingen/ir_attachment.py new file mode 100644 index 0000000..82558d2 --- /dev/null +++ b/pingen/ir_attachment.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Guewen Baconnier +# Copyright 2012 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp.osv import osv, orm, fields +from openerp.tools.translate import _ + + +class ir_attachment(orm.Model): + + _inherit = 'ir.attachment' + + _columns = { + 'send_to_pingen': fields.boolean('Send to Pingen.com'), + 'pingen_task_ids': fields.one2many( + 'pingen.task', 'attachment_id', + string="Pingen Task", readonly=True), + } + + def _prepare_pingen_task_vals(self, cr, uid, attachment, context=None): + return {'attachment_id': attachment.id, + 'config': 'created from attachment'} + + def _handle_pingen_task(self, cr, uid, attachment_id, context=None): + """ Reponsible of the related ``pingen.task`` when the ``send_to_pingen`` + field is modified. + + Only one pingen task can be created per attachment. + + When ``send_to_pingen`` is activated: + * Create a ``pingen.task`` if it does not already exist + * Put the related ``pingen.task`` to ``pending`` if it already exist + When it is deactivated: + * Do nothing if no related ``pingen.task`` exists + * Or cancel it + * If it has already been pushed to pingen.com, raises + an `osv.except_osv` exception + """ + pingen_task_obj = self.pool.get('pingen.task') + attachment = self.browse(cr, uid, attachment_id, context=context) + task = attachment.pingen_task_ids[0] if attachment.pingen_task_ids else None + if attachment.send_to_pingen: + if task: + task.write({'state': 'pending'}, context=context) + else: + pingen_task_obj.create( + cr, uid, + self._prepare_pingen_task_vals( + cr, uid, attachment, context=context), + context=context) + else: + if task: + if task.state == 'pushed': + raise osv.except_osv( + _('Error'), + _('The attachment %s is already pushed to pingen.com.') % \ + attachment.name) + task.write({'state': 'canceled'}, context=context) + return + + def create(self, cr, uid, vals, context=None): + attachment_id = super(ir_attachment, self).create(cr, uid, vals, context=context) + if 'send_to_pingen' in vals: + self._handle_pingen_task(cr, uid, attachment_id, context=context) + return attachment_id + + def write(self, cr, uid, ids, vals, context=None): + res = super(ir_attachment, self).write(cr, uid, ids, vals, context=context) + if 'send_to_pingen' in vals: + for attachment_id in ids: + self._handle_pingen_task(cr, uid, attachment_id, context=context) + return res + diff --git a/pingen/ir_attachment_view.xml b/pingen/ir_attachment_view.xml new file mode 100644 index 0000000..e0a27e0 --- /dev/null +++ b/pingen/ir_attachment_view.xml @@ -0,0 +1,28 @@ + + + + + + ir.attachment.pingen.view + ir.attachment + form + + + + + + + + + + + + + + diff --git a/pingen/pingen_task.py b/pingen/pingen_task.py new file mode 100644 index 0000000..af6bd74 --- /dev/null +++ b/pingen/pingen_task.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Guewen Baconnier +# Copyright 2012 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp.osv import orm, fields + + +class pingen_task(orm.Model): + """ A pingen task is the state of the synchronization of + an attachment with pingen.com + + It stores the configuration and the current state of the synchronization. + It also serves as a queue of documents to push to pingen.com + """ + + _name = 'pingen.task' + _inherits = {'ir.attachment': 'attachment_id'} + + _columns = { + 'attachment_id': fields.many2one( + 'ir.attachment', 'Document', required=True, ondelete='cascade'), + 'state': fields.selection( + [('pending', 'Pending'), + ('pushed', 'Pushed'), + ('error', 'Error'), + ('canceled', 'Canceled')], + string='State', readonly=True, required=True), + 'config': fields.text('Configuration (tmp)'), + } + + _defaults = { + 'state': 'pending', + } + + _sql_constraints = [ + ('pingen_task_attachment_uniq', + 'unique (attachment_id)', + 'Only one Pingen task is allowed per attachment.'), + ] + diff --git a/pingen/pingen_task_view.xml b/pingen/pingen_task_view.xml new file mode 100644 index 0000000..83540ca --- /dev/null +++ b/pingen/pingen_task_view.xml @@ -0,0 +1,62 @@ + + + + + + pingen.task.tree + pingen.task + tree + + + + + + + + + + pingen.task.form + pingen.task + form + +
+ + + + +
+ + + pingen.task.search + pingen.task + search + + + + + + + + + + + + + Pingen Tasks + ir.actions.act_window + pingen.task + form + tree,form + + + + + +
+
From 9575e7a3a73d1f30ba4efe0d5d991ba5a1d17e52 Mon Sep 17 00:00:00 2001 From: "Guewen Baconnier @ Camptocamp" Date: Mon, 19 Nov 2012 16:49:28 +0100 Subject: [PATCH 02/20] [IMP] push a pingen.task --- pingen/__openerp__.py | 13 +++- pingen/pingen_task.py | 115 ++++++++++++++++++++++++++++++++++++ pingen/pingen_task_view.xml | 10 +++- 3 files changed, 132 insertions(+), 6 deletions(-) diff --git a/pingen/__openerp__.py b/pingen/__openerp__.py index 3b48d34..314c473 100644 --- a/pingen/__openerp__.py +++ b/pingen/__openerp__.py @@ -37,13 +37,20 @@ What is pingen.com Pingen.com is a paid online service. It send uploaded documents by letter post. -Integration ------------ +Scope of the integration +------------------------ One can decide, per document / attachment, if it should be pushed to pingen.com. The documents are pushed asynchronously. - """, +Dependencies +------------ + + * Require the Python library `requests `_ + * The PDF files sent to pingen.com have to respect some `formatting rules + `_. + +""", 'website': 'http://www.camptocamp.com', 'init_xml': [], 'update_xml': [ diff --git a/pingen/pingen_task.py b/pingen/pingen_task.py index af6bd74..771aa56 100644 --- a/pingen/pingen_task.py +++ b/pingen/pingen_task.py @@ -19,8 +19,21 @@ # ############################################################################## +import requests +import urlparse +import base64 +import logging + +from cStringIO import StringIO + from openerp.osv import orm, fields +# TODO should be configurable +BASE_URL = 'https://stage-api.pingen.com' +TOKEN = '6bc041af6f02854461ef31c2121ef853' + +_logger = logging.getLogger(__name__) + class pingen_task(orm.Model): """ A pingen task is the state of the synchronization of @@ -43,6 +56,20 @@ class pingen_task(orm.Model): ('canceled', 'Canceled')], string='State', readonly=True, required=True), 'config': fields.text('Configuration (tmp)'), + 'date': fields.datetime('Creation Date'), + 'push_date': fields.datetime('Pushed Date'), + 'send': fields.boolean( + 'Send', + help="Defines if a document is merely uploaed or also sent"), + 'speed': fields.selection( + [(1, 'Priority'), (2, 'Economy')], + 'Speed', + help="Defines the sending speed if the document is automatically sent"), + 'color': fields.selection( [(0, 'B/W'), (1, 'Color')], 'Type of print'), + 'last_error_code': fields.integer('Error Code', readonly=True), + 'last_error_message': fields.text('Error Message', readonly=True), + 'pingen_id': fields.integer('Pingen ID'), + } _defaults = { @@ -55,3 +82,91 @@ class pingen_task(orm.Model): 'Only one Pingen task is allowed per attachment.'), ] + def _push_to_pingen(self, cr, uid, task_id, context=None): + """ Push a document to pingen.com + + + """ + success = False + push_url = urlparse.urljoin(BASE_URL, 'document/upload') + auth = {'token': TOKEN} + + config = { + 'send': False, + 'speed': 2, + 'color': 1, + } + + task = self.browse(cr, uid, task_id, context=context) + + if task.type == 'binary': + decoded_document = base64.decodestring(task.datas) + else: + url_resp = requests.get(task.url) + if url_resp.status_code != 200: + task.write({'last_error_code': False, + 'last_error_message': "%s" % req.error, + 'state': 'error'}, + context=context) + return False + decoded_document = requests.content + + document = {'file': (task.datas_fname, StringIO(decoded_document))} + try: + # TODO extract + req = requests.post( + push_url, + params=auth, + data=config, + files=document, + # verify = False required for staging environment + verify=False) + req.raise_for_status() + except (requests.HTTPError, + requests.Timeout, + requests.ConnectionError) as e: + msg = ('%s Message: %s. \n' + 'It occured when pushing the Pingen Task %s to pingen.com. \n' + 'It will be retried later.' % (e.__doc__, e, task.id)) + _logger.error(msg) + task.write( + {'last_error_code': False, + 'last_error_message': msg, + 'state': 'error'}, + context=context) + else: + response = req.json + if response['error']: + task.write( + {'last_error_code': response['errorcode'], + 'last_error_message': 'Error from pingen.com: %s' % response['errormessage'], + 'state': 'error'}, + context=context) + else: + task.write( + {'last_error_code': False, + 'last_error_message': False, + 'state': 'pushed', + 'pingen_id': response['id'],}, + context=context) + _logger.info('Pingen Task %s pushed to pingen.com.' % task.id) + success = True + + return success + + def push_to_pingen(self, cr, uid, ids, context=None): + """ Push a document to pingen.com + + Wrapper method for multiple ids (when triggered from button) + """ + for task_id in ids: + self._push_to_pingen(cr, uid, task_id, context=context) + return True + + + # r = requests.get(push_url, params=auth, verify=False) + + + # TODO: resolve = put in pending or put in pushed + + diff --git a/pingen/pingen_task_view.xml b/pingen/pingen_task_view.xml index 83540ca..1aeea82 100644 --- a/pingen/pingen_task_view.xml +++ b/pingen/pingen_task_view.xml @@ -8,8 +8,9 @@ tree - - + + + @@ -20,8 +21,11 @@ form
- + + + +