Browse Source

publish muk_converter - 11.0

pull/16/head
MuK IT GmbH 6 years ago
parent
commit
298a385846
  1. 1
      muk_converter/__init__.py
  2. 12
      muk_converter/__manifest__.py
  3. 6
      muk_converter/data/params.xml
  4. 5
      muk_converter/doc/changelog.rst
  5. 7
      muk_converter/doc/index.rst
  6. 3
      muk_converter/models/__init__.py
  7. 76
      muk_converter/models/converter.py
  8. 83
      muk_converter/models/res_config_settings.py
  9. 10
      muk_converter/models/store.py
  10. 1
      muk_converter/service/__init__.py
  11. 89
      muk_converter/service/provider.py
  12. 75
      muk_converter/service/unoconv.py
  13. BIN
      muk_converter/static/description/icon.png
  14. 1
      muk_converter/static/description/icon.svg
  15. 29
      muk_converter/static/description/index.html
  16. 34
      muk_converter/tests/test_converter.py
  17. 7
      muk_converter/tests/test_unoconv.py
  18. 14
      muk_converter/tools/converter.py
  19. 6
      muk_converter/views/convert.xml
  20. 64
      muk_converter/views/res_config_settings_view.xml
  21. 70
      muk_converter/wizards/convert.py

1
muk_converter/__init__.py

@ -18,6 +18,5 @@
################################################################################### ###################################################################################
from . import service from . import service
from . import tools
from . import models from . import models
from . import wizards from . import wizards

12
muk_converter/__manifest__.py

@ -20,7 +20,7 @@
{ {
"name": "MuK Converter", "name": "MuK Converter",
"summary": """Universal Converter""", "summary": """Universal Converter""",
"version": '11.0.1.1.6',
"version": '11.0.1.2.2',
"category": 'Extra Tools', "category": 'Extra Tools',
"license": "AGPL-3", "license": "AGPL-3",
"website": "https://www.mukit.at", "website": "https://www.mukit.at",
@ -30,15 +30,17 @@
"Mathias Markl <mathias.markl@mukit.at>", "Mathias Markl <mathias.markl@mukit.at>",
], ],
"depends": [ "depends": [
"muk_utils",
"iap",
"base_setup",
"muk_autovacuum", "muk_autovacuum",
"muk_fields_lobject", "muk_fields_lobject",
], ],
"data": [ "data": [
"security/ir.model.access.csv", "security/ir.model.access.csv",
"views/convert.xml",
"data/params.xml", "data/params.xml",
"data/autovacuum.xml", "data/autovacuum.xml",
"views/convert.xml",
"views/res_config_settings_view.xml",
], ],
"qweb": [ "qweb": [
"static/src/xml/*.xml", "static/src/xml/*.xml",
@ -48,9 +50,7 @@
], ],
"external_dependencies": { "external_dependencies": {
"python": [], "python": [],
"bin": [
"unoconv",
],
"bin": [],
}, },
"application": False, "application": False,
"installable": True, "installable": True,

6
muk_converter/data/params.xml

@ -19,6 +19,12 @@
<odoo noupdate="1"> <odoo noupdate="1">
<record id="muk_converter_service" model="ir.config_parameter">
<field name="key">muk_converter.service</field>
<field name="value">unoconv</field>
<field name="group_ids" eval="[(4, ref('base.group_system'))]"/>
</record>
<record id="muk_converter_max_store" model="ir.config_parameter"> <record id="muk_converter_max_store" model="ir.config_parameter">
<field name="key">muk_converter.max_store</field> <field name="key">muk_converter.max_store</field>
<field name="value">20</field> <field name="value">20</field>

5
muk_converter/doc/changelog.rst

@ -1,3 +1,8 @@
`1.2.0`
-------
- Added In-App Purchases option
`1.1.0` `1.1.0`
------- -------

7
muk_converter/doc/index.rst

@ -121,6 +121,13 @@ Contributors
* Mathias Markl <mathias.markl@mukit.at> * Mathias Markl <mathias.markl@mukit.at>
Images
------------
Some pictures are based on or inspired by the icon set of Font Awesome:
* `Font Awesome <https://fontawesome.com>`_
Author & Maintainer Author & Maintainer
------------------- -------------------

3
muk_converter/models/__init__.py

@ -17,5 +17,6 @@
# #
################################################################################### ###################################################################################
from . import converter
from . import store from . import store
from . import converter
from . import res_config_settings

76
muk_converter/models/converter.py

