[IMP] pingen: pre-commit stuff

This commit is contained in:
Anna Janiszewska
2023-01-26 12:55:11 +01:00
parent eb4a9f3b5a
commit 0529340243
17 changed files with 471 additions and 385 deletions

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Author: Guewen Baconnier # Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA # Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

View File

@@ -1,30 +1,29 @@
# -*- coding: utf-8 -*-
# Author: Guewen Baconnier # Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA # Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{ {
'name': 'pingen.com integration', "name": "pingen.com integration",
'version': '10.0.1.0.0', "version": "10.0.1.0.0",
'author': "Camptocamp,Odoo Community Association (OCA)", "author": "Camptocamp,Odoo Community Association (OCA)",
'maintainer': 'Camptocamp', "maintainer": "Camptocamp",
'license': 'AGPL-3', "license": "AGPL-3",
'category': 'Reporting', "category": "Reporting",
'complexity': 'easy', "complexity": "easy",
'depends': ['base_setup'], "depends": ["base_setup"],
'external_dependencies': { "external_dependencies": {
'python': ['requests'], "python": ["requests"],
}, },
'website': 'http://www.camptocamp.com', "website": "https://github.com/OCA/report-print-send",
'data': [ "data": [
'views/ir_attachment_view.xml', "views/ir_attachment_view.xml",
'views/pingen_document_view.xml', "views/pingen_document_view.xml",
'data/pingen_data.xml', "data/pingen_data.xml",
'views/base_config_settings.xml', "views/base_config_settings.xml",
'security/ir.model.access.csv', "security/ir.model.access.csv",
], ],
'tests': [], "tests": [],
'installable': True, "installable": True,
'auto_install': False, "auto_install": False,
'application': True, "application": True,
} }

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Camptocamp SA # Copyright 2018 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import ir_attachment from . import ir_attachment

View File

@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Camptocamp SA # Copyright 2018 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models from odoo import fields, models
class BaseConfigSettings(models.TransientModel): class BaseConfigSettings(models.TransientModel):
_inherit = 'base.config.settings' _inherit = "base.config.settings"
pingen_token = fields.Char(related='company_id.pingen_token') pingen_token = fields.Char(related="company_id.pingen_token")
pingen_staging = fields.Boolean(related='company_id.pingen_staging') pingen_staging = fields.Boolean(related="company_id.pingen_staging")

View File

@@ -1,37 +1,40 @@
# -*- coding: utf-8 -*-
# Author: Guewen Baconnier # Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA # Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import requests
import base64 import base64
from odoo import models, fields, _, api import requests
from odoo import _, api, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
class IrAttachment(models.Model): class IrAttachment(models.Model):
_inherit = 'ir.attachment' _inherit = "ir.attachment"
send_to_pingen = fields.Boolean("Send to Pingen.com") send_to_pingen = fields.Boolean("Send to Pingen.com")
pingen_document_ids = fields.One2many( pingen_document_ids = fields.One2many(
'pingen.document', 'attachment_id', "pingen.document", "attachment_id", string="Pingen Document", readonly=True
string='Pingen Document', readonly=True) )
pingen_send = fields.Boolean( pingen_send = fields.Boolean(
'Send', help="Defines if a document is merely uploaded or also sent", "Send",
default=True) help="Defines if a document is merely uploaded or also sent",
default=True,
)
pingen_speed = fields.Selection( pingen_speed = fields.Selection(
[('1', 'Priority'), ('2', 'Economy')], [("1", "Priority"), ("2", "Economy")],
'Speed', default='2', "Speed",
help='Defines the sending speed if the document is automatically sent') default="2",
pingen_color = fields.Selection([('0', 'B/W'), ('1', 'Color')], help="Defines the sending speed if the document is automatically sent",
'Type of print', )
default='0') pingen_color = fields.Selection(
[("0", "B/W"), ("1", "Color")], "Type of print", default="0"
)
def _prepare_pingen_document_vals(self): def _prepare_pingen_document_vals(self):
return {'attachment_id': self.id, return {"attachment_id": self.id, "config": "created from attachment"}
'config': 'created from attachment'}
def _handle_pingen_document(self): def _handle_pingen_document(self):
"""Reponsible of the related ``pingen.document`` """Reponsible of the related ``pingen.document``
@@ -48,35 +51,37 @@ class IrAttachment(models.Model):
* If it has already been pushed to pingen.com, raises * If it has already been pushed to pingen.com, raises
an `osv.except_osv` exception an `osv.except_osv` exception
""" """
pingen_document_obj = self.env['pingen.document'] pingen_document_obj = self.env["pingen.document"]
document = self.pingen_document_ids[0] if \ document = self.pingen_document_ids[0] if self.pingen_document_ids else None
self.pingen_document_ids else None
if self.send_to_pingen: if self.send_to_pingen:
if document: if document:
document.write({'state': 'pending'}) document.write({"state": "pending"})
else: else:
pingen_document_obj.create( pingen_document_obj.create(self._prepare_pingen_document_vals())
self._prepare_pingen_document_vals())
else: else:
if document: if document:
if document.state == 'pushed': if document.state == "pushed":
raise UserError( raise UserError(
_('Error. The attachment %s is ' _(
'already pushed to pingen.com.') % self.name) "Error. The attachment %s is "
document.write({'state': 'canceled'}) "already pushed to pingen.com."
)
% self.name
)
document.write({"state": "canceled"})
return return
@api.model @api.model
def create(self, vals): def create(self, vals):
attachment = super(IrAttachment, self).create(vals) attachment = super(IrAttachment, self).create(vals)
if 'send_to_pingen' in vals: if "send_to_pingen" in vals:
attachment._handle_pingen_document() attachment._handle_pingen_document()
return attachment return attachment
@api.multi @api.multi
def write(self, vals): def write(self, vals):
res = super(IrAttachment, self).write(vals) res = super(IrAttachment, self).write(vals)
if 'send_to_pingen' in vals: if "send_to_pingen" in vals:
for attachment in self: for attachment in self:
attachment._handle_pingen_document() attachment._handle_pingen_document()
return res return res
@@ -86,14 +91,12 @@ class IrAttachment(models.Model):
Returns None if the type is 'url' and the url is not reachable. Returns None if the type is 'url' and the url is not reachable.
""" """
decoded_document = None decoded_document = None
if self.type == 'binary': if self.type == "binary":
decoded_document = base64.b64decode(self.datas) decoded_document = base64.b64decode(self.datas)
elif self.type == 'url': elif self.type == "url":
response = requests.get(self.url) response = requests.get(self.url)
if response.ok: if response.ok:
decoded_document = requests.content decoded_document = requests.content
else: else:
raise UserError( raise UserError(_("The type of attachment %s is not handled") % self.type)
_('The type of attachment %s is not handled')
% self.type)
return decoded_document return decoded_document

