Browse Source
Merge pull request #516 from akretion/9-external-file-location
Merge pull request #516 from akretion/9-external-file-location
9 external file locationpull/799/head
Dave Lasley
8 years ago
committed by
GitHub
27 changed files with 1841 additions and 0 deletions
-
69external_file_location/README.rst
-
3external_file_location/__init__.py
-
35external_file_location/__openerp__.py
-
18external_file_location/data/cron.xml
-
99external_file_location/demo/task_demo.xml
-
347external_file_location/i18n/external_file_location.pot
-
347external_file_location/i18n/fr.po
-
3external_file_location/models/__init__.py
-
33external_file_location/models/attachment.py
-
68external_file_location/models/location.py
-
212external_file_location/models/task.py
-
5external_file_location/security/ir.model.access.csv
-
3external_file_location/tasks/__init__.py
-
27external_file_location/tasks/filestore.py
-
30external_file_location/tasks/ftp.py
-
30external_file_location/tasks/sftp.py
-
4external_file_location/tests/__init__.py
-
32external_file_location/tests/common.py
-
74external_file_location/tests/mock_server.py
-
50external_file_location/tests/test_filestore.py
-
86external_file_location/tests/test_ftp.py
-
85external_file_location/tests/test_sftp.py
-
28external_file_location/views/attachment_view.xml
-
70external_file_location/views/location_view.xml
-
12external_file_location/views/menu.xml
-
70external_file_location/views/task_view.xml
-
1requirements.txt
@ -0,0 +1,69 @@ |
|||||
|
.. 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 |
||||
|
|
||||
|
====================== |
||||
|
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. |
||||
|
For now, FTP, SFTP and local filestore are handled by the module. |
||||
|
|
||||
|
Installation |
||||
|
============ |
||||
|
|
||||
|
To install this module, you need to: |
||||
|
|
||||
|
* fs python module at version 0.5.4 or under |
||||
|
* 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 |
||||
|
|
||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
||||
|
:alt: Try me on Runbot |
||||
|
:target: https://runbot.odoo-community.org/runbot/149/9.0 |
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
Bugs are tracked on `GitHub Issues |
||||
|
<https://github.com/OCA/server-tools/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 <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_. |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Valentin CHEMIERE <valentin.chemiere@akretion.com> |
||||
|
* Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com> |
||||
|
* Florian DA COSTA <florian.dacosta@akretion.com> |
||||
|
|
||||
|
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. |
@ -0,0 +1,3 @@ |
|||||
|
from . import models |
||||
|
from . import tasks |
||||
|
from . import tests |
@ -0,0 +1,35 @@ |
|||||
|
# coding: utf-8 |
||||
|
# @ 2016 florian DA COSTA @ Akretion |
||||
|
# © 2016 @author Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
{ |
||||
|
'name': 'External File Location', |
||||
|
'version': '9.0.1.0.0', |
||||
|
'author': 'Akretion,Odoo Community Association (OCA)', |
||||
|
'website': 'www.akretion.com', |
||||
|
'license': 'AGPL-3', |
||||
|
'category': 'Generic Modules', |
||||
|
'depends': [ |
||||
|
'attachment_base_synchronize', |
||||
|
], |
||||
|
'external_dependencies': { |
||||
|
'python': [ |
||||
|
'fs', |
||||
|
'paramiko', |
||||
|
], |
||||
|
}, |
||||
|
'data': [ |
||||
|
'views/menu.xml', |
||||
|
'views/attachment_view.xml', |
||||
|
'views/location_view.xml', |
||||
|
'views/task_view.xml', |
||||
|
'data/cron.xml', |
||||
|
'security/ir.model.access.csv', |
||||
|
], |
||||
|
'demo': [ |
||||
|
'demo/task_demo.xml', |
||||
|
], |
||||
|
'installable': True, |
||||
|
'application': False, |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
|
||||
|
<record model="ir.cron" id="cronjob_run_exchange_tasks"> |
||||
|
<field name='name'>Run file exchange tasks</field> |
||||
|
<field name='interval_number'>30</field> |
||||
|
<field name='interval_type'>minutes</field> |
||||
|
<field name="numbercall">-1</field> |
||||
|
<field name="active">True</field> |
||||
|
<field name="doall" eval="False" /> |
||||
|
<field name="model">external.file.task</field> |
||||
|
<field name="function">run_task_scheduler</field> |
||||
|
<field name="args">([[('method_type', '=', 'import')]])</field> |
||||
|
</record> |
||||
|
|
||||
|
</data> |
||||
|
</openerp> |
@ -0,0 +1,99 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
|
||||
|
<record id="location_ftp" model="external.file.location"> |
||||
|
<field name="name">TEST FTP</field> |
||||
|
<field name="protocol">ftp</field> |
||||
|
<field name="address">my-ftp-address</field> |
||||
|
<field name="login">my-ftp-user</field> |
||||
|
<field name="password">my-ftp-password</field> |
||||
|
<field name="port">21</field> |
||||
|
</record> |
||||
|
<record id="location_sftp" model="external.file.location"> |
||||
|
<field name="name">TEST SFTP</field> |
||||
|
<field name="protocol">sftp</field> |
||||
|
<field name="address">my-sftp-address</field> |
||||
|
<field name="login">my-sftp-user</field> |
||||
|
<field name="password">my-sftp-password</field> |
||||
|
<field name="port">22</field> |
||||
|
</record> |
||||
|
<record id="location_filestore" model="external.file.location"> |
||||
|
<field name="name">TEST File Store</field> |
||||
|
<field name="protocol">file_store</field> |
||||
|
<field name="filestore_rootpath">/</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="ftp_import_task" model="external.file.task"> |
||||
|
<field name="method_type">import</field> |
||||
|
<field name="location_id" eval="ref('external_file_location.location_ftp')"/> |
||||
|
<field name="filename">test-import-ftp.txt</field> |
||||
|
<field name="filepath">/home/user/test</field> |
||||
|
<field name="name">Import FTP Task</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="ftp_export_task" model="external.file.task"> |
||||
|
<field name="method_type">export</field> |
||||
|
<field name="location_id" eval="ref('external_file_location.location_ftp')"/> |
||||
|
<field name="filepath">/home/user/test</field> |
||||
|
<field name="name">Export FTP Task</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="sftp_import_task" model="external.file.task"> |
||||
|
<field name="method_type">import</field> |
||||
|
<field name="location_id" eval="ref('external_file_location.location_sftp')"/> |
||||
|
<field name="filename">test-import-sftp.txt</field> |
||||
|
<field name="filepath">/home/user/test</field> |
||||
|
<field name="name">Import SFTP Task</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="sftp_export_task" model="external.file.task"> |
||||
|
<field name="method_type">export</field> |
||||
|
<field name="location_id" eval="ref('external_file_location.location_sftp')"/> |
||||
|
<field name="filepath">/home/user/test</field> |
||||
|
<field name="name">Export SFTP Task</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="filestore_import_task" model="external.file.task"> |
||||
|
<field name="method_type">import</field> |
||||
|
<field name="location_id" eval="ref('external_file_location.location_filestore')"/> |
||||
|
<field name="filename">test-import-filestore.txt</field> |
||||
|
<field name="filepath">/home/user/test</field> |
||||
|
<field name="name">Import filestore Task</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="filestore_export_task" model="external.file.task"> |
||||
|
<field name="method_type">export</field> |
||||
|
<field name="location_id" eval="ref('external_file_location.location_filestore')"/> |
||||
|
<field name="filepath">/home/user/test</field> |
||||
|
<field name="name">Export filestore Task</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="ir_attachment_export_file_sftp" model="ir.attachment.metadata"> |
||||
|
<field name="name">Sftp text export file</field> |
||||
|
<field name="datas">dGVzdCBzZnRwIGZpbGUgZXhwb3J0</field> |
||||
|
<field name="datas_fname">sftp_test_export.txt</field> |
||||
|
<field name="task_id" eval="ref('external_file_location.sftp_export_task')"/> |
||||
|
<field name="file_type">export_external_location</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="ir_attachment_export_file_ftp" model="ir.attachment.metadata"> |
||||
|
<field name="name">ftp text export file</field> |
||||
|
<field name="datas">dGVzdCBmdHAgZmlsZSBleHBvcnQ=</field> |
||||
|
<field name="datas_fname">ftp_test_export.txt</field> |
||||
|
<field name="task_id" eval="ref('external_file_location.ftp_export_task')"/> |
||||
|
<field name="file_type">export_external_location</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="ir_attachment_export_file_filestore" model="ir.attachment.metadata"> |
||||
|
<field name="name">filestore text export file</field> |
||||
|
<field name="datas">dGVzdCBmaWxlc3RvcmUgZmlsZSBleHBvcnQ=</field> |
||||
|
<field name="datas_fname">filestore_test_export.txt</field> |
||||
|
<field name="task_id" eval="ref('external_file_location.filestore_export_task')"/> |
||||
|
<field name="file_type">export_external_location</field> |
||||
|
</record> |
||||
|
|
||||
|
|
||||
|
|
||||
|
</data> |
||||
|
</openerp> |
@ -0,0 +1,347 @@ |
|||||
|
# Translation of Odoo Server. |
||||
|
# This file contains the translation of the following modules: |
||||
|
# * external_file_location |
||||
|
# |
||||
|
msgid "" |
||||
|
msgstr "" |
||||
|
"Project-Id-Version: Odoo Server 9.0c\n" |
||||
|
"Report-Msgid-Bugs-To: \n" |
||||
|
"POT-Creation-Date: 2016-08-10 16:51+0000\n" |
||||
|
"PO-Revision-Date: 2016-08-10 16:51+0000\n" |
||||
|
"Last-Translator: <>\n" |
||||
|
"Language-Team: \n" |
||||
|
"MIME-Version: 1.0\n" |
||||
|
"Content-Type: text/plain; charset=UTF-8\n" |
||||
|
"Content-Transfer-Encoding: \n" |
||||
|
"Plural-Forms: \n" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_needaction |
||||
|
msgid "Action Needed" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_task_after_import |
||||
|
msgid "Action after import a file" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_address |
||||
|
msgid "Address" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_after_import |
||||
|
msgid "After import" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_attachment_ids |
||||
|
msgid "Attachment" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_task_md5_check |
||||
|
msgid "Control file integrity after import with a md5 file" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_create_uid |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_create_uid |
||||
|
msgid "Created by" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_create_date |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_create_date |
||||
|
msgid "Created on" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_task_form |
||||
|
msgid "Data importation setting" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_ir_attachment_metadata_message_last_post |
||||
|
msgid "Date of the last message posted on the record." |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model,name:external_file_location.model_external_file_location |
||||
|
msgid "Description" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_display_name |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_display_name |
||||
|
msgid "Display Name" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: selection:external.file.task,method_type:0 |
||||
|
msgid "Export" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model,name:external_file_location.model_external_file_task |
||||
|
msgid "External file task" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_location_tree |
||||
|
msgid "File Location" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.actions.act_window,name:external_file_location.action_location |
||||
|
#: model:ir.ui.menu,name:external_file_location.menu_ir_location |
||||
|
msgid "File Locations" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.ui.menu,name:external_file_location.menu_file_exchange |
||||
|
msgid "File exchange" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_task_filename |
||||
|
msgid "File name which is imported.You can use file pattern like *.txtto import all txt files" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_file_type |
||||
|
msgid "File type" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_filestore_rootpath |
||||
|
msgid "FileStore Root Path" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_filename |
||||
|
msgid "Filename" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_filepath |
||||
|
msgid "Filepath" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_follower_ids |
||||
|
msgid "Followers" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_channel_ids |
||||
|
msgid "Followers (Channels)" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_partner_ids |
||||
|
msgid "Followers (Partners)" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_hide_login |
||||
|
msgid "Hide login" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_hide_password |
||||
|
msgid "Hide password" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_hide_port |
||||
|
msgid "Hide port" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_id |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_id |
||||
|
msgid "ID" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_ir_attachment_metadata_message_unread |
||||
|
msgid "If checked new messages require your attention." |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_ir_attachment_metadata_message_needaction |
||||
|
msgid "If checked, new messages require your attention." |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: selection:external.file.task,method_type:0 |
||||
|
msgid "Import" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_task_move_path |
||||
|
msgid "Imported File will be moved to this path" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_task_new_name |
||||
|
msgid "Imported File will be renamed to this nameName can use mako template where obj is an ir_attachement. template exemple : ${obj.name}-${obj.create_date}.csv" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_is_follower |
||||
|
msgid "Is Follower" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_last_post |
||||
|
msgid "Last Message Date" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location___last_update |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task___last_update |
||||
|
msgid "Last Modified on" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_write_uid |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_write_uid |
||||
|
msgid "Last Updated by" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_write_date |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_write_date |
||||
|
msgid "Last Updated on" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_location_id |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_location_id |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_location_form |
||||
|
msgid "Location" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_login |
||||
|
msgid "Login" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_md5_check |
||||
|
msgid "Md5 check" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_ids |
||||
|
msgid "Messages" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_method_type |
||||
|
msgid "Method type" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_move_path |
||||
|
msgid "Move path" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_name |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_name |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_location_form |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_task_form |
||||
|
msgid "Name" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_new_name |
||||
|
msgid "New name" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_needaction_counter |
||||
|
msgid "Number of Actions" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_ir_attachment_metadata_message_needaction_counter |
||||
|
msgid "Number of messages which requires an action" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_ir_attachment_metadata_message_unread_counter |
||||
|
msgid "Number of unread messages" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_password |
||||
|
msgid "Password" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_task_filepath |
||||
|
msgid "Path to imported/exported file" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_port |
||||
|
msgid "Port" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_protocol |
||||
|
msgid "Protocol" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_task_form |
||||
|
msgid "Run" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_location_filestore_rootpath |
||||
|
msgid "Server's root path" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_task_id |
||||
|
msgid "Task" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_task_ids |
||||
|
msgid "Task ids" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_location_form |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_task_form |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_task_tree |
||||
|
msgid "Tasks" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_task_file_type |
||||
|
msgid "The file type determines an import method to be used to parse and transform data before their import in ERP" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_unread |
||||
|
msgid "Unread Messages" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_unread_counter |
||||
|
msgid "Unread Messages Counter" |
||||
|
msgstr "" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model,name:external_file_location.model_ir_attachment_metadata |
||||
|
msgid "ir.attachment.metadata" |
||||
|
msgstr "" |
||||
|
|
@ -0,0 +1,347 @@ |
|||||
|
# Translation of Odoo Server. |
||||
|
# This file contains the translation of the following modules: |
||||
|
# * external_file_location |
||||
|
# |
||||
|
msgid "" |
||||
|
msgstr "" |
||||
|
"Project-Id-Version: Odoo Server 9.0c\n" |
||||
|
"Report-Msgid-Bugs-To: \n" |
||||
|
"POT-Creation-Date: 2016-08-10 16:51+0000\n" |
||||
|
"PO-Revision-Date: 2016-08-10 16:51+0000\n" |
||||
|
"Last-Translator: <>\n" |
||||
|
"Language-Team: \n" |
||||
|
"MIME-Version: 1.0\n" |
||||
|
"Content-Type: text/plain; charset=UTF-8\n" |
||||
|
"Content-Transfer-Encoding: \n" |
||||
|
"Plural-Forms: \n" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_needaction |
||||
|
msgid "Action Needed" |
||||
|
msgstr "A besoin d'une action" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_task_after_import |
||||
|
msgid "Action after import a file" |
||||
|
msgstr "Action après l'import du fichier" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_address |
||||
|
msgid "Address" |
||||
|
msgstr "Addresse" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_after_import |
||||
|
msgid "After import" |
||||
|
msgstr "Après import" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_attachment_ids |
||||
|
msgid "Attachment" |
||||
|
msgstr "Pièce jointe" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_task_md5_check |
||||
|
msgid "Control file integrity after import with a md5 file" |
||||
|
msgstr "Contrôle l'intégrité du fichier après l'import avec un fichier md5" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_create_uid |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_create_uid |
||||
|
msgid "Created by" |
||||
|
msgstr "Créé par" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_create_date |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_create_date |
||||
|
msgid "Created on" |
||||
|
msgstr "Créé le" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_task_form |
||||
|
msgid "Data importation setting" |
||||
|
msgstr "Data importation setting" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_ir_attachment_metadata_message_last_post |
||||
|
msgid "Date of the last message posted on the record." |
||||
|
msgstr "Date du dernier message publié sur cet enregistrement" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model,name:external_file_location.model_external_file_location |
||||
|
msgid "Description" |
||||
|
msgstr "Description" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_display_name |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_display_name |
||||
|
msgid "Display Name" |
||||
|
msgstr "Afficher le nom" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: selection:external.file.task,method_type:0 |
||||
|
msgid "Export" |
||||
|
msgstr "Export" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model,name:external_file_location.model_external_file_task |
||||
|
msgid "External file task" |
||||
|
msgstr "Tache" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_location_tree |
||||
|
msgid "File Location" |
||||
|
msgstr "Emplacement fichier" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.actions.act_window,name:external_file_location.action_location |
||||
|
#: model:ir.ui.menu,name:external_file_location.menu_ir_location |
||||
|
msgid "File Locations" |
||||
|
msgstr "Emplacements fichiers" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.ui.menu,name:external_file_location.menu_file_exchange |
||||
|
msgid "File exchange" |
||||
|
msgstr "Echange de fichier" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_task_filename |
||||
|
msgid "File name which is imported.You can use file pattern like *.txtto import all txt files" |
||||
|
msgstr "Nom du fichier importé. Vous pouvez utiliser une expression comme *.txt pour importer tous les fichiers txt" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_file_type |
||||
|
msgid "File type" |
||||
|
msgstr "Type de fichier" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_filestore_rootpath |
||||
|
msgid "FileStore Root Path" |
||||
|
msgstr "Emplacement racine" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_filename |
||||
|
msgid "Filename" |
||||
|
msgstr "Nom du fichier" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_filepath |
||||
|
msgid "Filepath" |
||||
|
msgstr "Chemin" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_follower_ids |
||||
|
msgid "Followers" |
||||
|
msgstr "Abonnés" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_channel_ids |
||||
|
msgid "Followers (Channels)" |
||||
|
msgstr "Abonnés (Canaux)" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_partner_ids |
||||
|
msgid "Followers (Partners)" |
||||
|
msgstr "Abonnés (Partenaires)" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_hide_login |
||||
|
msgid "Hide login" |
||||
|
msgstr "Cacher le login" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_hide_password |
||||
|
msgid "Hide password" |
||||
|
msgstr "Cacher le mot de passe" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_hide_port |
||||
|
msgid "Hide port" |
||||
|
msgstr "Cacher le port" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_id |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_id |
||||
|
msgid "ID" |
||||
|
msgstr "Identifiant" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_ir_attachment_metadata_message_unread |
||||
|
msgid "If checked new messages require your attention." |
||||
|
msgstr "Si coché, de nouveaux messages demandent votre attention." |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_ir_attachment_metadata_message_needaction |
||||
|
msgid "If checked, new messages require your attention." |
||||
|
msgstr "si elle est cochée, de nouveaux messages requièrent votre attention." |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: selection:external.file.task,method_type:0 |
||||
|
msgid "Import" |
||||
|
msgstr "Import" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_task_move_path |
||||
|
msgid "Imported File will be moved to this path" |
||||
|
msgstr "Le fichier importé sera déplacé dans cet emplacement" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_task_new_name |
||||
|
msgid "Imported File will be renamed to this nameName can use mako template where obj is an ir_attachement. template exemple : ${obj.name}-${obj.create_date}.csv" |
||||
|
msgstr "Imported File will be renamed to this nameName can use mako template where obj is an ir_attachement. template exemple : ${obj.name}-${obj.create_date}.csv" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_is_follower |
||||
|
msgid "Is Follower" |
||||
|
msgstr "Est un abonné" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_last_post |
||||
|
msgid "Last Message Date" |
||||
|
msgstr "Date du dernier message" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location___last_update |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task___last_update |
||||
|
msgid "Last Modified on" |
||||
|
msgstr "Dernière modification le" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_write_uid |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_write_uid |
||||
|
msgid "Last Updated by" |
||||
|
msgstr "Dernière mise à jour par" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_write_date |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_write_date |
||||
|
msgid "Last Updated on" |
||||
|
msgstr "Dernière mise à jour le" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_location_id |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_location_id |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_location_form |
||||
|
msgid "Location" |
||||
|
msgstr "Emplacement" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_login |
||||
|
msgid "Login" |
||||
|
msgstr "Identifiant" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_md5_check |
||||
|
msgid "Md5 check" |
||||
|
msgstr "Md5 check" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_ids |
||||
|
msgid "Messages" |
||||
|
msgstr "Messages" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_method_type |
||||
|
msgid "Method type" |
||||
|
msgstr "Type de méthode" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_move_path |
||||
|
msgid "Move path" |
||||
|
msgstr "chemin des archives" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_name |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_name |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_location_form |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_task_form |
||||
|
msgid "Name" |
||||
|
msgstr "Nom" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_task_new_name |
||||
|
msgid "New name" |
||||
|
msgstr "Nouveau nom" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_needaction_counter |
||||
|
msgid "Number of Actions" |
||||
|
msgstr "Nombre d'Actions" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_ir_attachment_metadata_message_needaction_counter |
||||
|
msgid "Number of messages which requires an action" |
||||
|
msgstr "Nombre de messages demandant une action" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_ir_attachment_metadata_message_unread_counter |
||||
|
msgid "Number of unread messages" |
||||
|
msgstr "Nombre de messages non lus" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_password |
||||
|
msgid "Password" |
||||
|
msgstr "Mot de passe" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_task_filepath |
||||
|
msgid "Path to imported/exported file" |
||||
|
msgstr "Path to imported/exported file" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_port |
||||
|
msgid "Port" |
||||
|
msgstr "Port" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_protocol |
||||
|
msgid "Protocol" |
||||
|
msgstr "Protocole" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_task_form |
||||
|
msgid "Run" |
||||
|
msgstr "Run" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_location_filestore_rootpath |
||||
|
msgid "Server's root path" |
||||
|
msgstr "Chemin racine du serveur" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_task_id |
||||
|
msgid "Task" |
||||
|
msgstr "Tache" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_external_file_location_task_ids |
||||
|
msgid "Task ids" |
||||
|
msgstr "Task ids" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_location_form |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_task_form |
||||
|
#: model:ir.ui.view,arch_db:external_file_location.view_task_tree |
||||
|
msgid "Tasks" |
||||
|
msgstr "Taches" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,help:external_file_location.field_external_file_task_file_type |
||||
|
msgid "The file type determines an import method to be used to parse and transform data before their import in ERP" |
||||
|
msgstr "Le type de fichier détermine la méthode d'import utilisée pour parser le fichier et transformer les données avant l'import dans l'ERP" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_unread |
||||
|
msgid "Unread Messages" |
||||
|
msgstr "Messages non lus" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model.fields,field_description:external_file_location.field_ir_attachment_metadata_message_unread_counter |
||||
|
msgid "Unread Messages Counter" |
||||
|
msgstr "Compteur de messages non lus" |
||||
|
|
||||
|
#. module: external_file_location |
||||
|
#: model:ir.model,name:external_file_location.model_ir_attachment_metadata |
||||
|
msgid "ir.attachment.metadata" |
||||
|
msgstr "ir.attachment.metadata" |
||||
|
|
@ -0,0 +1,3 @@ |
|||||
|
from . import attachment |
||||
|
from . import location |
||||
|
from . import task |
@ -0,0 +1,33 @@ |
|||||
|
# coding: utf-8 |
||||
|
# @ 2016 Florian DA COSTA @ Akretion |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
from openerp import models, fields, api |
||||
|
import base64 |
||||
|
import os |
||||
|
|
||||
|
|
||||
|
class IrAttachmentMetadata(models.Model): |
||||
|
_inherit = 'ir.attachment.metadata' |
||||
|
|
||||
|
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) |
||||
|
file_type = fields.Selection( |
||||
|
selection_add=[ |
||||
|
('export_external_location', |
||||
|
'Export File (External location)') |
||||
|
]) |
||||
|
|
||||
|
@api.multi |
||||
|
def _run(self): |
||||
|
super(IrAttachmentMetadata, self)._run() |
||||
|
if self.file_type == 'export_external_location': |
||||
|
protocols = self.env['external.file.location']._get_classes() |
||||
|
location = self.location_id |
||||
|
cls = protocols.get(location.protocol)[1] |
||||
|
path = os.path.join(self.task_id.filepath, self.datas_fname) |
||||
|
with cls.connect(location) as conn: |
||||
|
datas = base64.decodestring(self.datas) |
||||
|
conn.setcontents(path, data=datas) |
@ -0,0 +1,68 @@ |
|||||
|
# coding: utf-8 |
||||
|
# @ 2015 Valentin CHEMIERE @ Akretion |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
from openerp import models, fields, api |
||||
|
from ..tasks.filestore import FileStoreTask |
||||
|
from ..tasks.ftp import FtpTask |
||||
|
from ..tasks.sftp import SftpTask |
||||
|
|
||||
|
|
||||
|
class Location(models.Model): |
||||
|
_name = 'external.file.location' |
||||
|
_description = 'Location' |
||||
|
|
||||
|
name = fields.Char(string='Name', required=True) |
||||
|
protocol = fields.Selection(selection='_get_protocol', required=True) |
||||
|
address = fields.Char( |
||||
|
string='Address') |
||||
|
filestore_rootpath = fields.Char( |
||||
|
string='FileStore Root Path', |
||||
|
help="Server's root path") |
||||
|
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() |
||||
|
company_id = fields.Many2one( |
||||
|
'res.company', 'Company', |
||||
|
default=lambda self: self.env['res.company']._company_default_get( |
||||
|
'external.file.location')) |
||||
|
|
||||
|
@api.model |
||||
|
def _get_classes(self): |
||||
|
"surcharge this method to add new protocols" |
||||
|
return { |
||||
|
'ftp': ('FTP', FtpTask), |
||||
|
'sftp': ('SFTP', SftpTask), |
||||
|
'file_store': ('File Store', FileStoreTask), |
||||
|
} |
||||
|
|
||||
|
@api.model |
||||
|
def _get_protocol(self): |
||||
|
protocols = self._get_classes() |
||||
|
selection = [] |
||||
|
for key, val in protocols.iteritems(): |
||||
|
selection.append((key, val[0])) |
||||
|
return selection |
||||
|
|
||||
|
@api.onchange('protocol') |
||||
|
def onchange_protocol(self): |
||||
|
protocols = self._get_classes() |
||||
|
if self.protocol: |
||||
|
cls = protocols.get(self.protocol)[1] |
||||
|
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 |
@ -0,0 +1,212 @@ |
|||||
|
# coding: utf-8 |
||||
|
# @ 2015 Valentin CHEMIERE @ Akretion |
||||
|
# © @author Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
from openerp import models, fields, api |
||||
|
import openerp |
||||
|
from openerp import tools |
||||
|
from base64 import b64encode |
||||
|
import os |
||||
|
import datetime |
||||
|
import logging |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
|
||||
|
try: |
||||
|
# We use a jinja2 sandboxed environment to render mako templates. |
||||
|
# Note that the rendering does not cover all the mako syntax, in particular |
||||
|
# arbitrary Python statements are not accepted, and not all expressions are |
||||
|
# allowed: only "public" attributes (not starting with '_') of objects may |
||||
|
# be accessed. |
||||
|
# This is done on purpose: it prevents incidental or malicious execution of |
||||
|
# Python code that may break the security of the server. |
||||
|
from jinja2.sandbox import SandboxedEnvironment |
||||
|
mako_template_env = SandboxedEnvironment( |
||||
|
variable_start_string="${", |
||||
|
variable_end_string="}", |
||||
|
line_statement_prefix="%", |
||||
|
trim_blocks=True, # do not output newline after blocks |
||||
|
) |
||||
|
mako_template_env.globals.update({ |
||||
|
'str': str, |
||||
|
'datetime': datetime, |
||||
|
'len': len, |
||||
|
'abs': abs, |
||||
|
'min': min, |
||||
|
'max': max, |
||||
|
'sum': sum, |
||||
|
'filter': filter, |
||||
|
'reduce': reduce, |
||||
|
'map': map, |
||||
|
'round': round, |
||||
|
}) |
||||
|
except ImportError: |
||||
|
_logger.warning("jinja2 not available, templating features will not work!") |
||||
|
|
||||
|
|
||||
|
class Task(models.Model): |
||||
|
_name = 'external.file.task' |
||||
|
_description = 'External file task' |
||||
|
|
||||
|
name = fields.Char(required=True) |
||||
|
|
||||
|
method_type = fields.Selection( |
||||
|
[('import', 'Import'), ('export', 'Export')], |
||||
|
required=True) |
||||
|
|
||||
|
filename = fields.Char(help='File name which is imported.' |
||||
|
'You can use file pattern like *.txt' |
||||
|
'to import all txt files') |
||||
|
filepath = fields.Char(help='Path to imported/exported file') |
||||
|
|
||||
|
location_id = fields.Many2one('external.file.location', string='Location', |
||||
|
required=True) |
||||
|
|
||||
|
attachment_ids = fields.One2many('ir.attachment.metadata', 'task_id', |
||||
|
string='Attachment') |
||||
|
|
||||
|
move_path = fields.Char(string='Move Path', |
||||
|
help='Imported File will be moved to this path') |
||||
|
|
||||
|
new_name = fields.Char(string='New Name', |
||||
|
help='Imported File will be renamed to this name' |
||||
|
'Name can use mako template where obj is an ' |
||||
|
'ir_attachement. template exemple : ' |
||||
|
' ${obj.name}-${obj.create_date}.csv') |
||||
|
|
||||
|
md5_check = fields.Boolean(help='Control file integrity after import with' |
||||
|
' a md5 file') |
||||
|
|
||||
|
after_import = fields.Selection(selection='_get_action', |
||||
|
help='Action after import a file') |
||||
|
|
||||
|
company_id = fields.Many2one( |
||||
|
'res.company', 'Company', |
||||
|
default=lambda self: self.env['res.company']._company_default_get( |
||||
|
'external.file.task')) |
||||
|
|
||||
|
file_type = fields.Selection( |
||||
|
selection=[], |
||||
|
string="File Type", |
||||
|
help="The file type determines an import method to be used " |
||||
|
"to parse and transform data before their import in ERP") |
||||
|
|
||||
|
active = fields.Boolean(default=True) |
||||
|
|
||||
|
def _get_action(self): |
||||
|
return [('rename', 'Rename'), |
||||
|
('move', 'Move'), |
||||
|
('move_rename', 'Move & Rename'), |
||||
|
('delete', 'Delete'), |
||||
|
] |
||||
|
|
||||
|
@api.multi |
||||
|
def _prepare_attachment_vals(self, datas, filename, md5_datas): |
||||
|
self.ensure_one() |
||||
|
vals = { |
||||
|
'name': filename, |
||||
|
'datas': b64encode(datas), |
||||
|
'datas_fname': filename, |
||||
|
'task_id': self.id, |
||||
|
'external_hash': md5_datas, |
||||
|
'file_type': self.file_type or False, |
||||
|
} |
||||
|
return vals |
||||
|
|
||||
|
@api.model |
||||
|
def _template_render(self, template, record): |
||||
|
try: |
||||
|
template = mako_template_env.from_string(tools.ustr(template)) |
||||
|
except Exception: |
||||
|
_logger.exception("Failed to load template %r", template) |
||||
|
|
||||
|
variables = {'obj': record} |
||||
|
try: |
||||
|
render_result = template.render(variables) |
||||
|
except Exception: |
||||
|
_logger.exception( |
||||
|
"Failed to render template %r using values %r" % |
||||
|
(template, variables)) |
||||
|
render_result = u"" |
||||
|
if render_result == u"False": |
||||
|
render_result = u"" |
||||
|
return render_result |
||||
|
|
||||
|
@api.model |
||||
|
def run_task_scheduler(self, domain=None): |
||||
|
if domain is None: |
||||
|
domain = [] |
||||
|
tasks = self.env['external.file.task'].search(domain) |
||||
|
for task in tasks: |
||||
|
if task.method_type == 'import': |
||||
|
task.run_import() |
||||
|
elif task.method_type == 'export': |
||||
|
task.run_export() |
||||
|
|
||||
|
@api.multi |
||||
|
def run_import(self): |
||||
|
self.ensure_one() |
||||
|
protocols = self.env['external.file.location']._get_classes() |
||||
|
cls = protocols.get(self.location_id.protocol)[1] |
||||
|
attach_obj = self.env['ir.attachment.metadata'] |
||||
|
with cls.connect(self.location_id) as conn: |
||||
|
md5_datas = '' |
||||
|
for file_name in conn.listdir(path=self.filepath, |
||||
|
wildcard=self.filename or '', |
||||
|
files_only=True): |
||||
|
with api.Environment.manage(): |
||||
|
with openerp.registry( |
||||
|
self.env.cr.dbname).cursor() as new_cr: |
||||
|
new_env = api.Environment(new_cr, self.env.uid, |
||||
|
self.env.context) |
||||
|
try: |
||||
|
full_path = os.path.join(self.filepath, file_name) |
||||
|
file_data = conn.open(full_path, 'rb') |
||||
|
datas = file_data.read() |
||||
|
if self.md5_check: |
||||
|
md5_file = conn.open(full_path + '.md5', 'rb') |
||||
|
md5_datas = md5_file.read().rstrip('\r\n') |
||||
|
attach_vals = self._prepare_attachment_vals( |
||||
|
datas, file_name, md5_datas) |
||||
|
attachment = attach_obj.with_env(new_env).create( |
||||
|
attach_vals) |
||||
|
new_full_path = False |
||||
|
if self.after_import == 'rename': |
||||
|
new_name = self._template_render( |
||||
|
self.new_name, attachment) |
||||
|
new_full_path = os.path.join( |
||||
|
self.filepath, new_name) |
||||
|
elif self.after_import == 'move': |
||||
|
new_full_path = os.path.join( |
||||
|
self.move_path, file_name) |
||||
|
elif self.after_import == 'move_rename': |
||||
|
new_name = self._template_render( |
||||
|
self.new_name, attachment) |
||||
|
new_full_path = os.path.join( |
||||
|
self.move_path, new_name) |
||||
|
if new_full_path: |
||||
|
conn.rename(full_path, new_full_path) |
||||
|
if self.md5_check: |
||||
|
conn.rename( |
||||
|
full_path + '.md5', |
||||
|
new_full_path + '/md5') |
||||
|
if self.after_import == 'delete': |
||||
|
conn.remove(full_path) |
||||
|
if self.md5_check: |
||||
|
conn.remove(full_path + '.md5') |
||||
|
except Exception, e: |
||||
|
new_env.cr.rollback() |
||||
|
raise e |
||||
|
else: |
||||
|
new_env.cr.commit() |
||||
|
|
||||
|
@api.multi |
||||
|
def run_export(self): |
||||
|
self.ensure_one() |
||||
|
attachment_obj = self.env['ir.attachment.metadata'] |
||||
|
attachments = attachment_obj.search( |
||||
|
[('task_id', '=', self.id), ('state', '!=', 'done')]) |
||||
|
for attachment in attachments: |
||||
|
attachment.run() |
@ -0,0 +1,5 @@ |
|||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink |
||||
|
access_external_file_location_manager,external.file.location.manager,model_external_file_location,base.group_system,1,1,1,1 |
||||
|
access_external_file_location_user,external.file.location.user,model_external_file_location,base.group_user,1,0,0,0 |
||||
|
access_external_file_task_manager,external.file.task.manager,model_external_file_task,base.group_system,1,1,1,1 |
||||
|
access_external_file_task_user,external.file.task.user,model_external_file_task,base.group_user,1,0,0,0 |
@ -0,0 +1,3 @@ |
|||||
|
from . import ftp |
||||
|
from . import sftp |
||||
|
from . import filestore |
@ -0,0 +1,27 @@ |
|||||
|
# coding: utf-8 |
||||
|
# @ 2016 Florian DA COSTA @ Akretion |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
import logging |
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
try: |
||||
|
from fs import osfs |
||||
|
except ImportError: |
||||
|
_logger.debug('Cannot `import fs`.') |
||||
|
|
||||
|
|
||||
|
class FileStoreTask(osfs.OSFS): |
||||
|
|
||||
|
_key = 'filestore' |
||||
|
_name = 'File Store' |
||||
|
_default_port = None |
||||
|
_hide_login = True |
||||
|
_hide_password = True |
||||
|
_hide_port = True |
||||
|
|
||||
|
@staticmethod |
||||
|
def connect(location): |
||||
|
rootpath = location.filestore_rootpath or '/' |
||||
|
conn = FileStoreTask(rootpath) |
||||
|
return conn |
@ -0,0 +1,30 @@ |
|||||
|
# coding: utf-8 |
||||
|
# @ 2016 Florian DA COSTA @ Akretion |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
import logging |
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
try: |
||||
|
from fs import ftpfs |
||||
|
except ImportError: |
||||
|
_logger.debug('Cannot `import fs`.') |
||||
|
|
||||
|
|
||||
|
class FtpTask(ftpfs.FTPFS): |
||||
|
|
||||
|
_key = 'sftp' |
||||
|
_name = 'SFTP' |
||||
|
_synchronize_type = None |
||||
|
_default_port = 22 |
||||
|
_hide_login = False |
||||
|
_hide_password = False |
||||
|
_hide_port = False |
||||
|
|
||||
|
@staticmethod |
||||
|
def connect(location): |
||||
|
conn = FtpTask(location.address, |
||||
|
location.login, |
||||
|
location.password, |
||||
|
location.port) |
||||
|
return conn |
@ -0,0 +1,30 @@ |
|||||
|
# coding: utf-8 |
||||
|
# @ 2016 Florian DA COSTA @ Akretion |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
import logging |
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
try: |
||||
|
from fs import sftpfs |
||||
|
except ImportError: |
||||
|
_logger.debug('Cannot `import fs`.') |
||||
|
|
||||
|
|
||||
|
class SftpTask(sftpfs.SFTPFS): |
||||
|
|
||||
|
_key = 'sftp' |
||||
|
_name = 'SFTP' |
||||
|
_synchronize_type = None |
||||
|
_default_port = 22 |
||||
|
_hide_login = False |
||||
|
_hide_password = False |
||||
|
_hide_port = False |
||||
|
|
||||
|
@staticmethod |
||||
|
def connect(location): |
||||
|
connection_string = "{}:{}".format(location.address, location.port) |
||||
|
conn = SftpTask(connection=connection_string, |
||||
|
username=location.login, |
||||
|
password=location.password) |
||||
|
return conn |
@ -0,0 +1,4 @@ |
|||||
|
from . import mock_server |
||||
|
from . import test_ftp |
||||
|
from . import test_sftp |
||||
|
from . import test_filestore |
@ -0,0 +1,32 @@ |
|||||
|
# coding: utf-8 |
||||
|
# @ 2016 Florian da Costa @ Akretion |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
import openerp.tests.common as common |
||||
|
from openerp import api |
||||
|
from StringIO import StringIO |
||||
|
|
||||
|
|
||||
|
class ContextualStringIO(StringIO): |
||||
|
""" |
||||
|
snippet from http://bit.ly/1HfH6uW (stackoverflow) |
||||
|
""" |
||||
|
|
||||
|
def __enter__(self): |
||||
|
return self |
||||
|
|
||||
|
def __exit__(self, *args): |
||||
|
self.close() |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
class TestConnection(common.TransactionCase): |
||||
|
|
||||
|
def setUp(self): |
||||
|
super(TestConnection, self).setUp() |
||||
|
self.registry.enter_test_mode() |
||||
|
self.env = api.Environment(self.registry.test_cr, self.env.uid, |
||||
|
self.env.context) |
||||
|
|
||||
|
def tearDown(self): |
||||
|
self.registry.leave_test_mode() |
||||
|
super(TestConnection, self).tearDown() |
@ -0,0 +1,74 @@ |
|||||
|
# coding: utf-8 |
||||
|
# Copyright (C) 2014 initOS GmbH & Co. KG (<http://www.initos.com>). |
||||
|
# @ 2015 Valentin CHEMIERE @ Akretion |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
import mock |
||||
|
from contextlib import contextmanager |
||||
|
from collections import defaultdict |
||||
|
|
||||
|
|
||||
|
class MultiResponse(dict): |
||||
|
pass |
||||
|
|
||||
|
|
||||
|
class ConnMock(object): |
||||
|
|
||||
|
def __init__(self, response): |
||||
|
self.response = response |
||||
|
self._calls = [] |
||||
|
self.call_count = defaultdict(int) |
||||
|
|
||||
|
def __getattribute__(self, method): |
||||
|
if method not in ('_calls', 'response', 'call_count'): |
||||
|
def callable(*args, **kwargs): |
||||
|
self._calls.append({ |
||||
|
'method': method, |
||||
|
'args': args, |
||||
|
'kwargs': kwargs, |
||||
|
}) |
||||
|
call = self.response[method] |
||||
|
if isinstance(call, MultiResponse): |
||||
|
call = call[self.call_count[method]] |
||||
|
self.call_count[method] += 1 |
||||
|
return call |
||||
|
|
||||
|
return callable |
||||
|
else: |
||||
|
return super(ConnMock, self).__getattribute__(method) |
||||
|
|
||||
|
def __call__(self, *args, **kwargs): |
||||
|
return self |
||||
|
|
||||
|
def __enter__(self, *args, **kwargs): |
||||
|
return self |
||||
|
|
||||
|
def __exit__(self, *args, **kwargs): |
||||
|
pass |
||||
|
|
||||
|
def __repr__(self, *args, **kwargs): |
||||
|
return self |
||||
|
|
||||
|
def __getitem__(self, key): |
||||
|
return |
||||
|
|
||||
|
|
||||
|
@contextmanager |
||||
|
def server_mock_sftp(response): |
||||
|
with mock.patch('openerp.addons.external_file_location.tasks.sftp.' |
||||
|
'SftpTask', ConnMock(response)) as SFTPFS: |
||||
|
yield SFTPFS._calls |
||||
|
|
||||
|
|
||||
|
@contextmanager |
||||
|
def server_mock_ftp(response): |
||||
|
with mock.patch('openerp.addons.external_file_location.tasks.ftp.' |
||||
|
'FtpTask', ConnMock(response)) as FTPFS: |
||||
|
yield FTPFS._calls |
||||
|
|
||||
|
|
||||
|
@contextmanager |
||||
|
def server_mock_filestore(response): |
||||
|
with mock.patch('openerp.addons.external_file_location.tasks.filestore.' |
||||
|
'FileStoreTask', ConnMock(response)) as FTPFS: |
||||
|
yield FTPFS._calls |
@ -0,0 +1,50 @@ |
|||||
|
# coding: utf-8 |
||||
|
# @ 2015 Valentin CHEMIERE @ Akretion |
||||
|
# ©2016 @author Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
import logging |
||||
|
from base64 import b64decode |
||||
|
from .common import TestConnection, ContextualStringIO |
||||
|
from .mock_server import server_mock_filestore |
||||
|
|
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
|
||||
|
class TestfilestoreConnection(TestConnection): |
||||
|
|
||||
|
def setUp(self): |
||||
|
super(TestfilestoreConnection, self).setUp() |
||||
|
self.test_file_filestore = ContextualStringIO() |
||||
|
self.test_file_filestore.write('import filestore') |
||||
|
self.test_file_filestore.seek(0) |
||||
|
|
||||
|
def test_00_filestore_import(self): |
||||
|
self.task = self.env.ref( |
||||
|
'external_file_location.filestore_import_task') |
||||
|
with server_mock_filestore( |
||||
|
{'open': self.test_file_filestore, |
||||
|
'listdir': ['test-import-filestore.txt']}): |
||||
|
self.task.run_import() |
||||
|
search_file = self.env['ir.attachment.metadata'].search( |
||||
|
[('name', '=', 'test-import-filestore.txt')]) |
||||
|
self.assertEqual(len(search_file), 1) |
||||
|
self.assertEqual(b64decode(search_file[0].datas), 'import filestore') |
||||
|
|
||||
|
def test_01_filestore_export(self): |
||||
|
self.task = self.env.ref( |
||||
|
'external_file_location.filestore_export_task') |
||||
|
self.filestore_attachment = self.env.ref( |
||||
|
'external_file_location.ir_attachment_export_file_filestore') |
||||
|
with server_mock_filestore( |
||||
|
{'setcontents': ''}) as Fakefilestore: |
||||
|
self.task.run_export() |
||||
|
if Fakefilestore: |
||||
|
self.assertEqual('setcontents', Fakefilestore[-1]['method']) |
||||
|
self.assertEqual('done', self.filestore_attachment.state) |
||||
|
self.assertEqual( |
||||
|
'/home/user/test/filestore_test_export.txt', |
||||
|
Fakefilestore[-1]['args'][0]) |
||||
|
self.assertEqual( |
||||
|
'test filestore file export', |
||||
|
Fakefilestore[-1]['kwargs']['data']) |
@ -0,0 +1,86 @@ |
|||||
|
# coding: utf-8 |
||||
|
# @ 2015 Valentin CHEMIERE @ Akretion |
||||
|
# ©2016 @author Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
import logging |
||||
|
from base64 import b64decode |
||||
|
import hashlib |
||||
|
from .common import TestConnection, ContextualStringIO |
||||
|
from .mock_server import server_mock_ftp |
||||
|
from .mock_server import MultiResponse |
||||
|
from openerp.exceptions import UserError |
||||
|
|
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
|
||||
|
class TestFtpConnection(TestConnection): |
||||
|
|
||||
|
def setUp(self): |
||||
|
super(TestFtpConnection, self).setUp() |
||||
|
self.test_file_ftp = ContextualStringIO() |
||||
|
self.test_file_ftp.write('import ftp') |
||||
|
self.test_file_ftp.seek(0) |
||||
|
|
||||
|
def test_00_ftp_import(self): |
||||
|
self.task = self.env.ref('external_file_location.ftp_import_task') |
||||
|
with server_mock_ftp( |
||||
|
{'open': self.test_file_ftp, |
||||
|
'listdir': ['test-import-ftp.txt']}): |
||||
|
self.task.run_import() |
||||
|
search_file = self.env['ir.attachment.metadata'].search( |
||||
|
[('name', '=', 'test-import-ftp.txt')]) |
||||
|
self.assertEqual(len(search_file), 1) |
||||
|
self.assertEqual(b64decode(search_file[0].datas), 'import ftp') |
||||
|
|
||||
|
def test_01_ftp_export(self): |
||||
|
self.task = self.env.ref('external_file_location.ftp_export_task') |
||||
|
self.ftp_attachment = self.env.ref( |
||||
|
'external_file_location.ir_attachment_export_file_ftp') |
||||
|
with server_mock_ftp( |
||||
|
{'setcontents': ''}) as FakeFTP: |
||||
|
self.task.run_export() |
||||
|
if FakeFTP: |
||||
|
self.assertEqual('setcontents', FakeFTP[-1]['method']) |
||||
|
self.assertEqual('done', self.ftp_attachment.state) |
||||
|
self.assertEqual( |
||||
|
'/home/user/test/ftp_test_export.txt', |
||||
|
FakeFTP[-1]['args'][0]) |
||||
|
self.assertEqual( |
||||
|
'test ftp file export', |
||||
|
FakeFTP[-1]['kwargs']['data']) |
||||
|
|
||||
|
def test_02_ftp_import_md5(self): |
||||
|
md5_file = ContextualStringIO() |
||||
|
md5_file.write(hashlib.md5('import ftp').hexdigest()) |
||||
|
md5_file.seek(0) |
||||
|
task = self.env.ref('external_file_location.ftp_import_task') |
||||
|
task.md5_check = True |
||||
|
with server_mock_ftp( |
||||
|
{'open': MultiResponse({ |
||||
|
1: md5_file, |
||||
|
0: self.test_file_ftp}), |
||||
|
'listdir': [task.filename]}) as Fakeftp: |
||||
|
task.run_import() |
||||
|
search_file = self.env['ir.attachment.metadata'].search( |
||||
|
(('name', '=', task.filename),)) |
||||
|
self.assertEqual(len(search_file), 1) |
||||
|
self.assertEqual(b64decode(search_file[0].datas), |
||||
|
'import ftp') |
||||
|
self.assertEqual('open', Fakeftp[-1]['method']) |
||||
|
self.assertEqual(hashlib.md5('import ftp').hexdigest(), |
||||
|
search_file.external_hash) |
||||
|
|
||||
|
def test_03_ftp_import_md5_corrupt_file(self): |
||||
|
md5_file = ContextualStringIO() |
||||
|
md5_file.write(hashlib.md5('import test ftp corrupted').hexdigest()) |
||||
|
md5_file.seek(0) |
||||
|
task = self.env.ref('external_file_location.ftp_import_task') |
||||
|
task.md5_check = True |
||||
|
with server_mock_ftp( |
||||
|
{'open': MultiResponse({ |
||||
|
1: md5_file, |
||||
|
0: self.test_file_ftp}), |
||||
|
'listdir': [task.filename]}): |
||||
|
with self.assertRaises(UserError): |
||||
|
task.run_import() |
@ -0,0 +1,85 @@ |
|||||
|
# coding: utf-8 |
||||
|
# @ 2015 Valentin CHEMIERE @ Akretion |
||||
|
# ©2016 @author Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
import logging |
||||
|
from base64 import b64decode |
||||
|
import hashlib |
||||
|
from .common import TestConnection, ContextualStringIO |
||||
|
from .mock_server import server_mock_sftp |
||||
|
from .mock_server import MultiResponse |
||||
|
from openerp.exceptions import UserError |
||||
|
|
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
|
||||
|
class TestSftpConnection(TestConnection): |
||||
|
|
||||
|
def setUp(self): |
||||
|
super(TestSftpConnection, self).setUp() |
||||
|
self.test_file_sftp = ContextualStringIO() |
||||
|
self.test_file_sftp.write('import sftp') |
||||
|
self.test_file_sftp.seek(0) |
||||
|
|
||||
|
def test_00_sftp_import(self): |
||||
|
task = self.env.ref('external_file_location.sftp_import_task') |
||||
|
with server_mock_sftp( |
||||
|
{'open': self.test_file_sftp, |
||||
|
'listdir': [task.filename]}): |
||||
|
task.run_import() |
||||
|
search_file = self.env['ir.attachment.metadata'].search( |
||||
|
[('name', '=', task.filename)]) |
||||
|
self.assertEqual(len(search_file), 1) |
||||
|
self.assertEqual(b64decode(search_file[0].datas), 'import sftp') |
||||
|
|
||||
|
def test_01_sftp_export(self): |
||||
|
self.task = self.env.ref('external_file_location.sftp_export_task') |
||||
|
self.sftp_attachment = self.env.ref( |
||||
|
'external_file_location.ir_attachment_export_file_sftp') |
||||
|
with server_mock_sftp( |
||||
|
{'setcontents': ''}) as FakeSFTP: |
||||
|
self.task.run_export() |
||||
|
if FakeSFTP: |
||||
|
self.assertEqual('setcontents', FakeSFTP[-1]['method']) |
||||
|
self.assertEqual( |
||||
|
'/home/user/test/sftp_test_export.txt', |
||||
|
FakeSFTP[-1]['args'][0]) |
||||
|
self.assertEqual( |
||||
|
'test sftp file export', |
||||
|
FakeSFTP[-1]['kwargs']['data']) |
||||
|
|
||||
|
def test_02_sftp_import_md5(self): |
||||
|
md5_file = ContextualStringIO() |
||||
|
md5_file.write(hashlib.md5('import sftp').hexdigest()) |
||||
|
md5_file.seek(0) |
||||
|
task = self.env.ref('external_file_location.sftp_import_task') |
||||
|
task.md5_check = True |
||||
|
with server_mock_sftp( |
||||
|
{'open': MultiResponse({ |
||||
|
1: md5_file, |
||||
|
0: self.test_file_sftp}), |
||||
|
'listdir': [task.filename]}) as FakeSFTP: |
||||
|
task.run_import() |
||||
|
search_file = self.env['ir.attachment.metadata'].search( |
||||
|
(('name', '=', task.filename),)) |
||||
|
self.assertEqual(len(search_file), 1) |
||||
|
self.assertEqual(b64decode(search_file[0].datas), |
||||
|
'import sftp') |
||||
|
self.assertEqual('open', FakeSFTP[-1]['method']) |
||||
|
self.assertEqual(hashlib.md5('import sftp').hexdigest(), |
||||
|
search_file.external_hash) |
||||
|
|
||||
|
def test_03_sftp_import_md5_corrupt_file(self): |
||||
|
md5_file = ContextualStringIO() |
||||
|
md5_file.write(hashlib.md5('import test sftp corrupted').hexdigest()) |
||||
|
md5_file.seek(0) |
||||
|
task = self.env.ref('external_file_location.sftp_import_task') |
||||
|
task.md5_check = True |
||||
|
with server_mock_sftp( |
||||
|
{'open': MultiResponse({ |
||||
|
1: md5_file, |
||||
|
0: self.test_file_sftp}), |
||||
|
'listdir': [task.filename]}): |
||||
|
with self.assertRaises(UserError): |
||||
|
task.run_import() |
@ -0,0 +1,28 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
|
||||
|
<record id="view_attachment_improved_form" model="ir.ui.view"> |
||||
|
<field name="model">ir.attachment.metadata</field> |
||||
|
<field name="inherit_id" ref="attachment_base_synchronize.view_attachment_improved_form" /> |
||||
|
<field name="arch" type="xml"> |
||||
|
<field name="url" position="after"> |
||||
|
<field name="task_id" attrs="{'required': [('file_type', '=', 'export_external_location')]}"/> |
||||
|
<field name="location_id"/> |
||||
|
</field> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="view_external_attachment_tree" model="ir.ui.view"> |
||||
|
<field name="model">ir.attachment.metadata</field> |
||||
|
<field name="inherit_id" ref="attachment_base_synchronize.view_external_attachment_tree" /> |
||||
|
<field name="arch" type="xml"> |
||||
|
<field name="file_type" position="after"> |
||||
|
<field name="task_id"/> |
||||
|
<field name="location_id"/> |
||||
|
</field> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
</data> |
||||
|
</openerp> |
@ -0,0 +1,70 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
|
||||
|
<record id="view_location_form" model="ir.ui.view"> |
||||
|
<field name="model">external.file.location</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form string="Location" version="7.0"> |
||||
|
<sheet> |
||||
|
<group col="4"> |
||||
|
<div class="oe_title" style="width: 390px;" colspan="4"> |
||||
|
<label class="oe_edit_only" for="name" string="Name"/> |
||||
|
<h1><field name="name" class="oe_inline"/></h1> |
||||
|
</div> |
||||
|
<newline/> |
||||
|
<field name="protocol" colspan="2"/> |
||||
|
<newline/> |
||||
|
<field name="address" colspan="2" attrs="{'required': [('protocol', '!=', 'file_store')], 'invisible': [('protocol', '=', 'file_store')]}"/> |
||||
|
<field name="filestore_rootpath" colspan="2" attrs="{'required': [('protocol', '=', 'file_store')], 'invisible': [('protocol', '!=', 'file_store')]}"/> |
||||
|
<field name="port" colspan="2" attrs="{'invisible': [('hide_port', '=', True)], 'required': [('hide_port', '=', False)]}"/> |
||||
|
<field name="company_id" colspan="2"/> |
||||
|
<field name="login" colspan="2" attrs="{'invisible': [('hide_login', '=', True)], 'required': [('hide_login', '=', False)]}"/> |
||||
|
<field name="password" password="1" colspan="2" attrs="{'invisible': [('hide_password', '=', True)]}"/> |
||||
|
<separator string="Tasks" colspan="4"/> |
||||
|
<field name="task_ids" colspan="4" nolabel="1" context="{'hide_location': True, 'protocol': protocol}"> |
||||
|
<tree> |
||||
|
<field name="name"/> |
||||
|
<field name="name"/> |
||||
|
<field name="method_type"/> |
||||
|
<field name="filename"/> |
||||
|
<field name="filepath"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
<field name="hide_login" invisible="1"/> |
||||
|
<field name="hide_password" invisible="1"/> |
||||
|
<field name="hide_port" invisible="1"/> |
||||
|
</group> |
||||
|
</sheet> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="view_location_tree" model="ir.ui.view"> |
||||
|
<field name="model">external.file.location</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree string="File Location"> |
||||
|
<field name="name" select="1"/> |
||||
|
<field name="protocol"/> |
||||
|
<field name="address"/> |
||||
|
<field name="login"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="action_location" model="ir.actions.act_window"> |
||||
|
<field name="name">File Locations</field> |
||||
|
<field name="type">ir.actions.act_window</field> |
||||
|
<field name="res_model">external.file.location</field> |
||||
|
<field name="view_type">form</field> |
||||
|
<field name="view_id" eval="False"/> |
||||
|
</record> |
||||
|
|
||||
|
<menuitem id="menu_ir_location" |
||||
|
parent="base.menu_automation" |
||||
|
sequence="20" |
||||
|
action="action_location"/> |
||||
|
|
||||
|
</data> |
||||
|
</openerp> |
||||
|
|
@ -0,0 +1,12 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
|
||||
|
<menuitem id="menu_file_exchange" |
||||
|
parent="base.menu_administration" |
||||
|
sequence="20" |
||||
|
name="File exchange" |
||||
|
/> |
||||
|
|
||||
|
</data> |
||||
|
</openerp> |
@ -0,0 +1,70 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
<!-- © 2014-2016 Akretion (http://www.akretion.com) |
||||
|
# @ 2015 Valentin CHEMIERE @ Akretion |
||||
|
@author Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com> |
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). --> |
||||
|
<record id="view_task_form" model="ir.ui.view"> |
||||
|
<field name="model">external.file.task</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form string="Tasks" version="7.0"> |
||||
|
<header> |
||||
|
<button name="run_import" type="object" string="Run" icon="gtk-execute" attrs="{'invisible': [('method_type', '!=', 'import')]}"/> |
||||
|
<button name="run_export" type="object" string="Run" icon="gtk-execute" attrs="{'invisible': [('method_type', '!=', 'export')]}"/> |
||||
|
</header> |
||||
|
<sheet> |
||||
|
<field name="method_type" invisible="1"/> |
||||
|
<group col="4"> |
||||
|
<div class="oe_title" style="width: 390px;" colspan="4"> |
||||
|
<label class="oe_edit_only" for="name" string="Name"/> |
||||
|
<h1><field name="name" class="oe_inline"/></h1> |
||||
|
</div> |
||||
|
<field name="method_type" colspan="2"/> |
||||
|
<span colspan="2"/> |
||||
|
<field name="filename" colspan="4" attrs="{'invisible':[('method_type','!=','import')], 'required':[('method_type', '=', 'import')]}"/> |
||||
|
<field name="filepath" colspan="4" /> |
||||
|
<field name="company_id" colspan="4"/> |
||||
|
</group> |
||||
|
<group col="6"> |
||||
|
<field name="after_import" attrs="{'invisible':[('method_type','!=','import')]}"/> |
||||
|
<group col="4" colspan="4" > |
||||
|
<field name="move_path" colspan="4" |
||||
|
attrs="{'invisible':['|', '&', |
||||
|
('after_import','!=','move'), |
||||
|
('after_import','!=','move_rename'), |
||||
|
('method_type','!=','import')]}"/> |
||||
|
<field name="new_name" colspan="4" |
||||
|
attrs="{'invisible': ['|', '&', |
||||
|
('after_import','!=','rename'), |
||||
|
('after_import','!=','move_rename'), |
||||
|
('method_type','!=','import')]}"/> |
||||
|
</group> |
||||
|
<field name="md5_check" colspan="2" |
||||
|
attrs="{'invisible': |
||||
|
[('method_type','!=','import')]}" |
||||
|
/> |
||||
|
<field name="active" colspan="2"/> |
||||
|
</group> |
||||
|
<group string="Data importation setting"> |
||||
|
<field name="file_type" attrs="{'invisible':[('method_type','!=','import')]}"/> |
||||
|
</group> |
||||
|
</sheet> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="view_task_tree" model="ir.ui.view"> |
||||
|
<field name="model">external.file.task</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<tree string="Tasks" > |
||||
|
<field name="name" select="1"/> |
||||
|
<field name="method_type"/> |
||||
|
<field name="filename"/> |
||||
|
<field name="filepath"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
</data> |
||||
|
</openerp> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue