[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
# Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

View File

@@ -1,30 +1,29 @@
# -*- coding: utf-8 -*-
# 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': '10.0.1.0.0',
'author': "Camptocamp,Odoo Community Association (OCA)",
'maintainer': 'Camptocamp',
'license': 'AGPL-3',
'category': 'Reporting',
'complexity': 'easy',
'depends': ['base_setup'],
'external_dependencies': {
'python': ['requests'],
},
'website': 'http://www.camptocamp.com',
'data': [
'views/ir_attachment_view.xml',
'views/pingen_document_view.xml',
'data/pingen_data.xml',
'views/base_config_settings.xml',
'security/ir.model.access.csv',
],
'tests': [],
'installable': True,
'auto_install': False,
'application': True,
"name": "pingen.com integration",
"version": "10.0.1.0.0",
"author": "Camptocamp,Odoo Community Association (OCA)",
"maintainer": "Camptocamp",
"license": "AGPL-3",
"category": "Reporting",
"complexity": "easy",
"depends": ["base_setup"],
"external_dependencies": {
"python": ["requests"],
},
"website": "https://github.com/OCA/report-print-send",
"data": [
"views/ir_attachment_view.xml",
"views/pingen_document_view.xml",
"data/pingen_data.xml",
"views/base_config_settings.xml",
"security/ir.model.access.csv",
],
"tests": [],
"installable": True,
"auto_install": False,
"application": True,
}

View File

@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record forcecreate="True" id="ir_cron_push_pingen" model="ir.cron">
<field name="name">Run Pingen Document Push</field>
<field eval="True" name="active"/>
<field name="user_id" ref="base.user_root"/>
<field eval="True" name="active" />
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field eval="False" name="doall"/>
<field eval="False" name="doall" />
<field name="model">pingen.document</field>
<field name="function">_push_and_send_to_pingen_cron</field>
<field name="args">()</field>
@@ -17,12 +17,12 @@
<record forcecreate="True" id="ir_cron_update_pingen" model="ir.cron">
<field name="name">Run Pingen Document Update</field>
<field eval="True" name="active"/>
<field name="user_id" ref="base.user_root"/>
<field eval="True" name="active" />
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field eval="False" name="doall"/>
<field eval="False" name="doall" />
<field name="model">pingen.document</field>
<field name="function">_update_post_infos_cron</field>
<field name="args">()</field>

View File

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

View File

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

View File