View File

@@ -1,31 +1,30 @@
# -*- coding: utf-8 -*-
# Author: Guewen Baconnier # Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA # Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import requests
import logging
import urlparse
import json import json
import pytz import logging
from datetime import datetime from datetime import datetime
import pytz
import requests
import urlparse
from requests.packages.urllib3.filepost import encode_multipart_formdata from requests.packages.urllib3.filepost import encode_multipart_formdata
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
POST_SENDING_STATUS = { POST_SENDING_STATUS = {
100: 'Ready/Pending', 100: "Ready/Pending",
101: 'Processing', 101: "Processing",
102: 'Waiting for confirmation', 102: "Waiting for confirmation",
1: 'Sent', 1: "Sent",
300: 'Some error occured and object wasn\'t sent', 300: "Some error occured and object wasn't sent",
400: 'Sending cancelled', 400: "Sending cancelled",
} }
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' # this is the format used by pingen API DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" # this is the format used by pingen API
TZ = pytz.timezone('Europe/Zurich') # this is the timezone of the pingen API TZ = pytz.timezone("Europe/Zurich") # this is the timezone of the pingen API
def pingen_datetime_to_utc(dt): def pingen_datetime_to_utc(dt):
@@ -62,8 +61,8 @@ class Pingen(object):
@property @property
def url(self): def url(self):
if self.staging: if self.staging:
return 'https://stage-api.pingen.com' return "https://stage-api.pingen.com"
return 'https://api.pingen.com' return "https://api.pingen.com"
@property @property
def session(self): def session(self):
@@ -71,7 +70,7 @@ class Pingen(object):
if self._session is not None: if self._session is not None:
return self._session return self._session
self._session = requests.Session() self._session = requests.Session()
self._session.params = {'token': self._token} self._session.params = {"token": self._token}
self._session.verify = not self.staging self._session.verify = not self.staging
return self._session return self._session
@@ -99,28 +98,24 @@ class Pingen(object):
p_url = urlparse.urljoin(self.url, endpoint) p_url = urlparse.urljoin(self.url, endpoint)
if endpoint == 'document/get': if endpoint == "document/get":
complete_url = '{}{}{}{}{}'.format(p_url, complete_url = "{}{}{}{}{}".format(
'/id/', p_url, "/id/", kwargs["params"]["id"], "/token/", self._token
kwargs['params']['id'], )
'/token/',
self._token)
else: else:
complete_url = '{}{}{}'.format(p_url, complete_url = "{}{}{}".format(p_url, "/token/", self._token)
'/token/',
self._token)
response = method(complete_url, **kwargs) response = method(complete_url, **kwargs)
if response.json()['error']: if response.json()["error"]:
raise APIError( raise APIError(
"%s: %s" % (response.json()['errorcode'], "%s: %s"
response.json()['errormessage'])) % (response.json()["errorcode"], response.json()["errormessage"])
)
return response return response
def push_document(self, filename, filestream, def push_document(self, filename, filestream, send=None, speed=None, color=None):
send=None, speed=None, color=None):
"""Upload a document to pingen.com and eventually ask to send it """Upload a document to pingen.com and eventually ask to send it
:param str filename: name of the file to push :param str filename: name of the file to push
@@ -135,9 +130,9 @@ class Pingen(object):
3. dict of the created item on pingen (details) 3. dict of the created item on pingen (details)
""" """
data = { data = {
'send': send, "send": send,
'speed': speed, "speed": speed,
'color': color, "color": color,
} }
# we cannot use the `files` param alongside # we cannot use the `files` param alongside
@@ -146,25 +141,26 @@ class Pingen(object):
# the entire body and send it to `data` # the entire body and send it to `data`
# https://github.com/kennethreitz/requests/issues/950 # https://github.com/kennethreitz/requests/issues/950
formdata = { formdata = {
'file': (filename, filestream.read()), "file": (filename, filestream.read()),
'data': json.dumps(data), "data": json.dumps(data),
} }
multipart, content_type = encode_multipart_formdata(formdata) multipart, content_type = encode_multipart_formdata(formdata)
response = self._send( response = self._send(
self.session.post, self.session.post,
'document/upload', "document/upload",
headers={'Content-Type': content_type}, headers={"Content-Type": content_type},
data=multipart) data=multipart,
)
rjson = response.json() rjson = response.json()
document_id = rjson['id'] document_id = rjson["id"]
if rjson.get('send'): if rjson.get("send"):
# confusing name but send_id is the posted id # confusing name but send_id is the posted id
posted_id = rjson['send'][0]['send_id'] posted_id = rjson["send"][0]["send_id"]
item = rjson['item'] item = rjson["item"]
return document_id, posted_id, item return document_id, posted_id, item
@@ -178,16 +174,17 @@ class Pingen(object):
:return: id of the post on pingen.com :return: id of the post on pingen.com
""" """
data = { data = {
'speed': speed, "speed": speed,
'color': color, "color": color,
} }
response = self._send( response = self._send(
self.session.post, self.session.post,
'document/send', "document/send",
params={'id': document_id}, params={"id": document_id},
data={'data': json.dumps(data)}) data={"data": json.dumps(data)},
)
return response.json()['id'] return response.json()["id"]
def post_infos(self, post_id): def post_infos(self, post_id):
"""Return the information of a post """Return the information of a post
@@ -195,12 +192,9 @@ class Pingen(object):
:param int post_id: id of the document to send :param int post_id: id of the document to send
:return: dict of infos of the post :return: dict of infos of the post
""" """
response = self._send( response = self._send(self.session.get, "document/get", params={"id": post_id})
self.session.get,
'document/get',
params={'id': post_id})
return response.json()['item'] return response.json()["item"]
@staticmethod @staticmethod
def is_posted(post_infos): def is_posted(post_infos):
@@ -208,4 +202,4 @@ class Pingen(object):
:param dict post_infos: post infos returned by `post_infos` :param dict post_infos: post infos returned by `post_infos`
""" """
return post_infos['status'] == 1 return post_infos["status"] == 1