@ -17,12 +17,14 @@
# #
################################################################################### ###################################################################################
import base64
import hashlib import hashlib
import logging import logging
from odoo import api, models, fields
from odoo import api, models, fields, SUPERUSER_ID
from odoo.addons.muk_converter.tools import converter
from odoo.addons.muk_converter.service.unoconv import UnoconvConverter
from odoo.addons.muk_converter.service.provider import RemoteConverter
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -31,27 +33,61 @@ class Converter(models.AbstractModel):
_name = 'muk_converter.converter' _name = 'muk_converter.converter'
_description = 'Converter' _description = 'Converter'
#----------------------------------------------------------
# Functions
#----------------------------------------------------------
@api.model
def formats(self):
return self._provider().formats
@api.model
def imports(self):
return self._provider().imports
@api.model
def convert(self, filename, content, format="pdf", recompute=False, store=True):
binary_content = base64.b64decode(content)
checksum = hashlib.sha1(binary_content).hexdigest()
stored = self._retrieve(checksum, format)
if not recompute and stored.exists():
return base64.b64encode(stored.content)
else:
name = "%s.%s" % (filename, format)
output = self._parse(filename, binary_content, format)
if store:
self._store(checksum, name, output, format, stored)
return base64.b64encode(output)
#----------------------------------------------------------
# Helper
#----------------------------------------------------------
@api.model
def _provider(self):
params = self.env['ir.config_parameter'].sudo()
service = params.get_param('muk_converter.service')
if service == 'unoconv':
return UnoconvConverter()
else:
return RemoteConverter(env=self.env)
@api.model
def _parse(self, filename, content, format):
return self._provider().convert(content, filename=filename, format=format)
@api.model @api.model
def convert(self, filename, content, format="pdf", recompute=False):
def parse(filename, content, format):
return converter.convert(filename, content, format)
def store(checksum, filename, content, format, stored):
if not stored.exists():
def _retrieve(self, checksum, format):
domain = [["checksum", "=", checksum], ["format", "=", format]]
return self.env['muk_converter.store'].sudo().search(domain, limit=1)
@api.model
def _store(self, checksum, filename, content, format, stored):
if stored and stored.exists():
stored.write({'used_date': fields.Datetime.now})
else:
self.env['muk_converter.store'].sudo().create({ self.env['muk_converter.store'].sudo().create({
'checksum': checksum, 'checksum': checksum,
'format': format, 'format': format,
'content_fname': filename, 'content_fname': filename,
'content': content}) 'content': content})
else:
stored.write({'used_date': fields.Datetime.now})
checksum = hashlib.sha1(content).hexdigest()
stored = self.env['muk_converter.store'].sudo().search(
[["checksum", "=", checksum], ["format", "=", format]], limit=1)
if not recompute and stored.exists():
return stored.content
else:
output = parse(filename, content, format)
name = "%s.%s" % (filename, format)
store(checksum, name, output, format, stored)
return output

83
muk_converter/models/res_config_settings.py

@ -0,0 +1,83 @@
###################################################################################
#
# Copyright (C) 2017 MuK IT GmbH
#
# 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/>.
#
###################################################################################
import logging
import textwrap
from odoo import api, fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
converter_service = fields.Selection(
selection=[
("unoconv", "Local"),
("provider", "Service")],
string="Converter",
default="provider",
help=textwrap.dedent("""\
Converter engine, which is used for the conversion:
- Local: Use a locally installed unoconv installation
- Service: Use a service to do the conversion
"""))
converter_max_store = fields.Integer(
string="Storage Size",
help=textwrap.dedent("""\
To certify the conversion, converted files can be saved
and loaded from memory if necessary. You can set a maximum
size of the storage to prevent massive memory requirements.
"""))
converter_credit = fields.Boolean(
compute='_compute_converter_credit',
string="Converter insufficient credit")
@api.multi
def set_values(self):
res = super(ResConfigSettings, self).set_values()
param = self.env['ir.config_parameter'].sudo()
param.set_param("muk_converter.service", self.converter_service)
param.set_param("muk_converter.max_store", self.converter_max_store)
return res
@api.model
def get_values(self):
res = super(ResConfigSettings, self).get_values()
params = self.env['ir.config_parameter'].sudo()
res.update(
converter_service=params.get_param("muk_converter.service", default="provider"),
converter_max_store=int(params.get_param("muk_converter.max_store", default=20))
)
return res
@api.multi
def _compute_converter_credit(self):
credits = self.env['iap.account'].get_credits('muk_converter')
for record in self:
record.converter_credit = credits <= 0
@api.multi
def redirect_to_buy_converter_credit(self):
url = self.env['iap.account'].get_credits_url('muk_converter')
return {
'type': 'ir.actions.act_url',
'url': url,
'target': '_new',
}

