From 361c9985928c1163d25c154979717864aaef5cfd Mon Sep 17 00:00:00 2001 From: Valentin Chemiere Date: Wed, 17 Jun 2015 12:14:35 +0200 Subject: [PATCH 01/35] Add file import/export POC external_file_location integration in connector_flow --- external_file_location/README.rst | 59 ++++++++ external_file_location/__init__.py | 27 ++++ external_file_location/__openerp__.py | 49 ++++++ external_file_location/abstract_task.py | 26 ++++ external_file_location/attachment.py | 39 +++++ external_file_location/attachment_view.xml | 99 ++++++++++++ external_file_location/cron.xml | 18 +++ external_file_location/helper.py | 60 ++++++++ external_file_location/location.py | 69 +++++++++ external_file_location/location_view.xml | 69 +++++++++ external_file_location/menu.xml | 12 ++ .../security/ir.model.access.csv | 3 + external_file_location/task.py | 99 ++++++++++++ external_file_location/task_view.xml | 46 ++++++ external_file_location/tasks/__init__.py | 26 ++++ external_file_location/tasks/abstract_fs.py | 140 +++++++++++++++++ external_file_location/tasks/filestore.py | 69 +++++++++ external_file_location/tasks/ftp.py | 67 +++++++++ external_file_location/tasks/sftp.py | 73 +++++++++ external_file_location/tests/__init__.py | 24 +++ external_file_location/tests/mock_server.py | 75 ++++++++++ external_file_location/tests/test_sftp.py | 141 ++++++++++++++++++ 22 files changed, 1290 insertions(+) create mode 100644 external_file_location/README.rst create mode 100644 external_file_location/__init__.py create mode 100644 external_file_location/__openerp__.py create mode 100644 external_file_location/abstract_task.py create mode 100644 external_file_location/attachment.py create mode 100644 external_file_location/attachment_view.xml create mode 100644 external_file_location/cron.xml create mode 100644 external_file_location/helper.py create mode 100644 external_file_location/location.py create mode 100644 external_file_location/location_view.xml create mode 100644 external_file_location/menu.xml create mode 100644 external_file_location/security/ir.model.access.csv create mode 100644 external_file_location/task.py create mode 100644 external_file_location/task_view.xml create mode 100644 external_file_location/tasks/__init__.py create mode 100644 external_file_location/tasks/abstract_fs.py create mode 100644 external_file_location/tasks/filestore.py create mode 100644 external_file_location/tasks/ftp.py create mode 100644 external_file_location/tasks/sftp.py create mode 100644 external_file_location/tests/__init__.py create mode 100644 external_file_location/tests/mock_server.py create mode 100644 external_file_location/tests/test_sftp.py diff --git a/external_file_location/README.rst b/external_file_location/README.rst new file mode 100644 index 000000000..d245fa00a --- /dev/null +++ b/external_file_location/README.rst @@ -0,0 +1,59 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :alt: License + +External File Location +====================== + +This module was written to extend the functionality of ir.attachment to support remote communication and allow you to import/export file to a remote server + +Installation +============ + +To install this module, you need to: + +* fs python module +* Paramiko python module + +Usage +===== + +To use this module, you need to: + +* Add a location with your server infos +* Create a task with your file info and remote communication method +* A cron task will trigger each task + +For further information, please visit: + +* https://www.odoo.com/forum/help-1 + +Known issues / Roadmap +====================== + + +Credits +======= + +* Joel Grand-Guillaume Camptocamp +* initOS +* Valentin CHEMIERE + +Contributors +------------ + +* Sebastien BEAU + +Maintainer +---------- + +* Valentin CHEMIERE + +.. image:: http://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: http://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 http://odoo-community.org. diff --git a/external_file_location/__init__.py b/external_file_location/__init__.py new file mode 100644 index 000000000..101a9f4ad --- /dev/null +++ b/external_file_location/__init__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2014 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# +# 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 . import attachment +from . import location +from . import task +from . import tasks +from . import tests diff --git a/external_file_location/__openerp__.py b/external_file_location/__openerp__.py new file mode 100644 index 000000000..8d286d4e8 --- /dev/null +++ b/external_file_location/__openerp__.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2015 Akretion (http://www.akretion.com). +# @author Valentin CHEMIERE +# +# 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 . +# +############################################################################### + +{ + 'name': 'external_file_location', + 'version': '0.0.1', + 'author': 'Akretion', + 'website': 'www.akretion.com', + 'license': 'AGPL-3', + 'category': 'Generic Modules', + 'depends': [ + 'attachment_metadata', + ], + 'external_dependencies': { + 'python': [ + 'fs', + 'paramiko', + ], + }, + 'data': [ + 'menu.xml', + 'attachment_view.xml', + 'location_view.xml', + 'task_view.xml', + 'cron.xml', + 'security/ir.model.access.csv', + ], + 'installable': True, + 'application': True, + } diff --git a/external_file_location/abstract_task.py b/external_file_location/abstract_task.py new file mode 100644 index 000000000..15e2323e9 --- /dev/null +++ b/external_file_location/abstract_task.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from base64 import b64encode + + +class AbstractTask(object): + + _name = None + _key = None + _synchronize_type = None + _default_port = None + _hide_login = False + _hide_password = False + _hide_port = False + + def create_file(self, filename, data): + ir_attachment_id = self.env['ir.attachment.metadata'].create({ + 'name': filename, + 'datas': b64encode(data), + 'datas_fname': filename, + 'task_id': self.task and self.task.id or False, + 'location_id': self.task and self.task.location_id.id or False, + 'external_hash': self.ext_hash + }) + return ir_attachment_id diff --git a/external_file_location/attachment.py b/external_file_location/attachment.py new file mode 100644 index 000000000..26925547b --- /dev/null +++ b/external_file_location/attachment.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2015 Akretion (http://www.akretion.com). +# @author Valentin CHEMIERE +# +# 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 import models, fields + + +class IrAttachmentMetadata(models.Model): + _inherit = 'ir.attachment.metadata' + + sync_date = fields.Datetime() + state = fields.Selection([ + ('pending', 'Pending'), + ('failed', 'Failed'), + ('done', 'Done'), + ], readonly=False, required=True, default='pending') + state_message = fields.Text() + task_id = fields.Many2one('external.file.task', string='Task') + location_id = fields.Many2one('external.file.location', string='Location', + related='task_id.location_id', store=True + ) diff --git a/external_file_location/attachment_view.xml b/external_file_location/attachment_view.xml new file mode 100644 index 000000000..b11b6c4f6 --- /dev/null +++ b/external_file_location/attachment_view.xml @@ -0,0 +1,99 @@ + + + + + + ir.attachment.metadata + + + + + + + + + + + + + + ir.attachment.metadata + + + + + + + + + + + + + + + ir.attachment.metadata + + + + + + + + + + + + + + + + + + + + + + + + + + Attachments + ir.actions.act_window + ir.attachment.metadata + form + tree,form + + + + + + + + tree + + + + + + + form + + + + + + + + diff --git a/external_file_location/cron.xml b/external_file_location/cron.xml new file mode 100644 index 000000000..a18f5f6a2 --- /dev/null +++ b/external_file_location/cron.xml @@ -0,0 +1,18 @@ + + + + + + Run file exchange tasks + 30 + minutes + -1 + True + + external.file.task + _run + ([]) + + + + diff --git a/external_file_location/helper.py b/external_file_location/helper.py new file mode 100644 index 000000000..6bfe08dcb --- /dev/null +++ b/external_file_location/helper.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Joel Grand-Guillaume +# Copyright 2011-2012 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + + +def itersubclasses(cls, _seen=None): + """ + itersubclasses(cls) + Generator over all subclasses of a given class, in depth first order. + >>> list(itersubclasses(int)) == [bool] + True + >>> class A(object): pass + >>> class B(A): pass + >>> class C(A): pass + >>> class D(B,C): pass + >>> class E(D): pass + >>> + >>> for cls in itersubclasses(A): + ... print(cls.__name__) + B + D + E + C + >>> # get ALL (new-style) classes currently defined + >>> [cls.__name__ for cls in itersubclasses(object)] #doctest: +ELLIPSIS + ['type', ...'tuple', ...] + """ + if not isinstance(cls, type): + raise TypeError('itersubclasses must be called with ' + 'new-style classes, not %.100r' % cls + ) + if _seen is None: + _seen = set() + try: + subs = cls.__subclasses__() + except TypeError: # fails only when cls is type + subs = cls.__subclasses__(cls) + for sub in subs: + if sub not in _seen: + _seen.add(sub) + yield sub + for sub in itersubclasses(sub, _seen): + yield sub diff --git a/external_file_location/location.py b/external_file_location/location.py new file mode 100644 index 000000000..91d4ad2f8 --- /dev/null +++ b/external_file_location/location.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2015 Akretion (http://www.akretion.com). +# @author Valentin CHEMIERE +# +# 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 import models, fields, api +from .abstract_task import AbstractTask +from .helper import itersubclasses + + +class Location(models.Model): + _name = 'external.file.location' + _description = 'Description' + + name = fields.Char(string='Name', required=True) + protocol = fields.Selection(selection='_get_protocol', required=True) + address = fields.Char(string='Address', required=True) + port = fields.Integer() + login = fields.Char() + password = fields.Char() + task_ids = fields.One2many('external.file.task', 'location_id') + hide_login = fields.Boolean() + hide_password = fields.Boolean() + hide_port = fields.Boolean() + + def _get_protocol(self): + res = [] + for cls in itersubclasses(AbstractTask): + if not cls._synchronize_type: + cls_info = (cls._key, cls._name) + res.append(cls_info) + elif not cls._synchronize_type and cls._key and cls._name: + pass + return res + + @api.onchange('protocol') + def onchange_protocol(self): + for cls in itersubclasses(AbstractTask): + if cls._key == self.protocol: + self.port = cls._default_port + if cls._hide_login: + self.hide_login = True + else: + self.hide_login = False + if cls._hide_password: + self.hide_password = True + else: + self.hide_password = False + if cls._hide_port: + self.hide_port = True + else: + self.hide_port = False diff --git a/external_file_location/location_view.xml b/external_file_location/location_view.xml new file mode 100644 index 000000000..5d2ee0a9c --- /dev/null +++ b/external_file_location/location_view.xml @@ -0,0 +1,69 @@ + + + + + + external.file.location + +
+ + +
+
+ + + + + + + + + + + + + + + + +
- + + - - - +
@@ -45,7 +58,6 @@ - @@ -69,33 +81,67 @@
- - Attachments Tasks + + + + Attachments Import Tasks ir.actions.act_window attachment.synchronize.task form tree,form + [('method_type', '=', 'import')] tree - + form - + - + action="action_attachment_import_task"/> + + + + Attachments Export Tasks + ir.actions.act_window + attachment.synchronize.task + form + tree,form + + + [('method_type', '=', 'export')] + + + + + tree + + + + + + + form + + + + + diff --git a/attachment_synchronize/views/storage_backend_view.xml b/attachment_synchronize/views/storage_backend_views.xml similarity index 100% rename from attachment_synchronize/views/storage_backend_view.xml rename to attachment_synchronize/views/storage_backend_views.xml From 61276cff04e1107927aee836f99a2a6df0e11bab Mon Sep 17 00:00:00 2001 From: clementmbr Date: Wed, 8 Jul 2020 12:59:50 -0300 Subject: [PATCH 25/35] [IMP] task run_export + onchange link method_type and file_type if export --- .../models/attachment_queue.py | 8 +++++++- .../models/attachment_synchronize_task.py | 19 +++++++++---------- .../attachment_synchronize_task_views.xml | 13 +++++++++---- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/attachment_synchronize/models/attachment_queue.py b/attachment_synchronize/models/attachment_queue.py index 7a8e3390d..1047ee9f1 100644 --- a/attachment_synchronize/models/attachment_queue.py +++ b/attachment_synchronize/models/attachment_queue.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import os -from odoo import models, fields +from odoo import api, models, fields class AttachmentQueue(models.Model): @@ -30,3 +30,9 @@ class AttachmentQueue(models.Model): if self.task_id.emails: res = self.task_id.emails return res + + @api.onchange("task_id") + def onchange_task_id(self): + for attachment in self: + if attachment.task_id.method_type == "export": + attachment.file_type = "export" diff --git a/attachment_synchronize/models/attachment_synchronize_task.py b/attachment_synchronize/models/attachment_synchronize_task.py index 5d9239938..d1ad764c9 100644 --- a/attachment_synchronize/models/attachment_synchronize_task.py +++ b/attachment_synchronize/models/attachment_synchronize_task.py @@ -100,9 +100,11 @@ class AttachmentSynchronizeTask(models.Model): "when excuting the files linked to this task", ) - def toogle_enabled(self): + @api.onchange("method_type") + def onchange_method_type(self): for task in self: - task.enabled = not task.enabled + if task.method_type == "export": + task.file_type = "export" def _prepare_attachment_vals(self, data, filename): self.ensure_one() @@ -196,17 +198,14 @@ class AttachmentSynchronizeTask(models.Model): ) return list(set(filenames) - set(imported)) + def run_export(self): + for task in self: + task.attachment_ids.filtered(lambda a: a.state == "pending").run() + def button_toogle_enabled(self): for rec in self: rec.enabled = not rec.enabled def button_duplicate_record(self): self.ensure_one() - record = self.copy({"enabled": False}) - return { - "type": "ir.actions.act_window", - "res_model": record.backend_id._name, - "target": "current", - "view_mode": "form", - "res_id": record.backend_id.id, - } + self.copy({"enabled": False}) diff --git a/attachment_synchronize/views/attachment_synchronize_task_views.xml b/attachment_synchronize/views/attachment_synchronize_task_views.xml index 4ca32bbc1..13760d46b 100644 --- a/attachment_synchronize/views/attachment_synchronize_task_views.xml +++ b/attachment_synchronize/views/attachment_synchronize_task_views.xml @@ -7,9 +7,12 @@
+
+
-
-
+ + + + + + + + + + + + + - - - - - - - - - - + + - - - - -
- - +
- + attachment.synchronize.task @@ -70,7 +68,21 @@ + + From 3bbf135d52be3eba0938cfe2650d6695831510ee Mon Sep 17 00:00:00 2001 From: clementmbr Date: Thu, 9 Jul 2020 11:20:05 -0300 Subject: [PATCH 29/35] [IMP] rename task emails into failure_emails and improve tasks views --- .../models/attachment_queue.py | 4 +-- .../models/attachment_synchronize_task.py | 27 ++++++++++------- .../attachment_synchronize_task_views.xml | 29 ++++++++++--------- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/attachment_synchronize/models/attachment_queue.py b/attachment_synchronize/models/attachment_queue.py index 19c173761..48327c76c 100644 --- a/attachment_synchronize/models/attachment_queue.py +++ b/attachment_synchronize/models/attachment_queue.py @@ -28,8 +28,8 @@ class AttachmentQueue(models.Model): def _get_failure_emails(self): res = super()._get_failure_emails() - if self.task_id.emails: - res = self.task_id.emails + if self.task_id.failure_emails: + res = self.task_id.failure_emails return res @api.onchange("task_id") diff --git a/attachment_synchronize/models/attachment_synchronize_task.py b/attachment_synchronize/models/attachment_synchronize_task.py index 649cd03ba..b682a6d4c 100644 --- a/attachment_synchronize/models/attachment_synchronize_task.py +++ b/attachment_synchronize/models/attachment_synchronize_task.py @@ -51,10 +51,11 @@ class AttachmentSynchronizeTask(models.Model): name = fields.Char(required=True) method_type = fields.Selection( - [("import", "Import"), ("export", "Export")], required=True + [("import", "Import Task"), ("export", "Export Task")], required=True ) pattern = fields.Char( - help="Used to select the files to be imported. Import all the files if empty." + string="Selection Pattern", + help="Used to select the files to be imported. If empty, import all the files.", ) filepath = fields.Char( string="File Path", help="Path to imported/exported files in the Backend" @@ -83,16 +84,20 @@ class AttachmentSynchronizeTask(models.Model): file_type = fields.Selection( selection=[], string="File Type", - help="The file type indicates what Odoo will do with the files once imported", + help="The file type allows Odoo to recognize what to do with the files " + "once imported.", ) enabled = fields.Boolean("Enabled", default=True) avoid_duplicated_files = fields.Boolean( - string="Avoid duplicated files importation", - help="If checked, will avoid duplication file import", + string="Avoid importing duplicated files", + help="If checked, a file will not be imported if there is already an " + "Attachment Queue with the same name.", ) - emails = fields.Char( - string="Notification Emails", - help="These emails will receive a notification in case of the task failure", + failure_emails = fields.Char( + string="Failure Emails", + help="Used to fill the field 'Failure Emails' in the task related " + "Attachments Queues.\nThese emails will be notified if any operation on these " + "Attachment Queue's file type fails.", ) def _prepare_attachment_vals(self, data, filename): @@ -111,14 +116,16 @@ class AttachmentSynchronizeTask(models.Model): try: template = mako_template_env.from_string(tools.ustr(template)) except Exception: - _logger.exception("Failed to load template %r", template) + _logger.exception("Failed to load template '{}'".format(template)) variables = {"obj": record} try: render_result = template.render(variables) except Exception: _logger.exception( - "Failed to render template %r using values %r" % (template, variables) + "Failed to render template '{}'' using values '{}'".format( + template, variables + ) ) render_result = u"" if render_result == u"False": diff --git a/attachment_synchronize/views/attachment_synchronize_task_views.xml b/attachment_synchronize/views/attachment_synchronize_task_views.xml index 171027155..5d2da5bd1 100644 --- a/attachment_synchronize/views/attachment_synchronize_task_views.xml +++ b/attachment_synchronize/views/attachment_synchronize_task_views.xml @@ -12,7 +12,7 @@
-
-
+
- - + - + @@ -47,7 +48,7 @@ - + @@ -61,14 +62,14 @@ - + - + - + -
diff --git a/attachment_synchronize/static/description/sftp.png b/attachment_synchronize/static/description/sftp.png deleted file mode 100644 index 665d4258b77dc8c9a1d9a21b67d0b1e77cb4600c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32609 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 Date: Tue, 14 Jul 2020 15:24:54 -0300 Subject: [PATCH 33/35] [FIX] duplicate ids in attachment_synchronize_task_views and flake8 --- attachment_synchronize/__manifest__.py | 2 +- .../models/attachment_synchronize_task.py | 15 ++++++++------- .../views/attachment_synchronize_task_views.xml | 8 ++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/attachment_synchronize/__manifest__.py b/attachment_synchronize/__manifest__.py index 2c8dfa80d..b50774c2b 100644 --- a/attachment_synchronize/__manifest__.py +++ b/attachment_synchronize/__manifest__.py @@ -12,7 +12,7 @@ "category": "Generic Modules", "depends": [ "attachment_queue", - "storage_backend", # https://github.com/OCA/storage + "storage_backend", # https://github.com/OCA/storage ], "data": [ "views/attachment_queue_views.xml", diff --git a/attachment_synchronize/models/attachment_synchronize_task.py b/attachment_synchronize/models/attachment_synchronize_task.py index 257b3c21e..436c79a6a 100644 --- a/attachment_synchronize/models/attachment_synchronize_task.py +++ b/attachment_synchronize/models/attachment_synchronize_task.py @@ -85,20 +85,21 @@ class AttachmentSynchronizeTask(models.Model): file_type = fields.Selection( selection=[], string="File Type", - help="The file type allows Odoo to recognize what to do with the files " - "once imported.", + help="Used to fill the 'File Type' field in the imported 'Attachments Queues'." + "\nFurther operations will be realized on these Attachments Queues depending " + "on their 'File Type' value.", ) enabled = fields.Boolean("Enabled", default=True) avoid_duplicated_files = fields.Boolean( string="Avoid importing duplicated files", - help="If checked, a file will not be imported if there is already an " - "Attachment Queue with the same name.", + help="If checked, a file will not be imported if an Attachment Queue with the " + "same name already exists.", ) failure_emails = fields.Char( string="Failure Emails", - help="Used to fill the 'Failure Emails' fields in the 'Attachments Queues' " - "related to this task.\nThese emails will be notified if any operation on these " - "Attachment Queue's file type fails.", + help="Used to fill the 'Failure Emails' field in the 'Attachments Queues' " + "related to this task.\nAn alert will be sent to these emails if any operation " + "on these Attachment Queue's file type fails.", ) def _prepare_attachment_vals(self, data, filename): diff --git a/attachment_synchronize/views/attachment_synchronize_task_views.xml b/attachment_synchronize/views/attachment_synchronize_task_views.xml index 28a6812ad..7fe1d7999 100644 --- a/attachment_synchronize/views/attachment_synchronize_task_views.xml +++ b/attachment_synchronize/views/attachment_synchronize_task_views.xml @@ -123,14 +123,14 @@ {'default_method_type': 'import'}
- + tree - + form @@ -155,14 +155,14 @@ {'default_method_type': 'export'} - + tree - + form From 83f2c4d434b47bbd4d550a6f300d6c0885f2c836 Mon Sep 17 00:00:00 2001 From: clementmbr Date: Thu, 27 Aug 2020 09:45:52 +0200 Subject: [PATCH 34/35] [UPD] Add akretion maintainers --- attachment_synchronize/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/attachment_synchronize/__manifest__.py b/attachment_synchronize/__manifest__.py index b50774c2b..c816b1f71 100644 --- a/attachment_synchronize/__manifest__.py +++ b/attachment_synchronize/__manifest__.py @@ -8,6 +8,7 @@ "version": "12.0.1.0.0", "author": "Akretion,Odoo Community Association (OCA)", "website": "https://github.com/oca/server-tools", + "maintainers": ["florian-dacosta", "sebastienbeau", "GSLabIt", "bealdav"], "license": "AGPL-3", "category": "Generic Modules", "depends": [ @@ -24,5 +25,4 @@ "demo": ["demo/attachment_synchronize_task_demo.xml"], "installable": True, "development_status": "Beta", - "maintainers": ["florian-dacosta", "GSLabIt", "bealdav"], } From 2bf2d729642fd07cb15b3f0f8dcaf5a3be1964b9 Mon Sep 17 00:00:00 2001 From: David Beal Date: Thu, 27 Aug 2020 16:23:00 +0200 Subject: [PATCH 35/35] FIX attach_synchro: clean demo data --- .travis.yml | 2 ++ .../demo/attachment_synchronize_task_demo.xml | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5e98c7f1a..fa0c7a785 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,8 @@ install: - git clone --depth=1 https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} - travis_install_nightly + # Requirements to test server_environment modules + - printf '[options]\n\nrunning_env = dev\n' > ${HOME}/.openerp_serverrc script: - travis_run_tests diff --git a/attachment_synchronize/demo/attachment_synchronize_task_demo.xml b/attachment_synchronize/demo/attachment_synchronize_task_demo.xml index d3f8b45f3..f8e9879eb 100644 --- a/attachment_synchronize/demo/attachment_synchronize_task_demo.xml +++ b/attachment_synchronize/demo/attachment_synchronize_task_demo.xml @@ -6,7 +6,6 @@ import delete test_import - foo@example.org,bar@example.org @@ -14,7 +13,6 @@ export test_export - foo@example.org,bar@example.org