View File

@@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-
# Author: Guewen Baconnier # Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA # Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging import logging
from requests.exceptions import ConnectionError
from cStringIO import StringIO from cStringIO import StringIO
from requests.exceptions import ConnectionError
import odoo import odoo
from odoo import models, fields, _ from odoo import _, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
from .pingen import APIError, pingen_datetime_to_utc, POST_SENDING_STATUS
from .pingen import POST_SENDING_STATUS, APIError, pingen_datetime_to_utc
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -24,46 +24,54 @@ class PingenDocument(models.Model):
It also serves as a queue of documents to push to pingen.com It also serves as a queue of documents to push to pingen.com
""" """
_name = 'pingen.document' _name = "pingen.document"
_inherits = {'ir.attachment': 'attachment_id'} _inherits = {"ir.attachment": "attachment_id"}
attachment_id = fields.Many2one( attachment_id = fields.Many2one(
'ir.attachment', 'Document', "ir.attachment", "Document", required=True, readonly=True, ondelete="cascade"
required=True, readonly=True, )
ondelete='cascade')
state = fields.Selection( state = fields.Selection(
[('pending', 'Pending'), [
('pushed', 'Pushed'), ("pending", "Pending"),
('sendcenter', 'In Sendcenter'), ("pushed", "Pushed"),
('sent', 'Sent'), ("sendcenter", "In Sendcenter"),
('error', 'Connection Error'), ("sent", "Sent"),
('pingen_error', 'Pingen Error'), ("error", "Connection Error"),
('canceled', 'Canceled')], ("pingen_error", "Pingen Error"),
string='State', readonly=True, ("canceled", "Canceled"),
required=True, default='pending') ],
push_date = fields.Datetime('Push Date', readonly=True) 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 # for `error` and `pingen_error` states when we push
last_error_message = fields.Text('Error Message', readonly=True) last_error_message = fields.Text("Error Message", readonly=True)
# pingen IDs # pingen IDs
pingen_id = fields.Integer( pingen_id = fields.Integer(
'Pingen ID', readonly=True, "Pingen ID", readonly=True, help="ID of the document in the Pingen Documents"
help="ID of the document in the Pingen Documents") )
post_id = fields.Integer( post_id = fields.Integer(
'Pingen Post ID', readonly=True, "Pingen Post ID",
help="ID of the document in the Pingen Sendcenter") readonly=True,
help="ID of the document in the Pingen Sendcenter",
)
# sendcenter infos # sendcenter infos
post_status = fields.Char('Post Status', size=128, readonly=True) post_status = fields.Char("Post Status", size=128, readonly=True)
parsed_address = fields.Text('Parsed Address', readonly=True) parsed_address = fields.Text("Parsed Address", readonly=True)
cost = fields.Float('Cost', readonly=True) cost = fields.Float("Cost", readonly=True)
currency_id = fields.Many2one('res.currency', 'Currency', readonly=True) currency_id = fields.Many2one("res.currency", "Currency", readonly=True)
country_id = fields.Many2one('res.country', 'Country', readonly=True) country_id = fields.Many2one("res.country", "Country", readonly=True)
send_date = fields.Datetime('Date of sending', readonly=True) send_date = fields.Datetime("Date of sending", readonly=True)
pages = fields.Integer('Pages', readonly=True) pages = fields.Integer("Pages", readonly=True)
_sql_constraints = [ _sql_constraints = [
('pingen_document_attachment_uniq', (
'unique (attachment_id)', "pingen_document_attachment_uniq",
'Only one Pingen document is allowed per attachment.'), "unique (attachment_id)",
"Only one Pingen document is allowed per attachment.",
),
] ]
def _get_pingen_session(self): def _get_pingen_session(self):
@@ -83,33 +91,38 @@ class PingenDocument(models.Model):
StringIO(decoded_document), StringIO(decoded_document),
self.pingen_send, self.pingen_send,
self.pingen_speed, self.pingen_speed,
self.pingen_color) self.pingen_color,
)
except ConnectionError: except ConnectionError:
_logger.exception( _logger.exception(
'Connection Error when pushing Pingen Document %s to %s.' % "Connection Error when pushing Pingen Document %s to %s."
(self.id, pingen.url)) % (self.id, pingen.url)
)
raise raise
except APIError: except APIError:
_logger.error( _logger.error(
'API Error when pushing Pingen Document %s to %s.' % "API Error when pushing Pingen Document %s to %s."
(self.id, pingen.url)) % (self.id, pingen.url)
)
raise raise
error = False error = False
state = 'pushed' state = "pushed"
if post_id: if post_id:
state = 'sendcenter' state = "sendcenter"
elif infos['requirement_failure']: elif infos["requirement_failure"]:
state = 'pingen_error' state = "pingen_error"
error = _('The document does not meet the Pingen requirements.') error = _("The document does not meet the Pingen requirements.")
push_date = pingen_datetime_to_utc(infos['date']) push_date = pingen_datetime_to_utc(infos["date"])
self.write( self.write(
{'last_error_message': error, {
'state': state, "last_error_message": error,
'push_date': fields.Datetime.to_string(push_date), "state": state,
'pingen_id': doc_id, "push_date": fields.Datetime.to_string(push_date),
'post_id': post_id},) "pingen_id": doc_id,
_logger.info( "post_id": post_id,
'Pingen Document %s: pushed to %s' % (self.id, pingen.url)) },
)
_logger.info("Pingen Document %s: pushed to %s" % (self.id, pingen.url))
def push_to_pingen(self): def push_to_pingen(self):
"""Push a document to pingen.com """Push a document to pingen.com
@@ -123,28 +136,38 @@ class PingenDocument(models.Model):
try: try:
session = self._get_pingen_session() session = self._get_pingen_session()
self._push_to_pingen(pingen=session) self._push_to_pingen(pingen=session)
except ConnectionError as e: except ConnectionError:
state = 'error' state = "error"
error_msg = _('Connection Error when asking for ' error_msg = (
'sending the document %s to Pingen') % self.name _(
"Connection Error when asking for "
"sending the document %s to Pingen"
)
% self.name
)
except APIError as e: except APIError as e:
state = 'pingen_error' state = "pingen_error"
error_msg = _('Error when asking Pingen to send the document %s: ' error_msg = _(
'\n%s') % (self.name, e) "Error when asking Pingen to send the document %s: " "\n%s"
except Exception as e: ) % (self.name, e)
except Exception:
_logger.exception( _logger.exception(
'Unexpected Error when updating the status of pingen.document ' "Unexpected Error when updating the status of pingen.document "
'%s: ' % self.id) "%s: " % self.id
error_msg = _('Unexpected Error when updating the ' )
'status of Document %s') % self.name error_msg = (
_("Unexpected Error when updating the " "status of Document %s")
% self.name
)
finally: finally:
if error_msg: if error_msg:
vals = {'last_error_message': error_msg} vals = {"last_error_message": error_msg}
if state: if state:
vals.update({'state': state}) vals.update({"state": state})
with odoo.registry(self.env.cr.dbname).cursor() as new_cr: with odoo.registry(self.env.cr.dbname).cursor() as new_cr:
new_env = odoo.api.Environment( new_env = odoo.api.Environment(
new_cr, self.env.uid, self.env.context) new_cr, self.env.uid, self.env.context
)
self.with_env(new_env).write(vals) self.with_env(new_env).write(vals)
raise UserError(error_msg) raise UserError(error_msg)
@@ -158,40 +181,39 @@ class PingenDocument(models.Model):
""" """
with odoo.api.Environment.manage(): with odoo.api.Environment.manage():
with odoo.registry(self.env.cr.dbname).cursor() as new_cr: with odoo.registry(self.env.cr.dbname).cursor() as new_cr:
new_env = odoo.api.Environment( new_env = odoo.api.Environment(new_cr, self.env.uid, self.env.context)
new_cr, self.env.uid, self.env.context)
# Instead of raising, store the error in the pingen.document # Instead of raising, store the error in the pingen.document
self = self.with_env(new_env) self = self.with_env(new_env)
not_sent_docs = self.search([('state', '!=', 'sent')]) not_sent_docs = self.search([("state", "!=", "sent")])
for document in not_sent_docs: for document in not_sent_docs:
session = document._get_pingen_session() session = document._get_pingen_session()
if document.state == 'error': if document.state == "error":
document._resolve_error() document._resolve_error()
document.refresh() document.refresh()
try: try:
if document.state == 'pending': if document.state == "pending":
document._push_to_pingen(pingen=session) document._push_to_pingen(pingen=session)
elif document.state == 'pushed': elif document.state == "pushed":
document._ask_pingen_send(pingen=session) document._ask_pingen_send(pingen=session)
except ConnectionError as e: except ConnectionError as e:
document.write({'last_error_message': e, document.write({"last_error_message": e, "state": "error"})
'state': 'error'})
except APIError as e: except APIError as e:
document.write({'last_error_message': e, document.write(
'state': 'pingen_error'}) {"last_error_message": e, "state": "pingen_error"}
except BaseException as e: )
_logger.error('Unexpected error in pingen cron') except BaseException:
_logger.error("Unexpected error in pingen cron")
return True return True
def _resolve_error(self): def _resolve_error(self):
"""A document as resolved, put in the correct state""" """A document as resolved, put in the correct state"""
if self.post_id: if self.post_id:
state = 'sendcenter' state = "sendcenter"
elif self.pingen_id: elif self.pingen_id:
state = 'pushed' state = "pushed"
else: else:
state = 'pending' state = "pending"
self.write({'state': state}) self.write({"state": state})
def resolve_error(self): def resolve_error(self):
"""A document as resolved, put in the correct state""" """A document as resolved, put in the correct state"""
@@ -207,29 +229,29 @@ class PingenDocument(models.Model):
# for consistency # for consistency
if not self.pingen_send: if not self.pingen_send:
self.write({'pingen_send': True}) self.write({"pingen_send": True})
try: try:
post_id = pingen.send_document( post_id = pingen.send_document(
self.pingen_id, self.pingen_id, self.pingen_speed, self.pingen_color
self.pingen_speed, )
self.pingen_color)
except ConnectionError: except ConnectionError:
_logger.exception( _logger.exception(
'Connection Error when asking for sending Pingen Document %s ' "Connection Error when asking for sending Pingen Document %s "
'to %s.' % (self.id, pingen.url)) "to %s." % (self.id, pingen.url)
)
raise raise
except APIError: except APIError:
_logger.exception( _logger.exception(
'API Error when asking for sending Pingen Document %s to %s.' % "API Error when asking for sending Pingen Document %s to %s."
(self.id, pingen.url)) % (self.id, pingen.url)
)
raise raise
self.write( self.write(
{'last_error_message': False, {"last_error_message": False, "state": "sendcenter", "post_id": post_id}
'state': 'sendcenter', )
'post_id': post_id})
_logger.info( _logger.info(
'Pingen Document %s: asked for sending to %s' % ( "Pingen Document %s: asked for sending to %s" % (self.id, pingen.url)
self.id, pingen.url)) )
return True return True
def ask_pingen_send(self): def ask_pingen_send(self):
@@ -241,23 +263,30 @@ class PingenDocument(models.Model):
try: try:
session = self._get_pingen_session() session = self._get_pingen_session()
self._ask_pingen_send(pingen=session) self._ask_pingen_send(pingen=session)
except ConnectionError as e: except ConnectionError:
raise UserError( raise UserError(
_('Connection Error when asking for ' _(
'sending the document %s to Pingen') % self.name) "Connection Error when asking for "
"sending the document %s to Pingen"
)
% self.name
)
except APIError as e: except APIError as e:
raise UserError( raise UserError(
_('Error when asking Pingen to send the document %s: ' _("Error when asking Pingen to send the document %s: " "\n%s")
'\n%s') % (self.name, e)) % (self.name, e)
)
except BaseException as e: except BaseException:
_logger.exception( _logger.exception(
'Unexpected Error when updating the status ' "Unexpected Error when updating the status "
'of pingen.document %s: ' % self.id) "of pingen.document %s: " % self.id
)
raise UserError( raise UserError(
_('Unexpected Error when updating the status ' _("Unexpected Error when updating the status " "of Document %s")
'of Document %s') % self.name) % self.name
)
return True return True
def _update_post_infos(self, pingen): def _update_post_infos(self, pingen):
@@ -271,30 +300,30 @@ class PingenDocument(models.Model):
post_infos = pingen.post_infos(self.pingen_id) post_infos = pingen.post_infos(self.pingen_id)
except ConnectionError: except ConnectionError:
_logger.exception( _logger.exception(
'Connection Error when asking for ' "Connection Error when asking for "
'sending Pingen Document %s to %s.' % "sending Pingen Document %s to %s." % (self.id, pingen.url)
(self.id, pingen.url)) )
raise raise
except APIError: except APIError:
_logger.exception( _logger.exception(
'API Error when asking for sending Pingen Document %s to %s.' % "API Error when asking for sending Pingen Document %s to %s."
(self.id, pingen.url)) % (self.id, pingen.url)
)
raise raise
country = self.env['res.country'].search( country = self.env["res.country"].search([("code", "=", post_infos["country"])])
[('code', '=', post_infos['country'])]) send_date = pingen_datetime_to_utc(post_infos["date"])
send_date = pingen_datetime_to_utc(post_infos['date'])
vals = { vals = {
'post_status': POST_SENDING_STATUS[post_infos['status']], "post_status": POST_SENDING_STATUS[post_infos["status"]],
'parsed_address': post_infos['address'], "parsed_address": post_infos["address"],
'country_id': country.id, "country_id": country.id,
'send_date': fields.Datetime.to_string(send_date), "send_date": fields.Datetime.to_string(send_date),
'pages': post_infos['pages'], "pages": post_infos["pages"],
'last_error_message': False, "last_error_message": False,
} }
if pingen.is_posted(post_infos): if pingen.is_posted(post_infos):
vals['state'] = 'sent' vals["state"] = "sent"
self.write(vals) self.write(vals)
_logger.info('Pingen Document %s: status updated' % self.id) _logger.info("Pingen Document %s: status updated" % self.id)
def _update_post_infos_cron(self): def _update_post_infos_cron(self):
"""Update the informations from pingen of a """Update the informations from pingen of a
@@ -304,11 +333,10 @@ class PingenDocument(models.Model):
Do not raise errors, only skip the update of the record.""" Do not raise errors, only skip the update of the record."""
with odoo.api.Environment.manage(): with odoo.api.Environment.manage():
with odoo.registry(self.env.cr.dbname).cursor() as new_cr: with odoo.registry(self.env.cr.dbname).cursor() as new_cr:
new_env = odoo.api.Environment( new_env = odoo.api.Environment(new_cr, self.env.uid, self.env.context)
new_cr, self.env.uid, self.env.context)
# Instead of raising, store the error in the pingen.document # Instead of raising, store the error in the pingen.document
self = self.with_env(new_env) self = self.with_env(new_env)
pushed_docs = self.search([('state', '!=', 'sent')]) pushed_docs = self.search([("state", "!=", "sent")])
for document in pushed_docs: for document in pushed_docs:
session = document._get_pingen_session() session = document._get_pingen_session()
try: try:
@@ -319,7 +347,7 @@ class PingenDocument(models.Model):
# logged by _update_post_infos # logged by _update_post_infos
pass pass
except BaseException as e: except BaseException as e:
_logger.error('Unexcepted error in pingen cron: %', e) _logger.error("Unexcepted error in pingen cron: %", e)
raise raise
return True return True
@@ -332,19 +360,26 @@ class PingenDocument(models.Model):
try: try:
session = self._get_pingen_session() session = self._get_pingen_session()
self._update_post_infos(pingen=session) self._update_post_infos(pingen=session)
except ConnectionError as e: except ConnectionError:
raise UserError( raise UserError(
_('Connection Error when updating the status ' _(
'of Document %s from Pingen') % self.name) "Connection Error when updating the status "
"of Document %s from Pingen"
)
% self.name
)
except APIError as e: except APIError as e:
raise UserError( raise UserError(
_('Error when updating the status of Document %s from ' _("Error when updating the status of Document %s from " "Pingen: \n%s")
'Pingen: \n%s') % (self.name, e)) % (self.name, e)
except BaseException as e: )
except BaseException:
_logger.exception( _logger.exception(
'Unexpected Error when updating the status ' "Unexpected Error when updating the status "
'of pingen.document %s: ' % self.id) "of pingen.document %s: " % self.id
)
raise UserError( raise UserError(
_('Unexpected Error when updating the status ' _("Unexpected Error when updating the status " "of Document %s")
'of Document %s') % self.name) % self.name
)
return True return True