10
muk_converter/models/store.py

@ -30,12 +30,16 @@ class Store(models.Model):
_name = 'muk_converter.store' _name = 'muk_converter.store'
_description = 'Converter Store' _description = 'Converter Store'
#----------------------------------------------------------
# Database
#----------------------------------------------------------
name = fields.Char( name = fields.Char(
compute="_compute_name", compute="_compute_name",
string="Name", string="Name",
store=True) store=True)
used_date = fields.Date(
used_date = fields.Datetime(
string="Used on", string="Used on",
default=fields.Datetime.now) default=fields.Datetime.now)
@ -55,6 +59,10 @@ class Store(models.Model):
string="Data", string="Data",
required=True) required=True)
#----------------------------------------------------------
# Read
#----------------------------------------------------------
@api.depends('checksum', 'content_fname') @api.depends('checksum', 'content_fname')
def _compute_name(self): def _compute_name(self):
for record in self: for record in self:

1
muk_converter/service/__init__.py

@ -18,3 +18,4 @@
################################################################################### ###################################################################################
from . import unoconv from . import unoconv
from . import provider

89
muk_converter/service/provider.py

@ -0,0 +1,89 @@
###################################################################################
#
# Copyright (C) 2018 MuK IT GmbH
#
# 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/>.
#
###################################################################################
import base64
import logging
from odoo.addons.iap import jsonrpc
from odoo.addons.muk_utils.tools.cache import memoize
from odoo.addons.muk_utils.tools.file import guess_extension
_logger = logging.getLogger(__name__)
CONVERTER_DEFAULT_ENDPOINT = 'https://iap-converter.mukit.at'
CONVERTER_ENDPOINT_FORMATS = '/iap/converter/1/formats'
CONVERTER_ENDPOINT_IMPORTS = '/iap/converter/1/imports'
CONVERTER_ENDPOINT_CONVERT = '/iap/converter/1/convert'
class RemoteConverter(object):
def __init__(self, env):
self.params = env['ir.config_parameter'].sudo()
self.account = env['iap.account'].get('muk_converter')
def endpoint(self, route):
return "%s%s" % (self.params.get_param('muk_converter.endpoint', CONVERTER_DEFAULT_ENDPOINT), route)
def payload(self, params={}):
params.update({
'account_token': self.account.account_token,
'database_uuid': self.params.get_param('database.uuid'),
})
return params
@property
@memoize(timeout=3600)
def formats(self):
return jsonrpc(self.endpoint(CONVERTER_ENDPOINT_FORMATS), params=self.payload())
@property
@memoize(timeout=3600)
def imports(self):
return jsonrpc(self.endpoint(CONVERTER_ENDPOINT_IMPORTS), params=self.payload())
def convert(self, binary, mimetype=None, filename=None, export="binary", doctype="document", format="pdf"):
""" Converts a binary value to the given format.
:param binary: The binary value.
:param mimetype: The mimetype of the binary value.
:param filename: The filename of the binary value.
:param export: The output format (binary, file, base64).
:param doctype: Specify the document type (document, graphics, presentation, spreadsheet).
:param format: Specify the output format for the document.
:return: Returns the output depending on the given format.
:raises ValueError: The file extension could not be determined or the format is invalid.
"""
params = {
'format': format,
'doctype': doctype,
'mimetype': mimetype,
'filename': filename,
'content': base64.b64encode(binary),
}
result = jsonrpc(self.endpoint(CONVERTER_ENDPOINT_CONVERT), params=self.payload(params))
if export == 'base64':
return result
if export == 'file':
output = io.BytesIO()
output.write(base64.b64decode(result))
output.close()
return output
else:
return base64.b64decode(result)

75
muk_converter/service/unoconv.py

