Pingen migrated to 10.0

attachment_id not needed

Pingen get_pingen_session fixed
This commit is contained in:
a-baghirli
2017-10-17 18:11:16 +03:00
committed by Anna Janiszewska
parent c8281ce5e7
commit cf1e008676
11 changed files with 458 additions and 578 deletions

108
pingen/README.rst Normal file
View File

@@ -0,0 +1,108 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
===========================
Integration with pingen.com
===========================
What is pingen.com
==================
Pingen.com is a paid online service.
It sends uploaded documents by letter post.
Scope of the integration
========================
One can decide, per document / attachment, if it should be pushed
to pingen.com. The documents are pushed asynchronously.
A second cron updates the informations of the documents from pingen.com, so we
know which of them have been sent.
Configuration
=============
The authentication token is configured on the company's view. You can also
tick a checkbox if the staging environment (https://stage-api.pingen.com)
should be used.
The setup of the 2 crons can be changed as well:
* Run Pingen Document Push
* Run Pingen Document Update
Usage
=====
On the attachment view, a new pingen.com tab has been added.
You can tick a box to push the document to pingen.com.
There is 3 additional options:
* Send: the document will not be only uploaded, but will be also be sent
* Speed: priority or economy
* Type of print: color or black and white
Once the configuration is done and the attachment saved, a Pingen Document
is created. You can directly access to the latter on the Link on the right on
the attachment view.
You can find them in `Settings > Customization > Low Level Objets > Pingen
Documents` or in the more convenient `Documents` menu if you have installed the
`document` module.
Errors
======
Sometimes, pingen.com will refuse to send a document because it does not meet
its requirements. In such case, the document's state becomes "Pingen Error"
and you will need to manually handle the case, either from the pingen.com
backend, or by changing the document on OpenERP and resolving the error on the
Pingen Document.
When a connection error occurs, the action will be retried on the next
scheduler run.
Dependencies
============
* Require the Python library `requests <http://docs.python-requests.org/>`_
* The PDF files sent to pingen.com have to respect some `formatting rules
<https://stage-app.pingen.com/resources/pingen_requirements_v1_en.pdf>`_.
* The address must be in a format accepted by pingen.com: the last line
is the country in English or German.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/report-print-send/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Contributors
============
* Yannick Vaucher <yannick.vaucher@camptocamp.com>
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
* Anar Baghirli <a.baghirli@mobilunity.com>
Maintainer
==========
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit https://odoo-community.org.

View File

@@ -1,25 +1,9 @@
# -*- 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
# Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import ir_attachment
import pingen
import pingen_document
import res_company
from . import ir_attachment
from . import pingen
from . import pingen_document
from . import res_company

View File

@@ -1,27 +1,11 @@
# -*- 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
# Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
'name': 'pingen.com integration',
'version': '1.0',
'version': '10.0.1.0.0',
'author': "Camptocamp,Odoo Community Association (OCA)",
'maintainer': 'Camptocamp',
'license': 'AGPL-3',
@@ -31,79 +15,6 @@
'external_dependencies': {
'python': ['requests'],
},
'description': """
Integration with pingen.com
===========================
What is pingen.com
------------------
Pingen.com is a paid online service.
It sends uploaded documents by letter post.
Scope of the integration
------------------------
One can decide, per document / attachment, if it should be pushed
to pingen.com. The documents are pushed asynchronously.
A second cron updates the informations of the documents from pingen.com, so we
know which of them have been sent.
Configuration
-------------
The authentication token is configured on the company's view. You can also
tick a checkbox if the staging environment (https://stage-api.pingen.com)
should be used.
The setup of the 2 crons can be changed as well:
* Run Pingen Document Push
* Run Pingen Document Update
Usage
-----
On the attachment view, a new pingen.com tab has been added.
You can tick a box to push the document to pingen.com.
There is 3 additional options:
* Send: the document will not be only uploaded, but will be also be sent
* Speed: priority or economy
* Type of print: color or black and white
Once the configuration is done and the attachment saved, a Pingen Document
is created. You can directly access to the latter on the Link on the right on
the attachment view.
You can find them in `Settings > Customization > Low Level Objets > Pingen
Documents` or in the more convenient `Documents` menu if you have installed the
`document` module.
Errors
------
Sometimes, pingen.com will refuse to send a document because it does not meet
its requirements. In such case, the document's state becomes "Pingen Error" and
you will need to manually handle the case, either from the pingen.com backend,
or by changing the document on OpenERP and resolving the error on the Pingen
Document.
When a connection error occurs, the action will be retried on the next scheduler
run.
Dependencies
------------
* Require the Python library `requests <http://docs.python-requests.org/>`_
* The PDF files sent to pingen.com have to respect some `formatting rules
<https://stage-app.pingen.com/resources/pingen_requirements_v1_en.pdf>`_.
* The address must be in a format accepted by pingen.com: the last line
is the country in English or German.
""",
'website': 'http://www.camptocamp.com',
'data': [
'ir_attachment_view.xml',
@@ -113,7 +24,7 @@ Dependencies
'security/ir.model.access.csv',
],
'tests': [],
'installable': False,
'installable': True,
'auto_install': False,
'application': True,
}