View File

@@ -1,18 +1,18 @@
# -*- coding: utf-8 -*-
# Author: Guewen Baconnier # Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA # Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import models, fields from odoo import fields, models
from .pingen import Pingen from .pingen import Pingen
class ResCompany(models.Model): class ResCompany(models.Model):
_inherit = 'res.company' _inherit = "res.company"
pingen_token = fields.Char('Pingen Token', size=32) pingen_token = fields.Char("Pingen Token", size=32)
pingen_staging = fields.Boolean('Pingen Staging') pingen_staging = fields.Boolean("Pingen Staging")
def _pingen(self): def _pingen(self):
"""Return a Pingen instance to work on""" """Return a Pingen instance to work on"""

View File

@@ -9,8 +9,14 @@
<xpath expr="//group/group[2]" position="after"> <xpath expr="//group/group[2]" position="after">
<group string="Pingen info" groups="base.group_no_one"> <group string="Pingen info" groups="base.group_no_one">
<field name="send_to_pingen" /> <field name="send_to_pingen" />
<field name="pingen_send" attrs="{'required': [('send_to_pingen', '=', True)]}"/> <field
<field name="pingen_speed" attrs="{'required': [('pingen_send', '=', True)]}"/> name="pingen_send"
attrs="{'required': [('send_to_pingen', '=', True)]}"
/>
<field
name="pingen_speed"
attrs="{'required': [('pingen_send', '=', True)]}"
/>
<field name="pingen_color" /> <field name="pingen_color" />
</group> </group>
</xpath> </xpath>
@@ -23,5 +29,6 @@
name="Pingen Document" name="Pingen Document"
groups="" groups=""
res_model="pingen.document" res_model="pingen.document"
src_model="ir.attachment"/> src_model="ir.attachment"
/>
</odoo> </odoo>