@ -35,11 +35,11 @@ from contextlib import closing
from odoo.tools import config from odoo.tools import config
from odoo.tools.mimetypes import guess_mimetype from odoo.tools.mimetypes import guess_mimetype
from odoo.addons.muk_utils.tools import utils_os
from odoo.addons.muk_utils.tools.file import guess_extension
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
FORMATS = [
UNOCONV_FORMATS = [
"bib", "bmp", "csv", "dbf", "dif", "doc", "doc6", "doc95", "docbook", "docx", "docx7", "emf", "bib", "bmp", "csv", "dbf", "dif", "doc", "doc6", "doc95", "docbook", "docx", "docx7", "emf",
"eps", "fodg", "fodp", "fods", "fodt", "gif", "html", "jpg", "latex", "mediawiki", "met", "odd", "eps", "fodg", "fodp", "fods", "fodt", "gif", "html", "jpg", "latex", "mediawiki", "met", "odd",
"odg", "odp", "ods", "odt", "ooxml", "otg", "otp", "ots", "ott", "pbm", "pct", "pdb", "pdf", "pgm", "odg", "odp", "ods", "odt", "ooxml", "otg", "otp", "ots", "ott", "pbm", "pct", "pdb", "pdf", "pgm",
@ -57,7 +57,7 @@ FORMATS = [
"xls95", "xlsx", "xlt", "xlt5", "xlt95", "xpm" "xls95", "xlsx", "xlt", "xlt5", "xlt95", "xpm"
] ]
IMPORTS = [
UNOCONV_IMPORTS = [
"bmp", "csv", "dbf", "dif", "doc", "docx", "dot", "emf", "eps", "epub", "fodg", "fodp", "fods", "bmp", "csv", "dbf", "dif", "doc", "docx", "dot", "emf", "eps", "epub", "fodg", "fodp", "fods",
"fodt", "gif", "gnm", "gnumeric", "htm", "html", "jpeg", "jpg", "met", "mml", "odb", "odf", "odg", "fodt", "gif", "gnm", "gnumeric", "htm", "html", "jpeg", "jpg", "met", "mml", "odb", "odf", "odg",
"odp", "ods", "odt", "pbm", "pct", "pdb", "pdf", "pgm", "png", "pot", "ppm", "pps", "ppt", "pptx", "odp", "ods", "odt", "pbm", "pct", "pdb", "pdf", "pgm", "png", "pot", "ppm", "pps", "ppt", "pptx",
@ -72,49 +72,25 @@ IMPORTS = [
"wmf", "wri", "xls", "xlsx", "xlt", "xlw", "xml", "xpm" "wmf", "wri", "xls", "xlsx", "xlt", "xlw", "xml", "xpm"
] ]
def formats():
return FORMATS
class UnoconvConverter(object):
def imports():
return IMPORTS
@property
def formats(self):
return UNOCONV_FORMATS
def unoconv_environ():
@property
def imports(self):
return UNOCONV_IMPORTS
def environ(self):
env = os.environ.copy() env = os.environ.copy()
uno_path = config.get('uno_path', False) uno_path = config.get('uno_path', False)
if uno_path: if uno_path:
env['UNO_PATH'] = config['uno_path'] env['UNO_PATH'] = config['uno_path']
return env return env
def convert(input_path, output_path, doctype="document", format="pdf"):
"""
Convert a file to the given format.
:param input_path: The path of the file to convert.
:param output_path: The path of the output where the converted file is to be saved.
:param doctype: Specify the document type (document, graphics, presentation, spreadsheet).
:param format: Specify the output format for the document.
:raises CalledProcessError: The command returned non-zero exit status 1.
:raises OSError: This exception is raised when a system function returns a system-related error.
"""
try:
env = unoconv_environ()
shell = True if os.name in ('nt', 'os2') else False
args = ['unoconv', '--format=%s' % format, '--output=%s' % output_path, input_path]
process = Popen(args, stdout=PIPE, env=env, shell=shell)
outs, errs = process.communicate()
return_code = process.wait()
if return_code:
raise CalledProcessError(return_code, args, outs, errs)
except CalledProcessError:
_logger.exception("Error while running unoconv.")
raise
except OSError:
_logger.exception("Error while running unoconv.")
raise
def convert_binary(binary, mimetype=None, filename=None, export="binary", doctype="document", format="pdf"):
"""
Converts a binary value to the given format.
def convert(self, binary, mimetype=None, filename=None, export="binary", doctype="document", format="pdf"):
""" Converts a binary value to the given format.
:param binary: The binary value. :param binary: The binary value.
:param mimetype: The mimetype of the binary value. :param mimetype: The mimetype of the binary value.
@ -125,12 +101,12 @@ def convert_binary(binary, mimetype=None, filename=None, export="binary", doctyp
:return: Returns the output depending on the given format. :return: Returns the output depending on the given format.
:raises ValueError: The file extension could not be determined or the format is invalid. :raises ValueError: The file extension could not be determined or the format is invalid.
""" """
extension = utils_os.get_extension(binary, filename, mimetype)
extension = guess_extension(filename=filename, mimetype=mimetype, binary=binary)
if not extension: if not extension:
raise ValueError("The file extension could not be determined.") raise ValueError("The file extension could not be determined.")
if format not in FORMATS:
if format not in self.formats:
raise ValueError("Invalid export format.") raise ValueError("Invalid export format.")
if extension not in IMPORTS:
if extension not in self.imports:
raise ValueError("Invalid import format.") raise ValueError("Invalid import format.")
tmp_dir = tempfile.mkdtemp() tmp_dir = tempfile.mkdtemp()
try: try:
@ -141,7 +117,13 @@ def convert_binary(binary, mimetype=None, filename=None, export="binary", doctyp
tmp_ppath = tmp_ppath.replace("\\", "/") tmp_ppath = tmp_ppath.replace("\\", "/")
with closing(open(tmp_wpath, 'wb')) as file: with closing(open(tmp_wpath, 'wb')) as file:
file.write(binary) file.write(binary)
convert(tmp_wpath, tmp_ppath, doctype, format)
shell = True if os.name in ('nt', 'os2') else False
args = ['unoconv', '--format=%s' % format, '--output=%s' % tmp_ppath, tmp_wpath]
process = Popen(args, stdout=PIPE, env=self.environ(), shell=shell)
outs, errs = process.communicate()
return_code = process.wait()
if return_code:
raise CalledProcessError(return_code, args, outs, errs)
with closing(open(tmp_ppath, 'rb')) as file: with closing(open(tmp_ppath, 'rb')) as file:
if export == 'file': if export == 'file':
output = io.BytesIO() output = io.BytesIO()
@ -152,5 +134,14 @@ def convert_binary(binary, mimetype=None, filename=None, export="binary", doctyp
return base64.b64encode(file.read()) return base64.b64encode(file.read())
else: else:
return file.read() return file.read()
except CalledProcessError:
_logger.exception("Error while running unoconv.")
raise
except OSError:
_logger.exception("Error while running unoconv.")
raise
finally: finally:
shutil.rmtree(tmp_dir) shutil.rmtree(tmp_dir)
unoconv = UnoconvConverter()

BIN
muk_converter/static/description/icon.png

Before

Width: 250  |  Height: 250  |  Size: 19 KiB

After

Width: 250  |  Height: 250  |  Size: 20 KiB

1
muk_converter/static/description/icon.svg
File diff suppressed because it is too large
View File

29
muk_converter/static/description/index.html

@ -23,27 +23,26 @@
<section class="oe_container oe_dark" <section class="oe_container oe_dark"
style="margin-bottom: 20px; border-top: 5px solid #797979; border-bottom: 5px solid #797979;"> style="margin-bottom: 20px; border-top: 5px solid #797979; border-bottom: 5px solid #797979;">
<h3 class="oe_slogan" style="margin-bottom: 10px;">Demo</h3> <h3 class="oe_slogan" style="margin-bottom: 10px;">Demo</h3>
<div class="row" style="margin: auto; max-width: 200px;">
<div class="col-xs-6">
<div class="row" style="margin: auto; max-width: 250px;">
<div class="col-6 col-xs-6">
<h5 class="oe_slogan" style="font-size: 20px; margin: 2px;">User:</h5> <h5 class="oe_slogan" style="font-size: 20px; margin: 2px;">User:</h5>
</div> </div>
<div class="col-xs-6">
<div class="col-6 col-xs-6">
<h5 class="oe_slogan" style="font-size: 20px; margin: 2px;">apps</h5> <h5 class="oe_slogan" style="font-size: 20px; margin: 2px;">apps</h5>
</div> </div>
<div class="col-xs-6">
<div class="col-6 col-xs-6">
<h5 class="oe_slogan" style="font-size: 20px; margin: 2px;">Password:</h5> <h5 class="oe_slogan" style="font-size: 20px; margin: 2px;">Password:</h5>
</div> </div>
<div class="col-xs-6">
<div class="col-6 col-xs-6">
<h5 class="oe_slogan" style="font-size: 20px; margin: 2px;">demo</h5> <h5 class="oe_slogan" style="font-size: 20px; margin: 2px;">demo</h5>
</div> </div>
</div> </div>
<div class="oe_slogan" style="margin-top: 5px;"> <div class="oe_slogan" style="margin-top: 5px;">
<a class="btn btn-primary btn-lg mt8" <a class="btn btn-primary btn-lg mt8"
href="https://demo.mukit.at/web/login" href="https://demo.mukit.at/web/login"
style="position: relative; overflow: hidden;"><span
class="o_ripple"
style="height: 138px; width: 138px; top: -35.2969px; left: -8.17188px;"></span>
<i class="fa fa-video-camera"></i> Live Preview </a>
style="position: relative; overflow: hidden;">
<i class="fa fa-video-camera"></i> Live Preview
</a>
</div> </div>
</section> </section>
@ -55,13 +54,13 @@
<div class="oe_slogan"> <div class="oe_slogan">
<a class="btn btn-primary btn-lg mt8" href="mailto:sale@mukit.at"> <a class="btn btn-primary btn-lg mt8" href="mailto:sale@mukit.at">
<i class="fa fa-envelope"></i> Email <i class="fa fa-envelope"></i> Email
</a> <a class="btn btn-primary btn-lg mt8"
href="https://mukit.at/page/contactus"> <i class="fa fa-phone"></i>
Contact
</a> <a class="btn btn-primary btn-lg mt8" href="mailto:support@mukit.at">
</a>
<a class="btn btn-primary btn-lg mt8" href="https://mukit.at/page/contactus">
<i class="fa fa-phone"></i> Contact
</a>
<a class="btn btn-primary btn-lg mt8" href="mailto:support@mukit.at">
<i class="fa fa-life-ring"></i> Support <i class="fa fa-life-ring"></i> Support
</a> </a>
</div> </div>
<img src="logo.png" style="width: 200px; margin-bottom: 20px;"
class="center-block">
<img src="logo.png" style="width: 200px; margin-bottom: 20px; display: block;" class="mx-auto center-block">
</section> </section>

34
muk_converter/tests/test_converter.py

@ -18,13 +18,12 @@
################################################################################### ###################################################################################
import os import os
import base64
import logging import logging
import unittest import unittest
from odoo.tests import common from odoo.tests import common
from odoo.addons.muk_converter.tools import converter
_path = os.path.dirname(os.path.dirname(__file__)) _path = os.path.dirname(os.path.dirname(__file__))
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -32,24 +31,41 @@ class ConverterTestCase(common.TransactionCase):
def setUp(self): def setUp(self):
super(ConverterTestCase, self).setUp() super(ConverterTestCase, self).setUp()
self.params = self.env['ir.config_parameter']
self.store = self.env['muk_converter.store'].sudo()
self.converter = self.env['muk_converter.converter']
self.store_count = self.store.search([], count=True)
self.params.set_param('muk_converter.service', 'unoconv')
def tearDown(self): def tearDown(self):
super(ConverterTestCase, self).tearDown() super(ConverterTestCase, self).tearDown()
def test_formats(self): def test_formats(self):
self.assertTrue(converter.formats())
self.assertTrue(self.converter.formats())
def test_imports(self):
self.assertTrue(self.converter.imports())
@unittest.skipIf(os.environ.get('TRAVIS', False), "Skipped for Travis CI")
def test_convert_basic(self):
with open(os.path.join(_path, 'tests/data', 'sample.png'), 'rb') as file:
self.assertTrue(self.converter.convert('sample.png', base64.b64encode(file.read())))
@unittest.skipIf(os.environ.get('TRAVIS', False), "Skipped for Travis CI") @unittest.skipIf(os.environ.get('TRAVIS', False), "Skipped for Travis CI")
def test_convert(self):
def test_convert_format(self):
with open(os.path.join(_path, 'tests/data', 'sample.png'), 'rb') as file: with open(os.path.join(_path, 'tests/data', 'sample.png'), 'rb') as file:
self.assertTrue(converter.convert('sample.png', file.read(), "pdf"))
self.assertTrue(self.converter.convert('sample.png', base64.b64encode(file.read()), format="html"))
@unittest.skipIf(os.environ.get('TRAVIS', False), "Skipped for Travis CI") @unittest.skipIf(os.environ.get('TRAVIS', False), "Skipped for Travis CI")
def test_convert2pdf(self):
def test_convert_stored(self):
with open(os.path.join(_path, 'tests/data', 'sample.png'), 'rb') as file: with open(os.path.join(_path, 'tests/data', 'sample.png'), 'rb') as file:
self.assertTrue(converter.convert2pdf('sample.png', file.read()))
self.assertTrue(self.converter.convert('sample.png', base64.b64encode(file.read())))
self.assertTrue(self.store.search([], count=True) >= self.store_count)
self.assertTrue(self.converter.convert('sample.png', base64.b64encode(file.read())))
@unittest.skipIf(os.environ.get('TRAVIS', False), "Skipped for Travis CI") @unittest.skipIf(os.environ.get('TRAVIS', False), "Skipped for Travis CI")
def test_convert2html(self):
def test_convert_recompute(self):
with open(os.path.join(_path, 'tests/data', 'sample.png'), 'rb') as file: with open(os.path.join(_path, 'tests/data', 'sample.png'), 'rb') as file:
self.assertTrue(converter.convert2html('sample.png', file.read()))
self.assertTrue(self.converter.convert('sample.png', base64.b64encode(file.read()), recompute=True, store=False))
self.assertTrue(self.store.search([], count=True) == self.store_count)
self.assertTrue(self.converter.convert('sample.png', base64.b64encode(file.read())))

7
muk_converter/tests/test_unoconv.py

@ -23,7 +23,7 @@ import unittest
from odoo.tests import common from odoo.tests import common
from odoo.addons.muk_converter.service import unoconv
from odoo.addons.muk_converter.service.unoconv import UnoconvConverter
_path = os.path.dirname(os.path.dirname(__file__)) _path = os.path.dirname(os.path.dirname(__file__))
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -32,12 +32,13 @@ class UnoconvTestCase(common.TransactionCase):
def setUp(self): def setUp(self):
super(UnoconvTestCase, self).setUp() super(UnoconvTestCase, self).setUp()
self.unoconv = UnoconvConverter()
def tearDown(self): def tearDown(self):
super(UnoconvTestCase, self).tearDown() super(UnoconvTestCase, self).tearDown()
@unittest.skipIf(os.environ.get('TRAVIS', False), "Skipped for Travis CI") @unittest.skipIf(os.environ.get('TRAVIS', False), "Skipped for Travis CI")
def test_convert_binary(self):
def test_convert(self):
with open(os.path.join(_path, 'tests/data', 'sample.png'), 'rb') as file: with open(os.path.join(_path, 'tests/data', 'sample.png'), 'rb') as file:
self.assertTrue(unoconv.convert_binary(file.read()))
self.assertTrue(self.unoconv.convert(file.read(), filename='sample.png'))

14
muk_converter/tools/converter.py

@ -21,25 +21,25 @@ import logging
from odoo import tools from odoo import tools
from odoo.addons.muk_converter.service import unoconv
from odoo.addons.muk_converter.service.unoconv import unoconv
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
def formats(): def formats():
return unoconv.formats()
return unoconv.formats
def selection_formats(): def selection_formats():
return list(map(lambda format: (format, format.upper()), unoconv.formats()))
return list(map(lambda format: (format, format.upper()), unoconv.formats)
def imports(): def imports():
return unoconv.imports()
return unoconv.imports
def convert(filename, content, format): def convert(filename, content, format):
return unoconv.convert_binary(binary=content, filename=filename, format=format)
return unoconv.convert(content, filename=filename, format=format)
def convert2pdf(filename, content): def convert2pdf(filename, content):
return unoconv.convert_binary(binary=content, filename=filename)
return unoconv.convert(content, filename=filename, format="pdf")
def convert2html(filename, content): def convert2html(filename, content):
output = unoconv.convert_binary(binary=content, filename=filename, format="html")
output = unoconv.convert(content, filename=filename, format="html")
return tools.html_sanitize(output) return tools.html_sanitize(output)

6
muk_converter/views/convert.xml

@ -27,12 +27,8 @@
<group states="export"> <group states="export">
<field invisible="1" name="state" /> <field invisible="1" name="state" />
<group> <group>
<field name="type" />
<field name="input_url" widget="url"
attrs="{'required':[('type','=','url')], 'invisible':[('type','=','binary')]}" />
<field name="input_name" invisible="1" /> <field name="input_name" invisible="1" />
<field name="input_binary" filename="input_name"
attrs="{'required':[('type','=','binary')], 'invisible':[('type','=','url')]}" />
<field name="input_binary" filename="input_name" />
</group> </group>
<group> <group>
<field name="format" /> <field name="format" />

64
muk_converter/views/res_config_settings_view.xml

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2017 MuK IT GmbH
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/>.
-->
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
<field name="arch" type="xml">
<div name="multi_company" position="after">
<h2>File Converter</h2>
<div class="row mt16 o_settings_container" name="web_client">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane"></div>
<div class="o_setting_right_pane">
<label for="converter_service"/>
<div class="text-muted">
Converter engine, which is used for the conversion
</div>
<div class="mt8">
<field name="converter_service" class="o_light_label" widget="radio" required="True"/>
</div>
<div class="content-group" attrs="{'invisible': [('converter_service','!=','provider')]}">
<div id="partner_autocomplete_settings" position="inside">
<widget name="iap_credit_checker" service_name="muk_converter"/>
</div>
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane"></div>
<div class="o_setting_right_pane">
<label for="converter_max_store"/>
<div class="text-muted">
Maximum storage size of the converter store
</div>
<div class="mt8">
<field name="converter_max_store" class="o_light_label" />
</div>
</div>
</div>
</div>
</div>
</field>
</record>
</odoo>

70
muk_converter/wizards/convert.py

@ -25,8 +25,8 @@ import mimetypes
from odoo import _, api, fields, models from odoo import _, api, fields, models
from odoo.addons.muk_utils.tools.http import get_response
from odoo.addons.muk_converter.tools import converter
# from odoo.addons.muk_utils.tools.http import get_response TODO
#from odoo.addons.muk_converter.tools import converter
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -34,31 +34,36 @@ class ConverterWizard(models.TransientModel):
_name = "muk_converter.convert" _name = "muk_converter.convert"
#----------------------------------------------------------
# Selections
#----------------------------------------------------------
def _format_selection(self):
formats = self.env['muk_converter.converter'].formats()
return list(map(lambda format: (format, format.upper()), formats))
#----------------------------------------------------------
# Database
#----------------------------------------------------------
state = fields.Selection( state = fields.Selection(
selection=[("export", "Export"), ("download", "Download")],
selection=[
("export", "Export"),
("download", "Download")],
string="State", string="State",
required=True, required=True,
default="export") default="export")
type = fields.Selection(
selection=[("url", "URL"), ("binary", "File")],
string="Type",
default="binary",
change_default=True,
states={'export': [('required', True)]},
help="Either a binary file or an url can be converted")
input_url = fields.Char(
string="Url")
input_name = fields.Char( input_name = fields.Char(
string="Filename")
string="Filename",
states={'export': [('required', True)]})
input_binary = fields.Binary( input_binary = fields.Binary(
string="File")
string="File",
states={'export': [('required', True)]})
format = fields.Selection( format = fields.Selection(
selection=converter.selection_formats(),
selection=_format_selection,
string="Format", string="Format",
default="pdf", default="pdf",
states={'export': [('required', True)]}) states={'export': [('required', True)]})
@ -73,37 +78,26 @@ class ConverterWizard(models.TransientModel):
readonly=True, readonly=True,
states={'download': [('required', True)]}) states={'download': [('required', True)]})
#----------------------------------------------------------
# Functions
#----------------------------------------------------------
@api.multi @api.multi
def convert(self): def convert(self):
def export(record, content, filename):
name = "%s.%s" % (os.path.splitext(filename)[0], record.format)
output = record.env['muk_converter.converter'].convert(filename, content)
record.write({
self.ensure_one()
name = "%s.%s" % (os.path.splitext(self.input_name)[0], self.format)
output = self.env['muk_converter.converter'].convert(self.input_name, self.input_binary)
self.write({
'state': 'download', 'state': 'download',
'output_name': name, 'output_name': name,
'output_binary': base64.b64encode(output)})
'output_binary': output})
return { return {
"name": _("Convert File"), "name": _("Convert File"),
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
'res_model': 'muk_converter.convert', 'res_model': 'muk_converter.convert',
'view_mode': 'form', 'view_mode': 'form',
'view_type': 'form', 'view_type': 'form',
'res_id': record.id,
'res_id': self.id,
'views': [(False, 'form')], 'views': [(False, 'form')],
'target': 'new', 'target': 'new',
} }
record = self[0]
if record.input_url:
status, headers, content = get_response(record.input_url)
if status != 200:
raise ValueError("Failed to retrieve the file from the url.")
else:
extension = mimetypes.guess_extension(headers['content-type'])[1:].strip().lower()
if extension not in converter.imports():
raise ValueError("Invalid import format.")
else:
return export(record, content, record.input_name or "%s.%s" % (uuid.uuid4(), extension))
elif record.input_name and record.input_binary:
return export(record, base64.b64decode(record.input_binary), record.input_name)
else:
raise ValueError("The conversion requires either a valid url or a filename and a file.")
Loading…
Cancel
Save