@@ -1,40 +1,43 @@
# -*- coding: utf-8 -*-
# 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 odoo import models, fields, _, api
import requests
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class IrAttachment(models.Model):
_inherit = 'ir.attachment'
_inherit = "ir.attachment"
send_to_pingen = fields.Boolean("Send to Pingen.com")
pingen_document_ids = fields.One2many(
'pingen.document', 'attachment_id',
string='Pingen Document', readonly=True)
"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)
"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')
[("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"
)
def _prepare_pingen_document_vals(self):
return {'attachment_id': self.id,
'config': 'created from attachment'}
return {"attachment_id": self.id, "config": "created from attachment"}
def _handle_pingen_document(self):
""" Reponsible of the related ``pingen.document``
"""Reponsible of the related ``pingen.document``
when the ``send_to_pingen``
field is modified.
Only one pingen document can be created per attachment.
@@ -48,52 +51,52 @@ class IrAttachment(models.Model):
* If it has already been pushed to pingen.com, raises
an `osv.except_osv` exception
"""
pingen_document_obj = self.env['pingen.document']
document = self.pingen_document_ids[0] if \
self.pingen_document_ids else None
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'})
document.write({"state": "pending"})
else:
pingen_document_obj.create(
self._prepare_pingen_document_vals())
pingen_document_obj.create(self._prepare_pingen_document_vals())
else:
if document:
if document.state == 'pushed':
if document.state == "pushed":
raise UserError(
_('Error. The attachment %s is '
'already pushed to pingen.com.') % self.name)
document.write({'state': 'canceled'})
_(
"Error. The attachment %s is "
"already pushed to pingen.com."
)
% self.name
)
document.write({"state": "canceled"})
return
@api.model
def create(self, vals):
attachment = super(IrAttachment, self).create(vals)
if 'send_to_pingen' in vals:
if "send_to_pingen" in vals:
attachment._handle_pingen_document()
return attachment
@api.multi
def write(self, vals):
res = super(IrAttachment, self).write(vals)
if 'send_to_pingen' in vals:
if "send_to_pingen" in vals:
for attachment in self:
attachment._handle_pingen_document()
return res
def _decoded_content(self):
""" Returns the decoded content of an attachment (stored or url)
"""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 self.type == 'binary':
if self.type == "binary":
decoded_document = base64.b64decode(self.datas)
elif self.type == 'url':
elif self.type == "url":
response = requests.get(self.url)
if response.ok:
decoded_document = requests.content
else:
raise UserError(
_('The type of attachment %s is not handled')
% self.type)
raise UserError(_("The type of attachment %s is not handled") % self.type)
return decoded_document

View File

@@ -1,35 +1,34 @@
# -*- coding: utf-8 -*-
# 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 logging
from datetime import datetime
import pytz
import requests
import urlparse
from requests.packages.urllib3.filepost import encode_multipart_formdata
_logger = logging.getLogger(__name__)
POST_SENDING_STATUS = {
100: 'Ready/Pending',
101: 'Processing',
102: 'Waiting for confirmation',
1: 'Sent',
300: 'Some error occured and object wasn\'t sent',
400: 'Sending cancelled',
100: "Ready/Pending",
101: "Processing",
102: "Waiting for confirmation",
1: "Sent",
300: "Some error occured and object wasn't sent",
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):
""" Convert a date/time used by pingen.com to UTC timezone
"""Convert a date/time used by pingen.com to UTC timezone
:param dt: pingen date/time as string (as received from the API)
to convert to UTC
@@ -51,7 +50,7 @@ class APIError(PingenException):
class Pingen(object):
""" Interface to the pingen.com API """
"""Interface to the pingen.com API"""
def __init__(self, token, staging=True):
self._token = token
@@ -62,16 +61,16 @@ class Pingen(object):
@property
def url(self):
if self.staging:
return 'https://stage-api.pingen.com'
return 'https://api.pingen.com'
return "https://stage-api.pingen.com"
return "https://api.pingen.com"
@property
def session(self):
""" Build a requests session """
"""Build a requests session"""
if self._session is not None:
return self._session
self._session = requests.Session()
self._session.params = {'token': self._token}
self._session.params = {"token": self._token}
self._session.verify = not self.staging
return self._session
@@ -82,12 +81,12 @@ class Pingen(object):
self.close()
def close(self):
"""Dispose of any internal state. """
"""Dispose of any internal state."""
if self._session:
self._session.close()
def _send(self, method, endpoint, **kwargs):
""" Send a request to the pingen API using requests
"""Send a request to the pingen API using requests
Add necessary boilerplate to call pingen.com API
(authentication, configuration, ...)
@@ -99,29 +98,25 @@ class Pingen(object):
p_url = urlparse.urljoin(self.url, endpoint)
if endpoint == 'document/get':
complete_url = '{}{}{}{}{}'.format(p_url,
'/id/',
kwargs['params']['id'],
'/token/',
self._token)
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)
complete_url = "{}{}{}".format(p_url, "/token/", self._token)
response = method(complete_url, **kwargs)
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):
""" Upload a document to pingen.com and eventually ask to send it
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
:param StringIO filestream: file to push
@@ -135,10 +130,10 @@ class Pingen(object):
3. dict of the created item on pingen (details)
"""
data = {
'send': send,
'speed': speed,
'color': color,
}
"send": send,
"speed": speed,
"color": color,
}
# we cannot use the `files` param alongside
# with the `datas`param when data is a
@@ -146,30 +141,31 @@ class Pingen(object):
# the entire body and send it to `data`
# https://github.com/kennethreitz/requests/issues/950
formdata = {
'file': (filename, filestream.read()),
'data': json.dumps(data),
}
"file": (filename, filestream.read()),
"data": json.dumps(data),
}
multipart, content_type = encode_multipart_formdata(formdata)
response = self._send(
self.session.post,
'document/upload',
headers={'Content-Type': content_type},
data=multipart)
"document/upload",
headers={"Content-Type": content_type},
data=multipart,
)
rjson = response.json()
document_id = rjson['id']
if rjson.get('send'):
document_id = rjson["id"]
if rjson.get("send"):
# confusing name but send_id is the posted id
posted_id = rjson['send'][0]['send_id']
item = rjson['item']
posted_id = rjson["send"][0]["send_id"]
item = rjson["item"]
return document_id, posted_id, item
def send_document(self, document_id, speed=None, color=None):
""" Send a uploaded document to pingen.com
"""Send a uploaded document to pingen.com
:param int document_id: id of the document to send
:param int/str speed: sending speed of the document if it is send
@@ -178,34 +174,32 @@ class Pingen(object):
:return: id of the post on pingen.com
"""
data = {
'speed': speed,
'color': color,
}
"speed": speed,
"color": color,
}
response = self._send(
self.session.post,
'document/send',
params={'id': document_id},
data={'data': json.dumps(data)})
"document/send",
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
"""Return the information of a post
:param int post_id: id of the document to send
:return: dict of infos of the post
"""
response = self._send(
self.session.get,
'document/get',
params={'id': post_id})
response = self._send(self.session.get, "document/get", params={"id": post_id})
return response.json()['item']
return response.json()["item"]
@staticmethod
def is_posted(post_infos):
""" return True if the post has been sent
"""return True if the post has been sent
:param dict post_infos: post infos returned by `post_infos`
"""
return post_infos['status'] == 1
return post_infos["status"] == 1

View File

@@ -1,77 +1,85 @@
# -*- coding: utf-8 -*-
# Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging
from requests.exceptions import ConnectionError
from cStringIO import StringIO
from requests.exceptions import ConnectionError
import odoo
from odoo import models, fields, _
from odoo import _, fields, models
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__)
class PingenDocument(models.Model):
""" A pingen document is the state of the synchronization of
"""A pingen document 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.document'
_inherits = {'ir.attachment': 'attachment_id'}
_name = "pingen.document"
_inherits = {"ir.attachment": "attachment_id"}
attachment_id = fields.Many2one(
'ir.attachment', 'Document',
required=True, readonly=True,
ondelete='cascade')
"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)
[
("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)
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")
"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")
"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)
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',
'unique (attachment_id)',
'Only one Pingen document is allowed per attachment.'),
(
"pingen_document_attachment_uniq",
"unique (attachment_id)",
"Only one Pingen document is allowed per attachment.",
),
]
def _get_pingen_session(self):
""" Returns a pingen session for a user """
"""Returns a pingen session for a user"""
return self.company_id._pingen()
def _push_to_pingen(self, pingen=None):
""" Push a document to pingen.com
"""Push a document to pingen.com
:param Pingen pingen: optional pingen object to reuse session
"""
decoded_document = self.attachment_id._decoded_content()
@@ -83,36 +91,41 @@ class PingenDocument(models.Model):
StringIO(decoded_document),
self.pingen_send,
self.pingen_speed,
self.pingen_color)
self.pingen_color,
)
except ConnectionError:
_logger.exception(
'Connection Error when pushing Pingen Document %s to %s.' %
(self.id, pingen.url))
"Connection Error when pushing Pingen Document %s to %s."
% (self.id, pingen.url)
)
raise
except APIError:
_logger.error(
'API Error when pushing Pingen Document %s to %s.' %
(self.id, pingen.url))
"API Error when pushing Pingen Document %s to %s."
% (self.id, pingen.url)
)
raise
error = False
state = 'pushed'
state = "pushed"
if post_id:
state = 'sendcenter'
elif infos['requirement_failure']:
state = 'pingen_error'
error = _('The document does not meet the Pingen requirements.')
push_date = pingen_datetime_to_utc(infos['date'])
state = "sendcenter"
elif infos["requirement_failure"]:
state = "pingen_error"
error = _("The document does not meet the Pingen requirements.")
push_date = pingen_datetime_to_utc(infos["date"])
self.write(
{'last_error_message': error,
'state': state,
'push_date': fields.Datetime.to_string(push_date),
'pingen_id': doc_id,
'post_id': post_id},)
_logger.info(
'Pingen Document %s: pushed to %s' % (self.id, pingen.url))
{
"last_error_message": error,
"state": state,
"push_date": fields.Datetime.to_string(push_date),
"pingen_id": doc_id,
"post_id": post_id,
},
)
_logger.info("Pingen Document %s: pushed to %s" % (self.id, pingen.url))
def push_to_pingen(self):
""" Push a document to pingen.com
"""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.
@@ -123,117 +136,126 @@ class PingenDocument(models.Model):
try:
session = self._get_pingen_session()
self._push_to_pingen(pingen=session)
except ConnectionError as e:
state = 'error'
error_msg = _('Connection Error when asking for '
'sending the document %s to Pingen') % self.name
except ConnectionError:
state = "error"
error_msg = (
_(
"Connection Error when asking for "
"sending the document %s to Pingen"
)
% self.name
)
except APIError as e:
state = 'pingen_error'
error_msg = _('Error when asking Pingen to send the document %s: '
'\n%s') % (self.name, e)
except Exception as e:
state = "pingen_error"
error_msg = _(
"Error when asking Pingen to send the document %s: " "\n%s"
) % (self.name, e)
except Exception:
_logger.exception(
'Unexpected Error when updating the status of pingen.document '
'%s: ' % self.id)
error_msg = _('Unexpected Error when updating the '
'status of Document %s') % self.name
"Unexpected Error when updating the status of pingen.document "
"%s: " % self.id
)
error_msg = (
_("Unexpected Error when updating the " "status of Document %s")
% self.name
)
finally:
if error_msg:
vals = {'last_error_message': error_msg}
vals = {"last_error_message": error_msg}
if state:
vals.update({'state': state})
vals.update({"state": state})
with odoo.registry(self.env.cr.dbname).cursor() as new_cr:
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)
raise UserError(error_msg)
return True
def _push_and_send_to_pingen_cron(self):
""" Push a document to pingen.com
"""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
"""
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)
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')])
not_sent_docs = self.search([("state", "!=", "sent")])
for document in not_sent_docs:
session = document._get_pingen_session()
if document.state == 'error':
if document.state == "error":
document._resolve_error()
document.refresh()
try:
if document.state == 'pending':
if document.state == "pending":
document._push_to_pingen(pingen=session)
elif document.state == 'pushed':
elif document.state == "pushed":
document._ask_pingen_send(pingen=session)
except ConnectionError as e:
document.write({'last_error_message': e,
'state': 'error'})
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('Unexpected error in pingen cron')
document.write(
{"last_error_message": e, "state": "pingen_error"}
)
except BaseException:
_logger.error("Unexpected error in pingen cron")
return True
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:
state = 'sendcenter'
state = "sendcenter"
elif self.pingen_id:
state = 'pushed'
state = "pushed"
else:
state = 'pending'
self.write({'state': state})
state = "pending"
self.write({"state": state})
def resolve_error(self):
""" A document as resolved, put in the correct state """
"""A document as resolved, put in the correct state"""
for document in self:
document._resolve_error()
return True
def _ask_pingen_send(self, pingen):
""" For a document already pushed to pingen, ask to send it.
"""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 self.pingen_send:
self.write({'pingen_send': True})
self.write({"pingen_send": True})
try:
post_id = pingen.send_document(
self.pingen_id,
self.pingen_speed,
self.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.' % (self.id, pingen.url))
"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.' %
(self.id, pingen.url))
"API Error when asking for sending Pingen Document %s to %s."
% (self.id, pingen.url)
)
raise
self.write(
{'last_error_message': False,
'state': 'sendcenter',
'post_id': post_id})
{"last_error_message": False, "state": "sendcenter", "post_id": post_id}
)
_logger.info(
'Pingen Document %s: asked for sending to %s' % (
self.id, pingen.url))
"Pingen Document %s: asked for sending to %s" % (self.id, pingen.url)
)
return True
def ask_pingen_send(self):
""" For a document already pushed to pingen, ask to send it.
"""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.
"""
@@ -241,27 +263,34 @@ class PingenDocument(models.Model):
try:
session = self._get_pingen_session()
self._ask_pingen_send(pingen=session)
except ConnectionError as e:
except ConnectionError:
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:
raise UserError(
_('Error when asking Pingen to send the document %s: '
'\n%s') % (self.name, e))
_("Error when asking Pingen to send the document %s: " "\n%s")
% (self.name, e)
)
except BaseException as e:
except BaseException:
_logger.exception(
'Unexpected Error when updating the status '
'of pingen.document %s: ' % self.id)
"Unexpected Error when updating the status "
"of pingen.document %s: " % self.id
)
raise UserError(
_('Unexpected Error when updating the status '
'of Document %s') % self.name)
_("Unexpected Error when updating the status " "of Document %s")
% self.name
)
return True
def _update_post_infos(self, pingen):
""" Update the informations from
"""Update the informations from
pingen of a document in the Sendcenter
:param Pingen pingen: pingen object to reuse
"""
@@ -271,44 +300,43 @@ class PingenDocument(models.Model):
post_infos = pingen.post_infos(self.pingen_id)
except ConnectionError:
_logger.exception(
'Connection Error when asking for '
'sending Pingen Document %s to %s.' %
(self.id, pingen.url))
"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.' %
(self.id, pingen.url))
"API Error when asking for sending Pingen Document %s to %s."
% (self.id, pingen.url)
)
raise
country = self.env['res.country'].search(
[('code', '=', post_infos['country'])])
send_date = pingen_datetime_to_utc(post_infos['date'])
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']],
'parsed_address': post_infos['address'],
'country_id': country.id,
'send_date': fields.Datetime.to_string(send_date),
'pages': post_infos['pages'],
'last_error_message': False,
}
"post_status": POST_SENDING_STATUS[post_infos["status"]],
"parsed_address": post_infos["address"],
"country_id": country.id,
"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'
vals["state"] = "sent"
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):
""" Update the informations from pingen of a
"""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."""
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)
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')])
pushed_docs = self.search([("state", "!=", "sent")])
for document in pushed_docs:
session = document._get_pingen_session()
try:
@@ -319,12 +347,12 @@ class PingenDocument(models.Model):
# logged by _update_post_infos
pass
except BaseException as e:
_logger.error('Unexcepted error in pingen cron: %', e)
_logger.error("Unexcepted error in pingen cron: %", e)
raise
return True
def update_post_infos(self):
""" Update the informations from pingen of a document in the Sendcenter
"""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.
"""
@@ -332,19 +360,26 @@ class PingenDocument(models.Model):
try:
session = self._get_pingen_session()
self._update_post_infos(pingen=session)
except ConnectionError as e:
except ConnectionError:
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:
raise UserError(
_('Error when updating the status of Document %s from '
'Pingen: \n%s') % (self.name, e))
except BaseException as e:
_("Error when updating the status of Document %s from " "Pingen: \n%s")
% (self.name, e)
)
except BaseException:
_logger.exception(
'Unexpected Error when updating the status '
'of pingen.document %s: ' % self.id)
"Unexpected Error when updating the status "
"of pingen.document %s: " % self.id
)
raise UserError(
_('Unexpected Error when updating the status '
'of Document %s') % self.name)
_("Unexpected Error when updating the status " "of Document %s")
% self.name
)
return True

View File

@@ -1,20 +1,20 @@
# -*- coding: utf-8 -*-
# Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA
# 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
class ResCompany(models.Model):
_inherit = 'res.company'
_inherit = "res.company"
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):
""" Return a Pingen instance to work on """
"""Return a Pingen instance to work on"""
self.ensure_one()
return Pingen(self.pingen_token, staging=self.pingen_staging)

View File

@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="base_config_settings_inherit" model="ir.ui.view">
<field name="name">base.config.settings.inherit</field>
<field name="inherit_id" ref="base_setup.view_general_configuration"/>
<field name="inherit_id" ref="base_setup.view_general_configuration" />
<field name="model">base.config.settings</field>
<field name="arch" type="xml">
<group name="report" position="before">
<group string="Pingen Integration">
<field name="pingen_token" groups="base.group_system"/>
<field name="pingen_staging" groups="base.group_system"/>
<field name="pingen_token" groups="base.group_system" />
<field name="pingen_staging" groups="base.group_system" />
</group>
</group>
</field>

View File

@@ -1,16 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_attachment_form" model="ir.ui.view">
<field name="name">ir.attachment.pingen.view</field>
<field name="model">ir.attachment</field>
<field name="type">form</field>
<field name="inherit_id" ref="base.view_attachment_form"/>
<field name="inherit_id" ref="base.view_attachment_form" />
<field name="arch" type="xml">
<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="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" />
</group>
</xpath>
@@ -18,10 +24,11 @@
</record>
<act_window
context="{'search_default_attachment_id': [active_id], 'default_attachment_id': active_id}"
id="act_attachment_to_pingen_document"
name="Pingen Document"
groups=""
res_model="pingen.document"
src_model="ir.attachment"/>
context="{'search_default_attachment_id': [active_id], 'default_attachment_id': active_id}"
id="act_attachment_to_pingen_document"
name="Pingen Document"
groups=""
res_model="pingen.document"
src_model="ir.attachment"
/>
</odoo>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_pingen_document_tree" model="ir.ui.view">
@@ -7,12 +7,12 @@
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Pingen Document">
<field name="name"/>
<field name="datas_fname"/>
<field name="pingen_send"/>
<field name="pingen_speed"/>
<field name="pingen_color"/>
<field name="state"/>
<field name="name" />
<field name="datas_fname" />
<field name="pingen_send" />
<field name="pingen_speed" />
<field name="pingen_color" />
<field name="state" />
</tree>
</field>
</record>
@@ -24,88 +24,127 @@
<field name="arch" type="xml">
<form string="Pingen Document">
<header>
<field name="state" widget="statusbar"
statusbar_visible="pending,pushed,sent"
statusbar_colors='{"error":"red","pingen_error":"red","canceled":"grey","pushed":"blue","sent":"green"}'/>
<field
name="state"
widget="statusbar"
statusbar_visible="pending,pushed,sent"
statusbar_colors='{"error":"red","pingen_error":"red","canceled":"grey","pushed":"blue","sent":"green"}'
/>
</header>
<group colspan="4" col="6">
<field name="name" readonly="True"/>
<field name="type" readonly="True"/>
<field name="company_id" readonly="True" groups="base.group_multi_company" widget="selection"/>
<field name="name" readonly="True" />
<field name="type" readonly="True" />
<field
name="company_id"
readonly="True"
groups="base.group_multi_company"
widget="selection"
/>
</group>
<notebook colspan="4">
<page string="Pingen.com">
<separator string="Options" colspan="4"/>
<separator string="Options" colspan="4" />
<newline />
<group col="2" colspan="2">
<field name="pingen_send" 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'])]}"/>
<field
name="pingen_send"
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>
<separator string="Dates" colspan="4"/>
<separator string="Dates" colspan="4" />
<newline />
<group col="2" colspan="2">
<field name="push_date"/>
<field name="push_date" />
</group>
<group colspan="4" attrs="{'invisible': [('last_error_message', '=', False)]}">
<separator string="Errors" colspan="4"/>
<group
colspan="4"
attrs="{'invisible': [('last_error_message', '=', False)]}"
>
<separator string="Errors" colspan="4" />
<newline />
<group col="2" colspan="2">
<field nolabel="1" name="last_error_message"/>
<field nolabel="1" name="last_error_message" />
</group>
</group>
<group colspan="4" attrs="{'invisible': [('state', 'not in', ['sendcenter', 'sent'])]}">
<separator string="Sendcenter" colspan="4"/>
<group
colspan="4"
attrs="{'invisible': [('state', 'not in', ['sendcenter', 'sent'])]}"
>
<separator string="Sendcenter" colspan="4" />
<newline />
<group col="4" colspan="2">
<field colspan="4" name="post_status"/>
<field colspan="4" name="post_status" />
<group col="3" colspan="2">
<field name="cost"/>
<field colspan="1" nolabel="1" name="currency_id"/>
<field name="cost" />
<field colspan="1" nolabel="1" name="currency_id" />
</group>
<newline/>
<field name="parsed_address"/>
<field name="country_id"/>
<field name="send_date"/>
<field name="pages"/>
<newline />
<field name="parsed_address" />
<field name="country_id" />
<field name="send_date" />
<field name="pages" />
</group>
</group>
<separator string="Actions" colspan="4"/>
<separator string="Actions" colspan="4" />
<newline />
<group col="2" colspan="2">
<button name="push_to_pingen" type="object"
states="pending"
string="Push to pingen.com" icon="fa-upload"/>
<button name="ask_pingen_send" type="object"
states="pushed"
string="Ask pingen.com to send the document" icon="fa-share"/>
<button name="resolve_error" type="object"
states="error,pingen_error"
string="Errors resolved" icon="fa-repeat"/>
<button name="update_post_infos" type="object"
states="sendcenter"
string="Update the letter's informations" icon="fa-refresh"/>
<button
name="push_to_pingen"
type="object"
states="pending"
string="Push to pingen.com"
icon="fa-upload"
/>
<button
name="ask_pingen_send"
type="object"
states="pushed"
string="Ask pingen.com to send the document"
icon="fa-share"
/>
<button
name="resolve_error"
type="object"
states="error,pingen_error"
string="Errors resolved"
icon="fa-repeat"
/>
<button
name="update_post_infos"
type="object"
states="sendcenter"
string="Update the letter's informations"
icon="fa-refresh"
/>
</group>
</page>
<page string="Attachment">
<group col="4" colspan="4">
<separator string="Data" colspan="4"/>
<separator string="Data" colspan="4" />
<newline />
<group col="2" colspan="4" attrs="{'invisible':[('type','=','url')]}">
<field name="datas" filename="datas_fname" readonly="True"/>
<field name="datas_fname" select="1" readonly="True"/>
<field name="datas" filename="datas_fname" readonly="True" />
<field name="datas_fname" select="1" readonly="True" />
</group>
<group col="2" colspan="4" attrs="{'invisible':[('type','=','binary')]}">
<field name="url" widget="url" readonly="True"/>
<field name="url" widget="url" readonly="True" />
</group>
</group>
<group col="2" colspan="4">
<separator string="Attached To" colspan="2"/>
<field name="attachment_id"/>
<separator string="Attached To" colspan="2" />
<field name="attachment_id" />
</group>
</page>
</notebook>
@@ -119,28 +158,30 @@
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Pingen Document">
<filter icon="terp-project"
string="Pending"
domain="[('state','=','pending')]"/>
<filter icon="terp-stage"
string="Pushed"
domain="[('state','=','pushed')]"/>
<filter icon="gtk-print"
string="In Sendcenter"
domain="[('state','=','sendcenter')]"/>
<filter icon="kanban-apply"
string="Sent"
domain="[('state','=','sent')]"/>
<filter icon="kanban-stop"
string="Error"
domain="[('state','=','error')]"/>
<filter icon="STOCK_NO"
string="Pingen Error"
domain="[('state','=','pingen_error')]"/>
<filter icon="terp-dialog-close"
string="Canceled"
domain="[('state','=','canceled')]"/>
<separator orientation="vertical"/>
<filter
icon="terp-project"
string="Pending"
domain="[('state','=','pending')]"
/>
<filter icon="terp-stage" string="Pushed" domain="[('state','=','pushed')]" />
<filter
icon="gtk-print"
string="In Sendcenter"
domain="[('state','=','sendcenter')]"
/>
<filter icon="kanban-apply" string="Sent" domain="[('state','=','sent')]" />
<filter icon="kanban-stop" string="Error" domain="[('state','=','error')]" />
<filter
icon="STOCK_NO"
string="Pingen Error"
domain="[('state','=','pingen_error')]"
/>
<filter
icon="terp-dialog-close"
string="Canceled"
domain="[('state','=','canceled')]"
/>
<separator orientation="vertical" />
<field name="attachment_id" />
</search>
</field>
@@ -152,9 +193,9 @@
<field name="res_model">pingen.document</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_pingen_document_search"/>
<field name="search_view_id" ref="view_pingen_document_search" />
</record>
<menuitem action="action_pingen_document" id="menu_pingen_document"/>
<menuitem action="action_pingen_document" id="menu_pingen_document" />
</odoo>

View File

@@ -1,2 +1,3 @@
# generated from manifests external_dependencies
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,
)