View File

@@ -24,23 +24,40 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Pingen Document"> <form string="Pingen Document">
<header> <header>
<field name="state" widget="statusbar" <field
name="state"
widget="statusbar"
statusbar_visible="pending,pushed,sent" statusbar_visible="pending,pushed,sent"
statusbar_colors='{"error":"red","pingen_error":"red","canceled":"grey","pushed":"blue","sent":"green"}'/> statusbar_colors='{"error":"red","pingen_error":"red","canceled":"grey","pushed":"blue","sent":"green"}'
/>
</header> </header>
<group colspan="4" col="6"> <group colspan="4" col="6">
<field name="name" readonly="True" /> <field name="name" readonly="True" />
<field name="type" readonly="True" /> <field name="type" readonly="True" />
<field name="company_id" readonly="True" groups="base.group_multi_company" widget="selection"/> <field
name="company_id"
readonly="True"
groups="base.group_multi_company"
widget="selection"
/>
</group> </group>
<notebook colspan="4"> <notebook colspan="4">
<page string="Pingen.com"> <page string="Pingen.com">
<separator string="Options" colspan="4" /> <separator string="Options" colspan="4" />
<newline /> <newline />
<group col="2" colspan="2"> <group col="2" colspan="2">
<field name="pingen_send" attrs="{'readonly': [('state', 'in', ['sendcenter', 'sent'])]}"/> <field
<field name="pingen_speed" attrs="{'readonly': [('state', 'in', ['sendcenter', 'sent'])], 'required': [('pingen_send', '=', True)]}"/> name="pingen_send"
<field name="pingen_color" attrs="{'readonly': [('state', 'in', ['sendcenter', 'sent'])]}"/> attrs="{'readonly': [('state', 'in', ['sendcenter', 'sent'])]}"
/>
<field
name="pingen_speed"
attrs="{'readonly': [('state', 'in', ['sendcenter', 'sent'])], 'required': [('pingen_send', '=', True)]}"
/>
<field
name="pingen_color"
attrs="{'readonly': [('state', 'in', ['sendcenter', 'sent'])]}"
/>
</group> </group>
<separator string="Dates" colspan="4" /> <separator string="Dates" colspan="4" />
@@ -49,7 +66,10 @@
<field name="push_date" /> <field name="push_date" />
</group> </group>
<group colspan="4" attrs="{'invisible': [('last_error_message', '=', False)]}"> <group
colspan="4"
attrs="{'invisible': [('last_error_message', '=', False)]}"
>
<separator string="Errors" colspan="4" /> <separator string="Errors" colspan="4" />
<newline /> <newline />
<group col="2" colspan="2"> <group col="2" colspan="2">
@@ -57,7 +77,10 @@
</group> </group>
</group> </group>
<group colspan="4" attrs="{'invisible': [('state', 'not in', ['sendcenter', 'sent'])]}"> <group
colspan="4"
attrs="{'invisible': [('state', 'not in', ['sendcenter', 'sent'])]}"
>
<separator string="Sendcenter" colspan="4" /> <separator string="Sendcenter" colspan="4" />
<newline /> <newline />
<group col="4" colspan="2"> <group col="4" colspan="2">
@@ -77,18 +100,34 @@
<separator string="Actions" colspan="4" /> <separator string="Actions" colspan="4" />
<newline /> <newline />
<group col="2" colspan="2"> <group col="2" colspan="2">
<button name="push_to_pingen" type="object" <button
name="push_to_pingen"
type="object"
states="pending" states="pending"
string="Push to pingen.com" icon="fa-upload"/> string="Push to pingen.com"
<button name="ask_pingen_send" type="object" icon="fa-upload"
/>
<button
name="ask_pingen_send"
type="object"
states="pushed" states="pushed"
string="Ask pingen.com to send the document" icon="fa-share"/> string="Ask pingen.com to send the document"
<button name="resolve_error" type="object" icon="fa-share"
/>
<button
name="resolve_error"
type="object"
states="error,pingen_error" states="error,pingen_error"
string="Errors resolved" icon="fa-repeat"/> string="Errors resolved"
<button name="update_post_infos" type="object" icon="fa-repeat"
/>
<button
name="update_post_infos"
type="object"
states="sendcenter" states="sendcenter"
string="Update the letter's informations" icon="fa-refresh"/> string="Update the letter's informations"
icon="fa-refresh"
/>
</group> </group>
</page> </page>
<page string="Attachment"> <page string="Attachment">
@@ -119,27 +158,29 @@
<field name="type">search</field> <field name="type">search</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="Pingen Document"> <search string="Pingen Document">
<filter icon="terp-project" <filter
icon="terp-project"
string="Pending" string="Pending"
domain="[('state','=','pending')]"/> domain="[('state','=','pending')]"
<filter icon="terp-stage" />
string="Pushed" <filter icon="terp-stage" string="Pushed" domain="[('state','=','pushed')]" />
domain="[('state','=','pushed')]"/> <filter
<filter icon="gtk-print" icon="gtk-print"
string="In Sendcenter" string="In Sendcenter"
domain="[('state','=','sendcenter')]"/> domain="[('state','=','sendcenter')]"
<filter icon="kanban-apply" />
string="Sent" <filter icon="kanban-apply" string="Sent" domain="[('state','=','sent')]" />
domain="[('state','=','sent')]"/> <filter icon="kanban-stop" string="Error" domain="[('state','=','error')]" />
<filter icon="kanban-stop" <filter
string="Error" icon="STOCK_NO"
domain="[('state','=','error')]"/>
<filter icon="STOCK_NO"
string="Pingen Error" string="Pingen Error"
domain="[('state','=','pingen_error')]"/> domain="[('state','=','pingen_error')]"
<filter icon="terp-dialog-close" />
<filter
icon="terp-dialog-close"
string="Canceled" string="Canceled"
domain="[('state','=','canceled')]"/> domain="[('state','=','canceled')]"
/>
<separator orientation="vertical" /> <separator orientation="vertical" />
<field name="attachment_id" /> <field name="attachment_id" />
</search> </search>

View File

@@ -1,2 +1,3 @@
# generated from manifests external_dependencies # generated from manifests external_dependencies
pycups pycups
requests

View File

@@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@@ -0,0 +1 @@
../../../../pingen

6
setup/pingen/setup.py Normal file
View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)