View File

@@ -1,123 +1,99 @@
# -*- 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
# Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import requests
import base64
from openerp.osv import osv, orm, fields
from openerp.tools.translate import _
from odoo import models, fields, _
from odoo.exceptions import UserError
class ir_attachment(orm.Model):
class IrAttachment(models.Model):
_inherit = 'ir.attachment'
_columns = {
'send_to_pingen': fields.boolean('Send to Pingen.com'),
'pingen_document_ids': fields.one2many(
'pingen.document', 'attachment_id',
string='Pingen Document', readonly=True),
'pingen_send': fields.boolean(
'Send',
help="Defines if a document is merely uploaded or also sent"),
'pingen_speed': fields.selection(
[('1', 'Priority'), ('2', 'Economy')],
'Speed',
help="Defines the sending speed if the document is automatically sent"),
'pingen_color': fields.selection([('0', 'B/W'), ('1', 'Color')], 'Type of print'),
}
send_to_pingen = fields.Boolean("Send to Pingen.com")
pingen_document_ids = fields.One2many(
'pingen.document', 'attachment_id',
string='Pingen Document', readonly=True)
pingen_send = fields.Boolean(
'Send', help="Defines if a document is merely uploaded or also sent",
default=True)
pingen_speed = fields.Selection(
[('1', 'Priority'), ('2', 'Economy')],
'Speed', default='2',
help='Defines the sending speed if ' +
'the document is automatically sent')
pingen_color = fields.Selection([('0', 'B/W'), ('1', 'Color')],
'Type of print',
default='0')
_defaults = {
'pingen_send': True,
'pingen_color': '0',
'pingen_speed': '2',
}
def _prepare_pingen_document_vals(self, cr, uid, attachment, context=None):
return {'attachment_id': attachment.id,
def _prepare_pingen_document_vals(self):
return {'attachment_id': self.id,
'config': 'created from attachment'}
def _handle_pingen_document(self, cr, uid, attachment_id, context=None):
""" Reponsible of the related ``pingen.document`` when the ``send_to_pingen``
def _handle_pingen_document(self):
""" Reponsible of the related ``pingen.document``
when the ``send_to_pingen``
field is modified.
Only one pingen document can be created per attachment.
When ``send_to_pingen`` is activated:
* Create a ``pingen.document`` if it does not already exist
* Put the related ``pingen.document`` to ``pending`` if it already exist
* Put the related ``pingen.document`` to ``pending``
if it already exist
When it is deactivated:
* Do nothing if no related ``pingen.document`` exists
* Or cancel it
* If it has already been pushed to pingen.com, raises
an `osv.except_osv` exception
"""
pingen_document_obj = self.pool.get('pingen.document')
attachment = self.browse(cr, uid, attachment_id, context=context)
document = attachment.pingen_document_ids[0] if attachment.pingen_document_ids else None
if attachment.send_to_pingen:
pingen_document_obj = self.env['pingen.document']
document = self.pingen_document_ids[0] if \
self.pingen_document_ids else None
if self.send_to_pingen:
if document:
document.write({'state': 'pending'}, context=context)
document.write({'state': 'pending'})
else:
pingen_document_obj.create(
cr, uid,
self._prepare_pingen_document_vals(
cr, uid, attachment, context=context),
context=context)
self._prepare_pingen_document_vals())
else:
if document:
if document.state == 'pushed':
raise osv.except_osv(
_('Error'),
_('The attachment %s is already pushed to pingen.com.') %
attachment.name)
document.write({'state': 'canceled'}, context=context)
raise UserError(
_('Error. The attachment ' +
'%s is already pushed to pingen.com.') %
self.name)
document.write({'state': 'canceled'})
return
def create(self, cr, uid, vals, context=None):
attachment_id = super(ir_attachment, self).create(cr, uid, vals, context=context)
def create(self, vals):
attachment_id = super(IrAttachment, self).create(vals)
if 'send_to_pingen' in vals:
self._handle_pingen_document(cr, uid, attachment_id, context=context)
attachment_id._handle_pingen_document()
return attachment_id
def write(self, cr, uid, ids, vals, context=None):
res = super(ir_attachment, self).write(cr, uid, ids, vals, context=context)
def write(self, vals):
res = super(IrAttachment, self).write(vals)
if 'send_to_pingen' in vals:
for attachment_id in ids:
self._handle_pingen_document(cr, uid, attachment_id, context=context)
for attachment in self:
attachment._handle_pingen_document()
return res
def _decoded_content(self, cr, uid, attachment, context=None):
def _decoded_content(self):
""" Returns the decoded content of an attachment (stored or url)
Returns None if the type is 'url' and the url is not reachable.
"""
decoded_document = None
if attachment.type == 'binary':
decoded_document = base64.decodestring(attachment.datas)
elif attachment.type == 'url':
response = requests.get(attachment.url)
if self.type == 'binary':
decoded_document = base64.b64decode(self.datas)
elif self.type == 'url':
response = requests.get(self.url)
if response.ok:
decoded_document = requests.content
else:
raise Exception(
'The type of attachment %s is not handled' % attachment.type)
raise UserError(
_('The type of attachment %s is not handled')
% self.type)
return decoded_document

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<odoo>
<data>
<record id="view_attachment_form" model="ir.ui.view">
<field name="name">ir.attachment.pingen.view</field>
@@ -8,14 +8,14 @@
<field name="type">form</field>
<field name="inherit_id" ref="base.view_attachment_form"/>
<field name="arch" type="xml">
<page string="Notes" position="before">
<page string="Pingen.com">
<xpath expr="//group/group[2]" position="after">
<group string="Pingen info" groups="base.group_no_one">
<field name="send_to_pingen"/>
<field name="pingen_send" attrs="{'required': [('send_to_pingen', '=', True)]}"/>
<field name="pingen_speed" attrs="{'required': [('pingen_send', '=', True)]}"/>
<field name="pingen_color" />
</page>
</page>
</group>
</xpath>
</field>
</record>
@@ -28,4 +28,4 @@
src_model="ir.attachment"/>
</data>
</openerp>
</odoo>

View File

@@ -1,29 +1,14 @@
# -*- 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
# Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import requests
import logging
import urlparse
import json
import pytz
import base64
from datetime import datetime
from requests.packages.urllib3.filepost import encode_multipart_formdata
@@ -34,7 +19,7 @@ POST_SENDING_STATUS = {
100: 'Ready/Pending',
101: 'Processing',
102: 'Waiting for confirmation',
200: 'Sent',
1: 'Sent',
300: 'Some error occured and object wasn\'t sent',
400: 'Sending cancelled',
}
@@ -90,14 +75,9 @@ class Pingen(object):
""" Build a requests session """
if self._session is not None:
return self._session
self._session = requests.Session(
params={'token': self._token},
# with safe_mode, requests catch errors and
# returns a blank response with an error
config={'safe_mode': True},
# verify = False required for staging environment
# because the SSL certificate is wrong
verify=not self.staging)
self._session = requests.Session()
self._session.params = {'token': self._token}
self._session.verify = not self.staging
return self._session
def __enter__(self):
@@ -121,22 +101,36 @@ class Pingen(object):
:param str endpoint: endpoint to call
:param kwargs: additional arguments forwarded to the requests method
"""
complete_url = urlparse.urljoin(self.url, endpoint)
p_url = urlparse.urljoin(self.url, endpoint)
if endpoint == 'document/get':
complete_url = '{}{}{}{}{}'.format(p_url,
'/id/',
kwargs['params']['id'],
'/token/',
self._token)
else:
complete_url = '{}{}{}'.format(p_url,
'/token/',
self._token)
response = method(complete_url, **kwargs)
if not response.ok:
raise ConnectionError(
"%s: %s" % (response.json['errorcode'],
response.json['errormessage']))
"%s: %s" % (response.json()['errorcode'],
response.json()['errormessage']))
if response.json['error']:
if response.json()['error']:
raise APIError(
"%s: %s" % (response.json['errorcode'], response.json['errormessage']))
"%s: %s" % (response.json()['errorcode'],
response.json()['errormessage']))
return response
def push_document(self, filename, filestream, send=None, speed=None, color=None):
def push_document(self, filename, filestream,
send=None, speed=None, color=None):
""" Upload a document to pingen.com and eventually ask to send it
:param str filename: name of the file to push
@@ -174,7 +168,7 @@ class Pingen(object):
headers={'Content-Type': content_type},
data=multipart)
rjson = response.json
rjson = response.json()
document_id = rjson['id']
if rjson.get('send'):
@@ -203,7 +197,7 @@ class Pingen(object):
params={'id': document_id},
data={'data': json.dumps(data)})
return response.json['id']
return response.json()['id']
def post_infos(self, post_id):
""" Return the information of a post
@@ -213,10 +207,10 @@ class Pingen(object):
"""
response = self._send(
self.session.get,
'post/get',
'document/get',
params={'id': post_id})
return response.json['item']
return response.json()['item']
@staticmethod
def is_posted(post_infos):
@@ -224,4 +218,4 @@ class Pingen(object):
:param dict post_infos: post infos returned by `post_infos`
"""
return post_infos['status'] == 200
return post_infos['status'] == 1

