diff --git a/.travis.yml b/.travis.yml index 42a17a2..3121edd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ install: - git clone https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} - travis_install_nightly - - pip install cmislib + - pip install git+https://github.com/lmignon/python-cmislib.git@6.0.dev#egg=cmislib script: - travis_run_tests diff --git a/cmis/README.rst b/cmis/README.rst new file mode 100644 index 0000000..d877b60 --- /dev/null +++ b/cmis/README.rst @@ -0,0 +1,74 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +============== +CMIS Connector +============== + +This module is the base for Odoo modules implementing different integration +scenario with a CMIS server. +It allows you to configure a CMIS backend in Odoo. + +Installation +============ + +To be compliant with the latest version of CMIS (1.1), the connector use +the latest version of the python cmislib library not yet released at this +stage. The lib can be installed with: + +pip install svn+https://svn.apache.org/repos/asf/chemistry/cmislib/trunk#egg=cmislib + +Configuration +============= + +Create a new CMIS backend with the host, login and password. + +Usage +===== + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/104/9.0 + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed `feedback +`_. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* El Hadji Dem +* Maxime Chambreuil +* Laurent Mignon + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/cmis/__init__.py b/cmis/__init__.py index d737c16..0a6db3a 100644 --- a/cmis/__init__.py +++ b/cmis/__init__.py @@ -1,27 +1,16 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2014 Savoir-faire Linux -# (). -# -# 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 . -# -############################################################################## - +# © 2014-2015 Savoir-faire Linux (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import logging +import httplib2 +import functools from . import ( - cmis_backend, backend, - cmis_binding, + models, ) + +logger = logging.getLogger(__name__) + +logger.warning('Disable SSL Certificate Validation by python code') +httplib2.Http = functools.partial( + httplib2.Http, disable_ssl_certificate_validation=True) diff --git a/cmis/__openerp__.py b/cmis/__openerp__.py index 0d67ad9..bb0ea45 100644 --- a/cmis/__openerp__.py +++ b/cmis/__openerp__.py @@ -1,50 +1,14 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2014 Savoir-faire Linux -# (). -# -# 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 . -# -############################################################################## +# © 2014-2015 Savoir-faire Linux (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'CMIS', - 'version': '7.0.1.1.0', + 'version': '9.0.1.0.0', 'category': 'Connector', 'summary': 'Connect OpenERP with a CMIS server', - 'description': """ -CMIS Connector -============== - -This module is the base for OpenERP modules implementing different integration -scenario with a CMIS server. -It allows you to configure a CMIS backend in OpenERP. - -Configuration -============= - -Create a new CMIS backend with the host, login and password. - -Contributors ------------- -* El Hadji Dem (elhadji.dem@savoirfairelinux.com) -* Maxime Chambreuil (maxime.chambreuil@savoirfairelinux.com) -""", 'author': "Savoir-faire Linux, Odoo Community Association (OCA)", - 'website': 'www.savoirfairelinux.com', + 'website': 'https://odoo-community.org/', 'license': 'AGPL-3', 'depends': [ 'connector', @@ -55,7 +19,8 @@ 'data': [ 'views/cmis_backend_view.xml', 'views/cmis_menu.xml', + 'security/ir.model.access.csv', ], - 'installable': False, + 'installable': True, 'auto_install': False, } diff --git a/cmis/backend.py b/cmis/backend.py index 3c0e4b6..21d37b0 100644 --- a/cmis/backend.py +++ b/cmis/backend.py @@ -1,24 +1,6 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2014 Savoir-faire Linux -# (). -# -# 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 . -# -############################################################################## +# © 2014-2015 Savoir-faire Linux (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import openerp.addons.connector.backend as backend diff --git a/cmis/cmis_backend.py b/cmis/cmis_backend.py deleted file mode 100644 index 8f8156a..0000000 --- a/cmis/cmis_backend.py +++ /dev/null @@ -1,182 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2014 Savoir-faire Linux -# (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from openerp.osv import orm, fields -from openerp.tools.translate import _ -from openerp.addons.connector.connector import Environment -from openerp.addons.connector.session import ConnectorSession -from .unit.backend_adapter import CmisAdapter -import cmislib.exceptions - - -class cmis_backend(orm.Model): - _name = 'cmis.backend' - _description = 'CMIS Backend' - _inherit = 'connector.backend' - _backend_type = 'cmis' - _columns = { - 'version': fields.selection( - lambda self, *a, **kw: self._select_versions(*a, **kw), - 'Version', - required=True, - ), - 'location': fields.char( - 'Location', - required=True, - ), - 'username': fields.char( - 'Username', - required=True, - ), - 'password': fields.char( - 'Password', - required=True, - ), - 'initial_directory_read': fields.char( - 'Initial directory for reading', - required=True, - ), - 'initial_directory_write': fields.char( - 'Initial directory for writing', - required=True, - ), - 'browsing_ok': fields.boolean( - 'Allow browsing this backend', - ), - 'storing_ok': fields.boolean( - 'Allow storing in this backend', - ), - } - _defaults = { - 'initial_directory_read': '/', - 'initial_directory_write': '/', - } - - def select_versions(self, cr, uid, context=None): - """ Available versions in the backend. - Can be inherited to add custom versions. Using this method - to add a version from an ``_inherit`` does not constrain - to redefine the ``version`` field in the ``_inherit`` model. - """ - return [('1.0', '1.0')] - - def _select_versions(self, cr, uid, context=None): - """ Available versions in the backend. - If you want to add a version, do not override this - method, but ``select_version``. - """ - return self.select_versions(cr, uid, context=context) - - def _get_base_adapter(self, cr, uid, ids, context=None): - """ - Get an adapter to test the backend connection - """ - backend = self.browse(cr, uid, ids[0], context=context) - session = ConnectorSession(cr, uid, context=context) - environment = Environment(backend, session, None) - - return CmisAdapter(environment) - - def check_auth(self, cr, uid, ids, context=None): - """ Check the authentication with DMS """ - - adapter = self._get_base_adapter(cr, uid, ids, context=context) - return adapter._auth(ids) - - def check_directory_of_write(self, cr, uid, ids, context=None): - """Check access right to write from the path""" - if context is None: - context = self.pool['res.users'].context_get(cr, uid) - cmis_backend_obj = self.pool.get('cmis.backend') - datas_fname = 'testdoc' - # login with the cmis account - repo = self.check_auth(cr, uid, ids, context=context) - cmis_backend_rec = cmis_backend_obj.read( - cr, uid, ids, ['initial_directory_write'], - context=context)[0] - folder_path_write = cmis_backend_rec['initial_directory_write'] - # Testing the path - rs = repo.query("SELECT cmis:path FROM cmis:folder") - bool_path_write = self.check_existing_path(rs, folder_path_write) - # Check if we can create a doc from OE to EDM - # Document properties - if bool_path_write: - sub = repo.getObjectByPath(folder_path_write) - try: - sub.createDocumentFromString( - datas_fname, - contentString='hello, world', - contentType='text/plain') - except cmislib.exceptions.UpdateConflictException: - raise orm.except_orm( - _('Cmis Error!'), - _("The test file already exists in the DMS. " - "Please remove it and try again.")) - except cmislib.exceptions.RuntimeException: - raise orm.except_orm( - _('Cmis access right Error!'), - ("Please check your access right.")) - self.get_error_for_path(bool_path_write, folder_path_write) - - def check_directory_of_read(self, cr, uid, ids, context=None): - """Check access right to read from the path""" - if context is None: - context = self.pool['res.users'].context_get(cr, uid) - cmis_backend_rec = self.read( - cr, uid, ids, ['initial_directory_read'], - context=context)[0] - # Login with the cmis account - repo = self.check_auth(cr, uid, ids, context=context) - folder_path_read = cmis_backend_rec['initial_directory_read'] - # Testing the path - rs = repo.query("SELECT cmis:path FROM cmis:folder ") - bool_path_read = self.check_existing_path(rs, folder_path_read) - self.get_error_for_path(bool_path_read, folder_path_read) - - def check_existing_path(self, rs, folder_path): - """Function to check if the path is correct""" - for one_rs in rs: - # Print name of files - props = one_rs.getProperties() - if props['cmis:path'] == folder_path: - return True - return False - - def get_error_for_path(self, is_valid, path): - """Return following the boolean the right error message""" - if is_valid: - raise orm.except_orm(_('Cmis Message'), - _("Path is correct for : %s") % path) - else: - raise orm.except_orm(_('Cmis Error!'), - _("Error path for : %s") % path) - - def sanitize_input(self, file_name): - """Prevent injection by escaping: '%_""" - file_name = file_name.replace("'", r"\'") - file_name = file_name.replace("%", r"\%") - file_name = file_name.replace("_", r"\_") - return file_name - - def safe_query(self, query, file_name, repo): - args = map(self.sanitize_input, file_name) - return repo.query(query % ''.join(args)) diff --git a/cmis/cmis_binding.py b/cmis/cmis_binding.py deleted file mode 100644 index a1e8463..0000000 --- a/cmis/cmis_binding.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2015 - Present Savoir-faire Linux -# (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from openerp.osv import orm, fields - - -class CmisBinding(orm.AbstractModel): - _name = 'cmis.binding' - _inherit = 'external.binding' - _description = 'DMS Binding (Abstract)' - - _columns = { - 'backend_id': fields.many2one( - 'cmis.backend', 'CMIS Backend', required=True, - ondelete='restrict' - ), - 'dms_id': fields.integer('ID in Dms', required=True), - 'sync_date': fields.datetime( - 'Last Synchronization Date', required=True), - 'updated_on': fields.datetime('Last Update in Dms') - - } diff --git a/cmis/connector.py b/cmis/connector.py index c030194..21aa2dc 100644 --- a/cmis/connector.py +++ b/cmis/connector.py @@ -1,35 +1,14 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2014 Savoir-faire Linux -# (). -# -# 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 . -# -############################################################################## +# © 2014-2015 Savoir-faire Linux (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp.addons.connector.connector import ( - Environment, install_in_connector) +from openerp.addons.connector.connector import Environment from openerp.addons.connector.checkpoint import checkpoint -install_in_connector() - -def get_environment(session, model_name, backend_id): +def get_environment(session, model_name, cmis_backend_id): """ Create an environment to work with. """ - backend_record = session.browse('cmis.backend', backend_id) + backend_record = session.browse('cmis.backend', cmis_backend_id) env = Environment(backend_record, session, model_name) lang = backend_record.default_lang_id lang_code = lang.code if lang else 'en_US' @@ -37,7 +16,7 @@ def get_environment(session, model_name, backend_id): return env -def add_checkpoint(session, model_name, record_id, backend_id): +def add_checkpoint(session, model_name, record_id, cmis_backend_id): """ Add a row in the model ``connector.checkpoint`` for a record, meaning it has to be reviewed by a user. :param session: current session @@ -46,8 +25,8 @@ def add_checkpoint(session, model_name, record_id, backend_id): :type model_name: str :param record_id: ID of the record to be reviewed :type record_id: int - :param backend_id: ID of the Cmis Backend - :type backend_id: int + :param cmis_backend_id: ID of the Cmis Backend + :type cmis_backend_id: int """ return checkpoint.add_checkpoint( - session, model_name, record_id, 'cmis.backend', backend_id) + session, model_name, record_id, 'cmis.backend', cmis_backend_id) diff --git a/cmis/exceptions.py b/cmis/exceptions.py new file mode 100644 index 0000000..8da63e8 --- /dev/null +++ b/cmis/exceptions.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# © 2016 ACSONE SA/NV (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp.exceptions import UserError + + +class CMISError(UserError): + """CMIS Error!""" + def __init__(self, value): + super(CMISError, self).__init__(value) + + +class CMISConnectionError(CMISError): + """CMIS connection Error!""" + def __init__(self, value): + super(CMISConnectionError, self).__init__(value) diff --git a/cmis/i18n/fr.po b/cmis/i18n/fr.po index ea3223f..9038e71 100644 --- a/cmis/i18n/fr.po +++ b/cmis/i18n/fr.po @@ -19,17 +19,18 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #. module: cmis -#: view:cmis.backend:0 field:cmis.binding,backend_id:0 +#: view:cmis.backend:0 field:cmis.binding,cmis_backend_id:0 #: model:ir.model,name:cmis.model_cmis_backend msgid "CMIS Backend" msgstr "Backend CMIS" #. module: cmis -#: code:addons/cmis/unit/backend_adapter.py:55 -#: code:addons/cmis/unit/backend_adapter.py:58 +#: code:addons/cmis/unit/backend_adapter.py:34 #, python-format -msgid "Check your CMIS account configuration." -msgstr "Vérifier la configuration de compte CMIS" +msgid "CMIS backend not found.\n" +"Check your CMIS account configuration." +msgstr "Backend CMIS non trouvé.\n" +"Vérifier la configuration de compte CMIS." #. module: cmis #: code:addons/cmis/unit/backend_adapter.py:54 @@ -87,27 +88,25 @@ msgstr "Quelquechose s'est mal passé. _auth() a été appelé sans identifiant. msgid "Error path for : %s" msgstr "Erreur de chemin : %s" -#. module: cmis -#: field:cmis.backend,storing_ok:0 -msgid "Allow storing in this backend" -msgstr "Permettre le stockage dans ce backend" - #. module: cmis #: code:addons/cmis/cmis_backend.py:168 #, python-format msgid "Path is correct for : %s" msgstr "Le chemin est correct pour : %s" +#. module: cmis +#: code:addons/cmis/unit/backend_adapter.py:39 +#, python-format +msgid "Permission denied.\n" +"Check your CMIS account configuration." +msgstr "Permission refusée.\n" +"Vérifier la configuration de compte CMIS." + #. module: cmis #: view:cmis.backend:0 msgid "Cmis Configuration" msgstr "Configuration CMIS" -#. module: cmis -#: field:cmis.backend,initial_directory_read:0 -msgid "Initial directory for reading" -msgstr "Répertoire initial pour la lecture" - #. module: cmis #: field:cmis.binding,updated_on:0 msgid "Last Update in Dms" @@ -152,11 +151,6 @@ msgstr "Version" msgid "Initial directory for writing" msgstr "Répertoire initial pour l'écriture" -#. module: cmis -#: field:cmis.backend,browsing_ok:0 -msgid "Allow browsing this backend" -msgstr "Permettre la navigation dans ce backend" - #. module: cmis #: field:cmis.binding,dms_id:0 msgid "ID in Dms" diff --git a/cmis/models/__init__.py b/cmis/models/__init__.py new file mode 100644 index 0000000..7ddb65e --- /dev/null +++ b/cmis/models/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# © 2014-2015 Savoir-faire Linux (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import ( + cmis_backend, + cmis_binding, + cmis_object_ref, + cmis_folder, +) diff --git a/cmis/models/cmis_backend.py b/cmis/models/cmis_backend.py new file mode 100644 index 0000000..b169e74 --- /dev/null +++ b/cmis/models/cmis_backend.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +# © 2014-2015 Savoir-faire Linux (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import cmislib.exceptions +from cmislib.exceptions import ObjectNotFoundException + +from openerp import api, fields, models +from openerp.exceptions import Warning +from openerp.tools.translate import _ +from openerp.addons.connector.connector import ConnectorEnvironment +from openerp.addons.connector.session import ConnectorSession +from ..unit.backend_adapter import CmisAdapter +from ..exceptions import CMISError + + +class CmisBackend(models.Model): + _name = 'cmis.backend' + _description = 'CMIS Backend' + _inherit = 'connector.backend' + + _backend_type = 'cmis' + + version = fields.Selection( + selection=[('1.0', '1.0')], required=True) + location = fields.Char( + required=True) + username = fields.Char( + required=True) + password = fields.Char( + required=True) + initial_directory_write = fields.Char( + 'Initial directory for writing', required=True, default='/') + + @api.multi + def _get_base_adapter(self): + """ + Get an adapter to test the backend connection + """ + self.ensure_one() + session = ConnectorSession.from_env(self.env) + environment = ConnectorEnvironment(self, session, None) + return CmisAdapter(environment) + + @api.multi + def check_auth(self): + """ Check the authentication with DMS """ + self.ensure_one() + adapter = self._get_base_adapter() + return adapter._auth(self) + + @api.multi + def check_directory_of_write(self): + """Check access right to write from the path""" + datas_fname = 'testdoc' + for this in self: + # login with the cmis account + repo = this.check_auth() + folder_path_write = this.initial_directory_write + path_write_objectid = self.get_folder_by_path( + folder_path_write, + create_if_not_found=False, + cmis_parent_objectid=None) + # Check if we can create a doc from OE to EDM + # Document properties + if path_write_objectid: + try: + path_write_objectid.createDocumentFromString( + datas_fname, + contentString='hello, world', + contentType='text/plain') + except cmislib.exceptions.UpdateConflictException: + raise CMISError( + _("The test file already exists in the DMS. " + "Please remove it and try again.")) + except cmislib.exceptions.RuntimeException: + raise CMISError( + ("Please check your access right.")) + self.get_error_for_path(path_write_objectid != False, + folder_path_write) + + @api.multi + def get_folder_by_path(self, path, create_if_not_found=True, + cmis_parent_objectid=None): + self.ensure_one() + repo = self.check_auth() + if cmis_parent_objectid: + path = repo.getObject( + cmis_parent_objectid).getPaths()[0] + '/' + path + traversed = [] + try: + return repo.getObjectByPath(path) + except ObjectNotFoundException: + if not create_if_not_found: + return False + # The path doesn't exist and must be created + for part in path.split('/'): + try: + part = '%s' % part + traversed.append(part) + new_root = repo.getObjectByPath('/'.join(traversed)) + except ObjectNotFoundException: + new_root = repo.createFolder(new_root, part) + root = new_root + return root + + def get_error_for_path(self, is_valid, path): + """Return following the boolean the right error message""" + if is_valid: + raise Warning(_("Path is correct for : %s") % path) + else: + raise CMISError(_("Error path for : %s") % path) + + def sanitize_input(self, file_name): + """Prevent injection by escaping: '%_""" + file_name = file_name.replace("'", r"\'") + file_name = file_name.replace("%", r"\%") + file_name = file_name.replace("_", r"\_") + return file_name + + def safe_query(self, query, file_name, repo): + args = map(self.sanitize_input, file_name) + return repo.query(query % ''.join(args)) diff --git a/cmis/models/cmis_binding.py b/cmis/models/cmis_binding.py new file mode 100644 index 0000000..3b775a9 --- /dev/null +++ b/cmis/models/cmis_binding.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# © 2014-2015 Savoir-faire Linux (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import fields, models + + +class CmisBinding(models.AbstractModel): + _name = 'cmis.binding' + _inherit = 'external.binding' + _description = 'DMS Binding (Abstract)' + + cmis_backend_id = fields.Many2one( + 'cmis.backend', 'CMIS Backend', required=True, + ondelete='restrict', oldname='backend_id') + dms_id = fields.Integer('ID in Dms', required=True) + sync_date = fields.Datetime( + 'Last Synchronization Date', required=True) + updated_on = fields.Datetime('Last Update in Dms') diff --git a/cmis/models/cmis_folder.py b/cmis/models/cmis_folder.py new file mode 100644 index 0000000..8c6616e --- /dev/null +++ b/cmis/models/cmis_folder.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# © 2016 ACSONE SA/NV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import api, models +from openerp.exceptions import UserError + + +class CmisFolder(models.AbstractModel): + """A model linked to a folder into cmis + """ + _name = 'cmis.folder' + _inherit = 'cmis.object.ref' + _cmis_object_type = 'cmis:folder' + + @classmethod + def validate_cmis_name(cls, name): + INVALID = set('/\.') + for c in INVALID: + if c in name: + raise UserError("Invalid character '%s' for cmis " + "found in'%s'" % (c, name)) + + @api.multi + def _create_cmis_object(self, backend, parent_cmis_object): + self.ensure_one() + props = self._get_cmis_create_object_properties() + repo = backend.check_auth() + name = self.cmis_content_name + self.validate_cmis_name(name) + new_folder = repo.createFolder( + parent_cmis_object, self.cmis_content_name, properties=props) + return new_folder.getProperties()['cmis:objectId'] diff --git a/cmis/models/cmis_object_ref.py b/cmis/models/cmis_object_ref.py new file mode 100644 index 0000000..3de0c14 --- /dev/null +++ b/cmis/models/cmis_object_ref.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# © 2016 ACSONE SA/NV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import api, fields, models +from openerp.exceptions import UserError +from openerp.tools.translate import _ + + +class CmisObjectRef(models.AbstractModel): + """A technical model to hold a reference to an object into + a cmis container + """ + _name = 'cmis.object.ref' + _cmis_object_type = None # The object type in cmis ex :cmis:folder + + cmis_objectid = fields.Char( + string="CMIS ObjectId", requried=True, index=True, copy=False) + cmis_backend_id = fields.Many2one( + comodel_name="cmis.backend", + string="Backend", + oldname='backend_id', + copy=False) + + cmis_content_name = fields.Char(compute='get_names_for_cmis_content') + + _sql_constraints = [ + ('cmis_object_ref_uniq', + 'unique (cmis_objectid, cmis_backend_id)', + "Cmis object Id must be uniquein a given backend !"), + ] + + @api.multi + def get_names_for_cmis_content(self): + names = dict(self.name_get()) + for rec in self: + rec.cmis_content_name = names[rec.id] + + @api.model + def get_initial_directory_write(self, backend): + return '/'.join([backend.initial_directory_write, + self._name.replace('.', '_')]) + + @api.multi + def _get_cmis_create_object_properties(self): + """Return a dictionary of cmis properties to apply when + creating the cmis object + """ + self.ensure_one() + ret = {} + if self._cmis_object_type: + ret['cmis:objectTypeId'] = self._cmis_object_type + return ret + + def _create_cmis_object(self, backend, parent_cmis_object): + raise NotImplementedError('Must be implemented by specific types') + + @api.multi + def create_in_cmis(self, cmis_backend_id): + backend = self.env['cmis.backend'].browse(cmis_backend_id) + backend.ensure_one() + vals = {} + for rec in self: + if rec.cmis_objectid: + raise UserError( + _("Object %s already exists in CMIS (backend: %s)" % ( + rec.name, rec.cmis_backend_id.name))) + parent_cmis_object = backend.get_folder_by_path( + self.get_initial_directory_write(backend), + create_if_not_found=True) + cmis_objectid = rec._create_cmis_object( + backend, parent_cmis_object) + rec.write({ + 'cmis_objectid': cmis_objectid, + 'cmis_backend_id': backend.id + }) + vals[rec.id] = cmis_objectid + return vals diff --git a/cmis/tests/__init__.py b/cmis/tests/__init__.py index be75a74..3265cca 100644 --- a/cmis/tests/__init__.py +++ b/cmis/tests/__init__.py @@ -1,29 +1,5 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2013-2014 Savoir-faire Linux -# (). -# -# 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 . -# -############################################################################## +# -*- coding: utf-8 -*- +# © 2014-2015 Savoir-faire Linux (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from . import ( - test_model, -) - -checks = [ - test_model, -] +from . import test_model diff --git a/cmis/tests/test_model.py b/cmis/tests/test_model.py index 4291919..4881ce8 100644 --- a/cmis/tests/test_model.py +++ b/cmis/tests/test_model.py @@ -1,24 +1,6 @@ # -*- coding: utf-8 -*- -############################################################################### -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2010 - 2014 Savoir-faire Linux -# (). -# -# 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 . -# -############################################################################### +# © 2014-2015 Savoir-faire Linux (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from openerp.tests.common import TransactionCase @@ -43,7 +25,6 @@ def setUp(self): 'location': "http://localhost:8081/alfresco/s/cmis", 'username': 'admin', 'password': 'admin', - 'initial_directory_read': '/', 'initial_directory_write': '/', } diff --git a/cmis/unit/__init__.py b/cmis/unit/__init__.py index c239f82..2570fc0 100644 --- a/cmis/unit/__init__.py +++ b/cmis/unit/__init__.py @@ -1,24 +1,6 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2015 - Present Savoir-faire Linux -# (). -# -# 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 . -# -############################################################################## +# © 2014-2015 Savoir-faire Linux (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import backend_adapter from . import binder diff --git a/cmis/unit/backend_adapter.py b/cmis/unit/backend_adapter.py index a7f4252..edd20fe 100644 --- a/cmis/unit/backend_adapter.py +++ b/cmis/unit/backend_adapter.py @@ -1,61 +1,44 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2015 - Present Savoir-faire Linux -# (). -# -# 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 . -# -############################################################################## +# © 2014-2015 Savoir-faire Linux (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp.osv import orm -from openerp.tools.translate import _ -from openerp.addons.connector.unit.backend_adapter import CRUDAdapter -import cmislib.exceptions from cmislib.model import CmisClient +import cmislib.exceptions +from cmislib.browser.binding import BrowserBinding import urllib2 +import logging + +from openerp.tools.translate import _ +from openerp.addons.connector.unit.backend_adapter import CRUDAdapter +from openerp.addons.cmis.exceptions import CMISConnectionError + +logger = logging.getLogger(__name__) class CmisAdapter(CRUDAdapter): - def _auth(self, ids): + def _auth(self, cmis_backend): """Test connection with CMIS""" - if type(ids) is not list: - ids = [ids] - # Get the url, user and password for CMIS - # ids = self.search(cr, uid, []) - if not ids: - raise orm.except_orm( - _('Internal Error'), - _('Something very wrong happened. _auth() ' - 'called without any ids.') - ) - res = self.backend_record.read(['location', 'username', 'password'])[0] - url = res['location'] - user_name = res['username'] - user_password = res['password'] - client = CmisClient(url, user_name, user_password) + cmis_backend.ensure_one() + client = CmisClient( + cmis_backend.location, + cmis_backend.username, + cmis_backend.password, + binding=BrowserBinding()) try: return client.defaultRepository except cmislib.exceptions.ObjectNotFoundException: - raise orm.except_orm(_('CMIS connection Error!'), - _("Check your CMIS account configuration.")) + logger.exception("CMIS backend not found") + raise CMISConnectionError( + _("CMIS backend not found.\n" + "Check your CMIS account configuration.")) except cmislib.exceptions.PermissionDeniedException: - raise orm.except_orm(_('CMIS connection Error!'), - _("Check your CMIS account configuration.")) + logger.exception("Permission denied") + raise CMISConnectionError( + _("Permission denied.\n" + "Check your CMIS account configuration.")) except urllib2.URLError: - raise orm.except_orm(_('CMIS connection Error!'), - _("SERVER is down.")) + logger.exception("UrlError") + raise CMISConnectionError( + _("SERVER is down.")) diff --git a/cmis/unit/binder.py b/cmis/unit/binder.py index bba4907..c6713d9 100644 --- a/cmis/unit/binder.py +++ b/cmis/unit/binder.py @@ -1,130 +1,14 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2015 - Present Savoir-faire Linux -# (). -# -# 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 . -# -############################################################################## +# © 2014-2015 Savoir-faire Linux (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT from openerp.addons.connector.connector import Binder from ..backend import cmis -from datetime import datetime -from openerp.tools import ustr - @cmis class CmisModelBinder(Binder): _model_name = [] - - def to_openerp(self, external_id, unwrap=False): - """ Give the OpenERP ID for an external ID - - :param external_id: external ID for which we want the OpenERP ID - :param unwrap: if True, returns the openerp_id of the dms_xxxx - record, else return the id (binding id) of that record - :return: a record ID, depending on the value of unwrap, - or None if the external_id is not mapped - :rtype: int - """ - with self.session.change_context({'active_test': False}): - binding_ids = self.session.search(self.model._name, [ - ('dms_id', '=', ustr(external_id)), - ('backend_id', '=', self.backend_record.id) - ]) - - if not binding_ids: - return None - - binding_id = binding_ids[0] - - if unwrap: - return self.session.read( - self.model._name, binding_id, ['openerp_id'])['openerp_id'][0] - else: - return binding_id - - def to_backend(self, record_id, wrap=False): - """ Give the external ID for an OpenERP ID - - :param record_id: OpenERP ID for which we want the external id - :param wrap: if False, record_id is the ID of the binding, - if True, record_id is the ID of the normal record, the - method will search the corresponding binding and returns - the backend id of the binding - :return: backend identifier of the record - """ - if wrap: - with self.session.change_context({'active_test': False}): - erp_id = self.session.search( - self.model._name, - [('openerp_id', '=', record_id), - ('backend_id', '=', self.backend_record.id) - ]) - if erp_id: - record_id = erp_id[0] - else: - return None - - dms_record = self.session.read( - self.model._name, record_id, ['dms_id']) - - return dms_record['dms_id'] - - def bind(self, external_id, binding_id): - """ Create the link between an external ID and an OpenERP ID and - update the last synchronization date. - - :param external_id: External ID to bind - :param binding_id: OpenERP ID to bind - :type binding_id: int - """ - # avoid to trigger the export when we modify the `dms_id` - context = self.session.context.copy() - context['connector_no_export'] = True - now_fmt = datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT) - - self.environment.model.write( - self.session.cr, self.session.uid, binding_id, { - 'dms_id': ustr(external_id), - 'sync_date': now_fmt - }, context=context) - - def unwrap_binding(self, binding_id, browse=False): - binding = self.session.read( - self.model._name, binding_id, ['openerp_id']) - - openerp_id = binding['openerp_id'][0] - - if browse: - return self.session.browse(self.unwrap_model(), openerp_id) - - return openerp_id - - def unwrap_model(self): - """ This binder assumes that the normal model - lays in ``openerp_id`` since - this is the field we use in the ``_inherits`` bindings. - """ - try: - column = self.model._columns['openerp_id'] - except KeyError: - raise ValueError('Cannot unwrap model %s, because it has ' - 'no openerp_id field' % self.model._name) - return column._obj + _external_field = 'dms_id' + _backend_field = 'cmis_backend_id' diff --git a/cmis/unit/import_synchronizer.py b/cmis/unit/import_synchronizer.py index d46be6f..8feae1e 100644 --- a/cmis/unit/import_synchronizer.py +++ b/cmis/unit/import_synchronizer.py @@ -1,31 +1,13 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2015 - Present Savoir-faire Linux -# (). -# -# 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 . -# -############################################################################## +# © 2014-2015 Savoir-faire Linux (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import logging -from openerp.addons.connector.unit.synchronizer import ImportSynchronizer +from openerp.addons.connector.unit.synchronizer import Importer _logger = logging.getLogger(__name__) -class CmisImportSynchronizer(ImportSynchronizer): +class CmisImportSynchronizer(Importer): """ Base importer for Dms """ def __init__(self, environment): @@ -100,6 +82,6 @@ def run(self, dms_id, options=None): self.binder.bind(self.dms_id, binding_id) -class CmisBatchImportSynchronizer(ImportSynchronizer): +class CmisBatchImportSynchronizer(Importer): def run(self, filters=None, options=None): raise NotImplementedError diff --git a/cmis/unit/mapper.py b/cmis/unit/mapper.py index 7e233e8..ceebc62 100644 --- a/cmis/unit/mapper.py +++ b/cmis/unit/mapper.py @@ -1,24 +1,6 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2015 - Present Savoir-faire Linux -# (). -# -# 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 . -# -############################################################################## +# © 2014-2015 Savoir-faire Linux (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from openerp.addons.connector.unit.mapper import ImportMapper, mapping from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT @@ -28,8 +10,8 @@ class CmisImportMapper(ImportMapper): @mapping - def backend_id(self, record): - return {'backend_id': self.backend_record.id} + def cmis_backend_id(self, record): + return {'cmis_backend_id': self.backend_record.id} @mapping def updated_on(self, record): diff --git a/cmis/views/cmis_backend_view.xml b/cmis/views/cmis_backend_view.xml index 025ef34..972ed69 100644 --- a/cmis/views/cmis_backend_view.xml +++ b/cmis/views/cmis_backend_view.xml @@ -23,23 +23,11 @@ - - -