View File

@@ -12,7 +12,7 @@
<field eval="False" name="doall"/>
<field name="model">pingen.document</field>
<field name="function">_push_and_send_to_pingen_cron</field>
<field name="args">(None,)</field>
<field name="args">()</field>
</record>
<record forcecreate="True" id="ir_cron_update_pingen" model="ir.cron">
@@ -25,7 +25,7 @@
<field eval="False" name="doall"/>
<field name="model">pingen.document</field>
<field name="function">_update_post_infos_cron</field>
<field name="args">(None,)</field>
<field name="args">()</field>
</record>
</data>

View File

@@ -1,39 +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 <http://www.gnu.org/licenses/>.
#
##############################################################################
# Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging
from cStringIO import StringIO
from contextlib import closing
from openerp.osv import osv, orm, fields
from openerp.tools.translate import _
from openerp import pooler, tools
from .pingen import APIError, ConnectionError, POST_SENDING_STATUS, \
pingen_datetime_to_utc
# from contextlib import closing
from odoo import models, fields, _
from odoo.exceptions import UserError
import odoo
from .pingen import APIError, ConnectionError, \
POST_SENDING_STATUS, pingen_datetime_to_utc
_logger = logging.getLogger(__name__)
class pingen_document(orm.Model):
class PingenDocument(models.Model):
""" A pingen document is the state of the synchronization of
an attachment with pingen.com
@@ -44,46 +28,38 @@ class pingen_document(orm.Model):
_name = 'pingen.document'
_inherits = {'ir.attachment': 'attachment_id'}
_columns = {
'attachment_id': fields.many2one(
'ir.attachment', 'Document',
required=True, readonly=True,
ondelete='cascade'),
'state': fields.selection(
[('pending', 'Pending'),
('pushed', 'Pushed'),
('sendcenter', 'In Sendcenter'),
('sent', 'Sent'),
('error', 'Connection Error'),
('pingen_error', 'Pingen Error'),
('canceled', 'Canceled')],
string='State', readonly=True, required=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', readonly=True,
help="ID of the document in the Pingen Documents"),
'post_id': fields.integer(
'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 = {
'state': 'pending',
}
attachment_id = fields.Many2one(
'ir.attachment', 'Document',
required=True, readonly=True,
ondelete='cascade')
state = fields.Selection(
[('pending', 'Pending'),
('pushed', 'Pushed'),
('sendcenter', 'In Sendcenter'),
('sent', 'Sent'),
('error', 'Connection Error'),
('pingen_error', 'Pingen Error'),
('canceled', 'Canceled')],
string='State', readonly=True,
required=True, default='pending')
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', readonly=True,
help="ID of the document in the Pingen Documents")
post_id = fields.Integer(
'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)
_sql_constraints = [
('pingen_document_attachment_uniq',
@@ -91,43 +67,34 @@ class pingen_document(orm.Model):
'Only one Pingen document is allowed per attachment.'),
]
def _get_pingen_session(self, cr, uid, context=None):
def _get_pingen_session(self):
""" Returns a pingen session for a user """
company = self.pool.get('res.users').browse(
cr, uid, uid, context=context).company_id
return self.pool.get('res.company')._pingen(cr, uid, company, context=context)
return self.company_id._pingen()
def _push_to_pingen(self, cr, uid, document, pingen=None, context=None):
def _push_to_pingen(self, pingen=None):
""" Push a document to pingen.com
:param Pingen pingen: optional pingen object to reuse session
"""
attachment_obj = self.pool.get('ir.attachment')
decoded_document = attachment_obj._decoded_content(
cr, uid, document.attachment_id, context=context)
decoded_document = self.attachment_id._decoded_content()
if pingen is None:
pingen = self._get_pingen_session(cr, uid, context=context)
pingen = self._get_pingen_session()
try:
doc_id, post_id, infos = pingen.push_document(
document.datas_fname,
self.datas_fname,
StringIO(decoded_document),
document.pingen_send,
document.pingen_speed,
document.pingen_color)
self.pingen_send,
self.pingen_speed,
self.pingen_color)
except ConnectionError:
_logger.exception(
'Connection Error when pushing Pingen Document %s to %s.' %
(document.id, pingen.url))
(self.id, pingen.url))
raise
except APIError:
_logger.error(
'API Error when pushing Pingen Document %s to %s.' %
(document.id, pingen.url))
(self.id, pingen.url))
raise
error = False
state = 'pushed'
if post_id:
@@ -135,291 +102,254 @@ class pingen_document(orm.Model):
elif infos['requirement_failure']:
state = 'pingen_error'
error = _('The document does not meet the Pingen requirements.')
push_date = pingen_datetime_to_utc(infos['date'])
document.write(
self.write(
{'last_error_message': error,
'state': state,
'push_date': push_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT),
'push_date': fields.Datetime.to_string(push_date),
'pingen_id': doc_id,
'post_id': post_id},
context=context)
_logger.info('Pingen Document %s: pushed to %s' % (document.id, pingen.url))
'post_id': post_id},)
_logger.info(
'Pingen Document %s: pushed to %s' % (self.id, pingen.url))
def push_to_pingen(self, cr, uid, ids, context=None):
def push_to_pingen(self):
""" Push a document to pingen.com
Convert errors to osv.except_osv to be handled by the client.
Wrapper method for multiple ids (when triggered from button for
instance) for public interface.
"""
assert len(ids) == 1, "Only 1 id is allowed"
with self._get_pingen_session(cr, uid, context=context) as session:
for document in self.browse(cr, uid, ids, context=context):
try:
self._push_to_pingen(
cr, uid, document, pingen=session, context=context)
except ConnectionError as e:
raise osv.except_osv(
_('Pingen Connection Error'),
_('Connection Error when asking for sending the document %s to Pingen') % document.name)
except APIError as e:
raise osv.except_osv(
_('Pingen Error'),
_('Error when asking Pingen to send the document %s: '
'\n%s') % (document.name, e))
except:
_logger.exception(
'Unexcepted Error when updating the status of pingen.document %s: ' %
document.id)
raise osv.except_osv(
_('Error'),
_('Unexcepted Error when updating the status of Document %s') % document.name)
self.ensure_one()
for document in self:
try:
session = self._get_pingen_session()
document._push_to_pingen(pingen=session)
except ConnectionError as e:
raise UserError(
_('Connection Error when asking for ' +
'sending the document %s to Pingen')
% document.name)
except APIError as e:
raise UserError(
_('Error when asking Pingen to send the document %s: '
'\n%s') % (document.name, e))
except Exception as e:
_logger.exception(
'Unexcepted Error when updating ' +
'the status of pingen.document %s: ' %
document.id)
raise UserError(
_('Unexcepted Error when updating the ' +
'status of Document %s') % document.name)
return True
def _push_and_send_to_pingen_cron(self, cr, uid, ids, context=None):
def _push_and_send_to_pingen_cron(self):
""" Push a document to pingen.com
Intended to be used in a cron.
Commit after each record
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'])],
limit=100,
context=context)
with closing(pooler.get_db(cr.dbname).cursor()) as loc_cr, \
self._get_pingen_session(cr, uid, context=context) as session:
for document in self.browse(loc_cr, uid, ids, context=context):
if document.state == 'error':
self._resolve_error(loc_cr, uid, document, context=context)
document.refresh()
try:
if document.state == 'pending':
self._push_to_pingen(
loc_cr, uid, document, pingen=session, context=context)
elif document.state == 'pushed':
self._ask_pingen_send(
loc_cr, uid, document, pingen=session, context=context)
except ConnectionError as e:
document.write({'last_error_message': e,
'state': 'error'},
context=context)
except APIError as e:
document.write({'last_error_message': e,
'state': 'pingen_error'},
context=context)
except:
_logger.error('Unexcepted error in pingen cron')
loc_cr.rollback()
raise
else:
loc_cr.commit()
with odoo.api.Environment.manage():
with odoo.registry(self.env.cr.dbname).cursor() as new_cr:
new_env = odoo.api.Environment(
new_cr, self.env.uid, self.env.context)
# Instead of raising, store the error in the pingen.document
self = self.with_env(new_env)
not_sent_docs = self.search([('state', '!=', 'sent')])
for document in not_sent_docs:
session = document._get_pingen_session()
if document.state == 'error':
document._resolve_error()
document.refresh()
try:
if document.state == 'pending':
document._push_to_pingen(pingen=session)
elif document.state == 'pushed':
document._ask_pingen_send(pingen=session)
except ConnectionError as e:
document.write({'last_error_message': e,
'state': 'error'})
except APIError as e:
document.write({'last_error_message': e,
'state': 'pingen_error'})
except BaseException as e:
_logger.error('Unexcepted error in pingen cron')
return True
def _resolve_error(self, cr, uid, document, context=None):
def _resolve_error(self):
""" A document as resolved, put in the correct state """
if document.post_id:
if self.post_id:
state = 'sendcenter'
elif document.pingen_id:
elif self.pingen_id:
state = 'pushed'
else:
state = 'pending'
document.write({'state': state}, context=context)
self.write({'state': state})
def resolve_error(self, cr, uid, ids, context=None):
def resolve_error(self):
""" A document as resolved, put in the correct state """
for document in self.browse(cr, uid, ids, context=context):
self._resolve_error(cr, uid, document, context=context)
for document in self:
document._resolve_error()
return True
def _ask_pingen_send(self, cr, uid, document, pingen, context=None):
def _ask_pingen_send(self, pingen):
""" For a document already pushed to pingen, ask to send it.
:param Pingen pingen: pingen object to reuse
"""
# sending has been explicitely asked so we change the option
# for consistency
if not document.pingen_send:
document.write({'pingen_send': True}, context=context)
if not self.pingen_send:
self.write({'pingen_send': True})
try:
post_id = pingen.send_document(
document.pingen_id,
document.pingen_speed,
document.pingen_color)
self.pingen_id,
self.pingen_speed,
self.pingen_color)
except ConnectionError:
_logger.exception('Connection Error when asking for sending Pingen Document %s to %s.' %
(document.id, pingen.url))
_logger.exception(
'Connection Error when asking for sending ' +
'Pingen Document %s to %s.' %
(self.id, pingen.url))
raise
except APIError:
_logger.exception('API Error when asking for sending Pingen Document %s to %s.' %
(document.id, pingen.url))
_logger.exception(
'API Error when asking for sending ' +
'Pingen Document %s to %s.' %
(self.id, pingen.url))
raise
document.write(
self.write(
{'last_error_message': False,
'state': 'sendcenter',
'post_id': post_id},
context=context)
_logger.info('Pingen Document %s: asked for sending to %s' % (document.id, pingen.url))
'post_id': post_id})
_logger.info(
'Pingen Document %s: asked for' +
'sending to %s' % (self.id, pingen.url))
return True
def ask_pingen_send(self, cr, uid, ids, context=None):
def ask_pingen_send(self):
""" For a document already pushed to pingen, ask to send it.
Wrapper method for multiple ids (when triggered from button for
instance) for public interface.
"""
assert len(ids) == 1, "Only 1 id is allowed"
with self._get_pingen_session(cr, uid, context=context) as session:
for document in self.browse(cr, uid, ids, context=context):
try:
self._ask_pingen_send(cr, uid, document, pingen=session, context=context)
except ConnectionError as e:
raise osv.except_osv(
_('Pingen Connection Error'),
_('Connection Error when asking for '
'sending the document %s to Pingen') % document.name)
self.ensure_one()
for document in self:
try:
session = document._get_pingen_session()
document._ask_pingen_send(pingen=session)
except ConnectionError as e:
raise UserError(
_('Connection Error when asking for '
'sending the document %s to Pingen') % document.name)
except APIError as e:
raise osv.except_osv(
_('Pingen Error'),
_('Error when asking Pingen to send the document %s: '
'\n%s') % (document.name, e))
except APIError as e:
raise UserError(
_('Error when asking Pingen to send the document %s: '
'\n%s') % (document.name, e))
except:
_logger.exception(
'Unexcepted Error when updating the status of pingen.document %s: ' %
document.id)
raise osv.except_osv(
_('Error'),
_('Unexcepted Error when updating the status of Document %s') % document.name)
except BaseException as e:
_logger.exception(
'Unexcepted Error when updating the ' +
'status of pingen.document %s: ' %
document.id)
raise UserError(
_('Unexcepted Error when updating the ' +
'status of Document %s') % document.name)
return True
def _update_post_infos(self, cr, uid, document, pingen, context=None):
""" Update the informations from pingen of a document in the Sendcenter
def _update_post_infos(self, pingen):
""" Update the informations from
pingen of a document in the Sendcenter
:param Pingen pingen: pingen object to reuse
"""
if not document.post_id:
if not self.pingen_id:
return
try:
post_infos = pingen.post_infos(document.post_id)
post_infos = pingen.post_infos(self.pingen_id)
except ConnectionError:
_logger.exception(
'Connection Error when asking for '
'sending Pingen Document %s to %s.' %
(document.id, pingen.url))
(self.id, pingen.url))
raise
except APIError:
_logger.exception(
'API Error when asking for sending Pingen Document %s to %s.' %
(document.id, pingen.url))
'API Error when asking for sending ' +
'Pingen Document %s to %s.' %
(self.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)
# currency_ids = self.env['res.currency'].search(
# [('name', '=', post_infos['currency'])])
country = self.env['res.country'].search(
[('code', '=', post_infos['country'])])
send_date = pingen_datetime_to_utc(post_infos['date'])
vals = {
'post_status': POST_SENDING_STATUS[post_infos['status']],
'cost': post_infos['cost'],
'currency_id': currency_ids[0] if currency_ids else False,
# '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': send_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT),
'pages': post_infos['pages'],
'country_id': country.id if country else False,
'send_date': fields.Datetime.to_string(send_date),
# 'pages': post_infos['pages'],
'last_error_message': False,
}
if pingen.is_posted(post_infos):
vals['state'] = 'sent'
self.write(vals)
_logger.info('Pingen Document %s: status updated' % self.id)
document.write(vals, context=context)
_logger.info('Pingen Document %s: status updated' % document.id)
def _update_post_infos_cron(self, cr, uid, ids, context=None):
""" Update the informations from pingen of a document in the Sendcenter
def _update_post_infos_cron(self):
""" Update the informations from pingen of a
document in the Sendcenter
Intended to be used in a cron.
Commit after each record
Do not raise errors, only skip the update of the record.
"""
if not ids:
ids = self.search(
cr, uid,
[('state', '=', 'sendcenter')],
context=context)
with closing(pooler.get_db(cr.dbname).cursor()) as loc_cr, \
self._get_pingen_session(cr, uid, context=context) as session:
for document in self.browse(loc_cr, uid, ids, context=context):
try:
self._update_post_infos(
loc_cr, uid, document, pingen=session, context=context)
except (ConnectionError, APIError):
# will be retried the next time
# In any case, the error has been logged by _update_post_infos
loc_cr.rollback()
except:
_logger.error('Unexcepted error in pingen cron')
loc_cr.rollback()
raise
else:
loc_cr.commit()
Do not raise errors, only skip the update of the record."""
with odoo.api.Environment.manage():
with odoo.registry(self.env.cr.dbname).cursor() as new_cr:
new_env = odoo.api.Environment(
new_cr, self.env.uid, self.env.context)
# Instead of raising, store the error in the pingen.document
self = self.with_env(new_env)
pushed_docs = self.search([('state', '!=', 'sent')])
for document in pushed_docs:
session = document._get_pingen_session()
try:
document._update_post_infos(pingen=session)
except (ConnectionError, APIError):
# will be retried the next time
# In any case, the error has been
# logged by _update_post_infos
pass
except BaseException as e:
_logger.error('Unexcepted error in pingen cron: %', e)
raise
return True
def update_post_infos(self, cr, uid, ids, context=None):
def update_post_infos(self):
""" 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.
"""
assert len(ids) == 1, "Only 1 id is allowed"
with self._get_pingen_session(cr, uid, context=context) as session:
for document in self.browse(cr, uid, ids, context=context):
try:
self._update_post_infos(
cr, uid, document, pingen=session, context=context)
except ConnectionError as e:
raise osv.except_osv(
_('Pingen Connection Error'),
_('Connection Error when updating the status of Document %s'
' from Pingen') % document.name)
except APIError as e:
raise osv.except_osv(
_('Pingen Error'),
_('Error when updating the status of Document %s from Pingen: '
'\n%s') % (document.name, e))
except:
_logger.exception(
'Unexcepted Error when updating the status of pingen.document %s: ' %
document.id)
raise osv.except_osv(
_('Error'),
_('Unexcepted Error when updating the status of Document %s') % document.name)
self.ensure_one()
for document in self:
try:
session = document._get_pingen_session()
document._update_post_infos(pingen=session)
except ConnectionError as e:
raise UserError(
_('Connection Error when updating ' +
'the status of Document %s'
' from Pingen') % document.name)
except APIError as e:
raise UserError(
_('Error when updating the status ' +
'of Document %s from Pingen: '
'\n%s') % (document.name, e))
except BaseException as e:
_logger.exception(
'Unexcepted Error when updating ' +
'the status of pingen.document %s: ' %
document.id)
raise UserError(
_('Unexcepted Error when updating ' +
'the status of Document %s') % document.name)
return True

View File

@@ -155,7 +155,7 @@
<field name="search_view_id" ref="view_pingen_document_search"/>
</record>
<menuitem action="action_pingen_document" id="menu_pingen_document" parent="base.next_id_4"/>
<menuitem action="action_pingen_document" id="menu_pingen_document" parent="base.next_id_9"/>
</data>
</openerp>

View File

@@ -1,42 +1,20 @@
# -*- 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
# Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from openerp.osv import orm, fields
from openerp.osv.orm import browse_record
from odoo import models, fields
from .pingen import Pingen
class res_company(orm.Model):
class ResCompany(models.Model):
_inherit = 'res.company'
_columns = {
'pingen_token': fields.char('Pingen Token', size=32),
'pingen_staging': fields.boolean('Pingen Staging')
}
pingen_token = fields.Char('Pingen Token', size=32)
pingen_staging = fields.Boolean('Pingen Staging')
def _pingen(self, cr, uid, company, context=None):
def _pingen(self):
""" Return a Pingen instance to work on """
assert isinstance(company, (int, long, browse_record)), \
"one id or browse_record expected"
if not isinstance(company, browse_record):
company = self.browse(cr, uid, company, context=context)
return Pingen(company.pingen_token, staging=company.pingen_staging)
self.ensure_one()
return Pingen(self.pingen_token, staging=self.pingen_staging)

View File

@@ -1,20 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record model="ir.ui.view" id="view_company_inherit_form">
<field name="name">res.company.form.inherit</field>
<field name="model">res.company</field>
<field name="type">form</field>
<field name="inherit_id" ref="base.view_company_form"/>
<field name="arch" type="xml">
<page string="Configuration" position="inside">
<separator string="Pingen.com" colspan="4"/>
<field name="pingen_token" groups="base.group_system"/>
<field name="pingen_staging" groups="base.group_system"/>
</page>
<xpath expr="//page[1]" position="after">
<page name="pingenconf" string="Pingen Information">
<group name="ping_gr_1">
<field name="pingen_token" groups="base.group_system"/>
<field name="pingen_staging" groups="base.group_system"/>
</group>
</page>
</xpath>
</field>
</record>
</data>
</openerp>