Browse Source

[WIP] Use pattern in file name & add move and rename file option

[WIP] use jinja to render new file name based on simple template

[FIX] bug move and rename file option

[ADD] add file type on task

[FIX] add file type in attachemnt create method

[WIP] test rename file

[WIP] inherit view from attachment_metadata

[FIX] bug of inherit view and menu from attachment_metadata

[IMP] add move & rename test to test_sftp

[IMP] move file location menu inside automation menu and rename it

[IMP] add ir.model.access manager rule

[FIX] typing mistake

[FIX] access file store without login/password

[IMP] reorganize task form view

[FIX] api.multi in task run method

[FIX] OCA guidelines

[FIX] add authors and contributors

[FIX] fix pylint

[FIX] add oca_dependencies & fixe oca version format

[FIX] fix pylint & oca_dependencies

[FIX] fix views path

[FIX] set application to False
12.0-mig-module_prototyper_last
Mourad El Hadj Mimoune 9 years ago
committed by David Beal
parent
commit
a417c3e37e
  1. 5
      external_file_location/README.rst
  2. 4
      external_file_location/__init__.py
  3. 20
      external_file_location/__openerp__.py
  4. 99
      external_file_location/attachment_view.xml
  5. 0
      external_file_location/data/cron.xml
  6. 3
      external_file_location/models/__init__.py
  7. 0
      external_file_location/models/attachment.py
  8. 0
      external_file_location/models/helper.py
  9. 2
      external_file_location/models/location.py
  10. 115
      external_file_location/models/task.py
  11. 2
      external_file_location/security/ir.model.access.csv
  12. 86
      external_file_location/task.py
  13. 94
      external_file_location/tasks/abstract_fs.py
  14. 3
      external_file_location/tasks/abstract_task.py
  15. 2
      external_file_location/tasks/sftp.py
  16. 69
      external_file_location/tests/test_sftp.py
  17. 50
      external_file_location/views/attachment_view.xml
  18. 6
      external_file_location/views/location_view.xml
  19. 0
      external_file_location/views/menu.xml
  20. 30
      external_file_location/views/task_view.xml

5
external_file_location/README.rst

@ -37,17 +37,18 @@ Credits
* Joel Grand-Guillaume Camptocamp * Joel Grand-Guillaume Camptocamp
* initOS <http://initos.com> * initOS <http://initos.com>
* Valentin CHEMIERE <valentin.chemiere@akretion.com> * Valentin CHEMIERE <valentin.chemiere@akretion.com>
* Mourad EL HADJ MIMOUNE <mourad.elhadj.mimoune@akretion.com>
Contributors Contributors
------------ ------------
* Sebastien BEAU <sebastian.beau@akretion.com> * Sebastien BEAU <sebastian.beau@akretion.com>
* David BEAL <david.beal@akretion.com>
Maintainer Maintainer
---------- ----------
* Valentin CHEMIERE <valentin.chemiere@akretion.com>
.. image:: http://odoo-community.org/logo.png .. image:: http://odoo-community.org/logo.png
:alt: Odoo Community Association :alt: Odoo Community Association
:target: http://odoo-community.org :target: http://odoo-community.org

4
external_file_location/__init__.py

@ -1,5 +1,3 @@
from . import attachment
from . import location
from . import task
from . import models
from . import tasks from . import tasks
from . import tests from . import tests

20
external_file_location/__openerp__.py

@ -1,11 +1,12 @@
# coding: utf-8 # coding: utf-8
# @ 2015 Valentin CHEMIERE @ Akretion # @ 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). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{ {
'name': 'external_file_location', 'name': 'external_file_location',
'version': '0.0.1',
'author': 'Akretion',
'version': '8.0.1.0.0',
'author': 'Akretion,Odoo Community Association (OCA)',
'website': 'www.akretion.com', 'website': 'www.akretion.com',
'license': 'AGPL-3', 'license': 'AGPL-3',
'category': 'Generic Modules', 'category': 'Generic Modules',
@ -19,13 +20,14 @@
], ],
}, },
'data': [ 'data': [
'menu.xml',
'attachment_view.xml',
'location_view.xml',
'task_view.xml',
'cron.xml',
'views/menu.xml',
'views/attachment_view.xml',
'views/location_view.xml',
'views/task_view.xml',
'data/cron.xml',
'security/ir.model.access.csv', 'security/ir.model.access.csv',
], ],
'installable': True, 'installable': True,
'application': True,
}
'application': False,
'images': [],
}

99
external_file_location/attachment_view.xml

@ -1,99 +0,0 @@
<?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_metadata.view_attachment_improved_form" />
<field name="arch" type="xml">
<field name="url" position="after">
<field name="sync_date"/>
<field name="state"/>
<field name="state_message"/>
<field name="task_id"/>
<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="arch" type="xml">
<tree string="Attachments" >
<field name="name"/>
<field name="datas_fname"/>
<field name="task_id"/>
<field name="location_id"/>
<field name="type"/>
<field name="create_date"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="view_external_attachment_search" model="ir.ui.view">
<field name="model">ir.attachment.metadata</field>
<field name="arch" type="xml">
<search string="Attachments">
<field name="name" filter_domain="['|', ('name','ilike',self), ('datas_fname','ilike',self)]" string="Attachment"/>
<field name="create_date"/>
<filter icon="terp-stage"
string="URL"
domain="[('type','=','url')]"/>
<filter icon="terp-stock_align_left_24"
string="Binary"
domain="[('type','=','binary')]"/>
<separator/>
<filter name="my_documents_filter"
string="My Document(s)"
icon="terp-personal"
domain="[('create_uid','=',uid)]"
help="Filter on my documents"/>
<field name="create_uid"/>
<field name="type"/>
<filter string="Pending" domain="[('state', '=', 'pending')]"/>
<filter string="Failed" domain="[('state', '=', 'failed')]"/>
<filter string="Done" domain="[('state', '=', 'done')]"/>
<group expand="0" string="Group By">
<filter string="Owner" icon="terp-personal" domain="[]" context="{'group_by':'create_uid'}"/>
<filter string="Type" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'type'}" groups="base.group_no_one"/>
<filter string="Company" icon="terp-gtk-home" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Creation Month" icon="terp-go-month" domain="[]" context="{'group_by':'create_date'}"/>
<filter string="State" domain="[]" context="{'group_by': 'state'}"/>
</group>
</search>
</field>
</record>
<record id="action_attachment" model="ir.actions.act_window">
<field name="name">Attachments</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">ir.attachment.metadata</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" eval="False"/>
<!-- <field name="domain">[('task_id', '!=', False)]</field> -->
<field name="search_view_id" ref="view_external_attachment_search"/>
</record>
<record id="ir_attachment_view2" model="ir.actions.act_window.view">
<field eval="10" name="sequence"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_external_attachment_tree"/>
<field name="act_window_id" ref="action_attachment"/>
</record>
<record id="ir_attachment_view3" model="ir.actions.act_window.view">
<field eval="10" name="sequence"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_attachment_improved_form"/>
<field name="act_window_id" ref="action_attachment"/>
</record>
<menuitem id="menu_ir_attachment"
parent="menu_file_exchange"
sequence="20"
action="action_attachment"/>
</data>
</openerp>

0
external_file_location/cron.xml → external_file_location/data/cron.xml

3
external_file_location/models/__init__.py

@ -0,0 +1,3 @@
from . import attachment
from . import location
from . import task

0
external_file_location/attachment.py → external_file_location/models/attachment.py

0
external_file_location/helper.py → external_file_location/models/helper.py

2
external_file_location/location.py → external_file_location/models/location.py

@ -3,7 +3,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields, api from openerp import models, fields, api
from .abstract_task import AbstractTask
from ..tasks.abstract_task import AbstractTask
from .helper import itersubclasses from .helper import itersubclasses

115
external_file_location/models/task.py

@ -0,0 +1,115 @@
# 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
from .helper import itersubclasses, get_erp_module, is_module_installed
from ..tasks.abstract_task import AbstractTask
class Task(models.Model):
_name = 'external.file.task'
_description = 'External file task'
name = fields.Char(required=True)
method = fields.Selection(selection='_get_method', required=True,
help='procotol and trasmitting info')
method_type = fields.Char()
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 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')
file_type = fields.Selection(
selection="_get_file_type",
string="File type",
help="The file type determines an import method to be used "
"to parse and transform data before their import in ERP")
def _get_action(self):
return [('rename', 'Rename'),
('move', 'Move'),
('move_rename', 'Move & Rename'),
('delete', 'Delete'),
]
def _get_file_type(self):
"""This is the method to be inherited for adding file types
The basic import do not apply any parsing or transform of the file.
The file is just added as an attachement
"""
return [('basic_import', 'Basic import')]
def _get_method(self):
res = []
for cls in itersubclasses(AbstractTask):
if not is_module_installed(self.env, get_erp_module(cls)):
continue
if cls._synchronize_type and (
'protocol' not in self._context or
cls._key == self._context['protocol']):
cls_info = (cls._key + '_' + cls._synchronize_type,
cls._name + ' ' + cls._synchronize_type)
res.append(cls_info)
return res
@api.onchange('method')
def onchange_method(self):
if self.method:
if 'import' in self.method:
self.method_type = 'import'
elif 'export' in self.method:
self.method_type = 'export'
@api.model
def _run(self, domain=None):
if not domain:
domain = []
tasks = self.env['external.file.task'].search(domain)
tasks.run()
@api.multi
def run(self):
for tsk in self:
for cls in itersubclasses(AbstractTask):
if not is_module_installed(self.env, get_erp_module(cls)):
continue
cls_build = '%s_%s' % (cls._key, cls._synchronize_type)
if cls._synchronize_type and cls_build == tsk.method:
method_class = cls
config = {
'host': tsk.location_id.address,
# ftplib does not support unicode
'user': tsk.location_id.login and\
tsk.location_id.login.encode('utf-8'),
'pwd': tsk.location_id.password and \
tsk.location_id.password.encode('utf-8'),
'port': tsk.location_id.port,
'allow_dir_creation': False,
'file_name': tsk.filename,
'path': tsk.filepath,
'attachment_ids': tsk.attachment_ids,
'task': tsk,
'move_path': tsk.move_path,
'new_name': tsk.new_name,
'after_import': tsk.after_import,
'file_type': tsk.file_type,
'md5_check': tsk.md5_check,
}
conn = method_class(self.env, config)
conn.run()

2
external_file_location/security/ir.model.access.csv

@ -1,3 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 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_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 access_external_file_task_user,external.file.task.user,model_external_file_task,base.group_user,1,0,0,0

86
external_file_location/task.py

@ -1,86 +0,0 @@
# 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 .helper import itersubclasses, get_erp_module, is_module_installed
from .abstract_task import AbstractTask
class Task(models.Model):
_name = 'external.file.task'
_description = 'Description'
name = fields.Char(required=True)
method = fields.Selection(selection='_get_method', required=True,
help='procotol and trasmitting info')
method_type = fields.Char()
filename = fields.Char(help='File name which is imported')
filepath = fields.Char(help='Path to imported 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')
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')
def _get_action(self):
return [('move', 'Move'), ('delete', 'Delete')]
def _get_method(self):
res = []
for cls in itersubclasses(AbstractTask):
if not is_module_installed(self.env, get_erp_module(cls)):
continue
if cls._synchronize_type and (
'protocol' not in self._context or
cls._key == self._context['protocol']):
cls_info = (cls._key + '_' + cls._synchronize_type,
cls._name + ' ' + cls._synchronize_type)
res.append(cls_info)
return res
@api.onchange('method')
def onchange_method(self):
if self.method:
if 'import' in self.method:
self.method_type = 'import'
elif 'export' in self.method:
self.method_type = 'export'
@api.model
def _run(self, domain=None):
if not domain:
domain = []
tasks = self.env['external.file.task'].search(domain)
tasks.run()
@api.one
def run(self):
for cls in itersubclasses(AbstractTask):
if not is_module_installed(self.env, get_erp_module(cls)):
continue
cls_build = '%s_%s' % (cls._key, cls._synchronize_type)
if cls._synchronize_type and cls_build == self.method:
method_class = cls
config = {
'host': self.location_id.address,
# ftplib does not support unicode
'user': self.location_id.login.encode('utf-8'),
'pwd': self.location_id.password.encode('utf-8'),
'port': self.location_id.port,
'allow_dir_creation': False,
'file_name': self.filename,
'path': self.filepath,
'attachment_ids': self.attachment_ids,
'task': self,
'move_path': self.move_path,
'after_import': self.after_import,
'md5_check': self.md5_check,
}
conn = method_class(self.env, config)
conn.run()

94
external_file_location/tasks/abstract_fs.py

@ -1,14 +1,52 @@
# coding: utf-8 # coding: utf-8
# Copyright (C) 2014 initOS GmbH & Co. KG (<http://www.initos.com>). # Copyright (C) 2014 initOS GmbH & Co. KG (<http://www.initos.com>).
# @ 2015 Valentin CHEMIERE @ Akretion # @ 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). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from ..abstract_task import AbstractTask
import logging import logging
import os import os
import fnmatch
import datetime
from openerp import tools
from .abstract_task import AbstractTask
_logger = logging.getLogger(__name__) _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 AbstractFSTask(AbstractTask): class AbstractFSTask(AbstractTask):
_name = None _name = None
@ -26,7 +64,9 @@ class AbstractFSTask(AbstractTask):
self.file_name = config.get('file_name', '') self.file_name = config.get('file_name', '')
self.path = config.get('path') or '.' self.path = config.get('path') or '.'
self.move_path = config.get('move_path', '') self.move_path = config.get('move_path', '')
self.new_name = config.get('new_name', '')
self.after_import = config.get('after_import', False) self.after_import = config.get('after_import', False)
self.file_type = config.get('file_type', False)
self.attachment_ids = config.get('attachment_ids', False) self.attachment_ids = config.get('attachment_ids', False)
self.task = config.get('task', False) self.task = config.get('task', False)
self.ext_hash = False self.ext_hash = False
@ -67,12 +107,30 @@ class AbstractFSTask(AbstractTask):
def _get_files(self, conn, path): def _get_files(self, conn, path):
process_files = [] process_files = []
files_list = conn.listdir(path) files_list = conn.listdir(path)
for file in files_list:
if file == self.file_name:
source_name = self._source_name(self.path, self.file_name)
process_files.append((file, source_name))
pattern = self.file_name
for file_name in fnmatch.filter(files_list, pattern):
source_name = self._source_name(self.path, file_name)
process_files.append((file_name, source_name))
return process_files return process_files
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
def _process_file(self, conn, file_to_process): def _process_file(self, conn, file_to_process):
if self.md5_check: if self.md5_check:
self.ext_hash = self._get_hash(file_to_process[1], conn) self.ext_hash = self._get_hash(file_to_process[1], conn)
@ -81,17 +139,31 @@ class AbstractFSTask(AbstractTask):
self.path, self.path,
self.file_name, self.file_name,
self.move_path) self.move_path)
# Move/delete files only after all files have been processed.
move = False
rename = False
if self.after_import:
move = 'move' in self.after_import
rename = 'rename' in self.after_import
# Move/rename/delete files only after all
# files have been processed.
if self.after_import == 'delete': if self.after_import == 'delete':
self._delete_file(conn, file_to_process[1]) self._delete_file(conn, file_to_process[1])
elif self.after_import == 'move':
if not conn.exists(self.move_path):
elif rename or move:
new_name = file_to_process[0]
if rename and self.new_name:
new_name_render = self._template_render(
self.new_name, att_id)
if new_name_render:
# Avoid space in file name
new_name = new_name_render.replace(' ', '_')
if self.move_path and not conn.exists(self.move_path):
conn.makedir(self.move_path) conn.makedir(self.move_path)
move_path = self.move_path if self.move_path else self.path
self._move_file( self._move_file(
conn, conn,
file_to_process[1], file_to_process[1],
self._source_name(self.move_path, file_to_process[0]))
self._source_name(move_path, new_name))
return att_id return att_id
def _handle_existing_target(self, fs_conn, target_name, filedata): def _handle_existing_target(self, fs_conn, target_name, filedata):

3
external_file_location/abstract_task.py → external_file_location/tasks/abstract_task.py

@ -22,6 +22,7 @@ class AbstractTask(object):
'datas_fname': filename, 'datas_fname': filename,
'task_id': self.task and self.task.id or False, 'task_id': self.task and self.task.id or False,
'location_id': self.task and self.task.location_id.id or False, 'location_id': self.task and self.task.location_id.id or False,
'external_hash': self.ext_hash
'external_hash': self.ext_hash,
'file_type': self.file_type,
}) })
return ir_attachment_id return ir_attachment_id

2
external_file_location/tasks/sftp.py

@ -27,7 +27,7 @@ class SftpImportTask(SftpTask):
def run(self): def run(self):
connection_string = "{}:{}".format(self.host, self.port) connection_string = "{}:{}".format(self.host, self.port)
root = "/home/{}".format(self.user)
root = "/"
att_ids = [] att_ids = []
with sftpfs.SFTPFS(connection=connection_string, with sftpfs.SFTPFS(connection=connection_string,
root_path=root, root_path=root,

69
external_file_location/tests/test_sftp.py

@ -1,15 +1,20 @@
# coding: utf-8 # coding: utf-8
# @ 2015 Valentin CHEMIERE @ Akretion # @ 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). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import logging
from StringIO import StringIO
from base64 import b64decode
import hashlib
import openerp.tests.common as common import openerp.tests.common as common
from ..tasks.sftp import SftpImportTask from ..tasks.sftp import SftpImportTask
from ..tasks.sftp import SftpExportTask from ..tasks.sftp import SftpExportTask
from .mock_server import (server_mock) from .mock_server import (server_mock)
from .mock_server import MultiResponse from .mock_server import MultiResponse
from StringIO import StringIO
from base64 import b64decode
import hashlib
_logger = logging.getLogger(__name__)
class ContextualStringIO(StringIO): class ContextualStringIO(StringIO):
@ -37,7 +42,7 @@ class TestNewSource(common.TransactionCase):
'password': 'test', 'password': 'test',
'host': 'test', 'host': 'test',
'port': 22, 'port': 22,
'attachment_ids': self.env['ir.attachment.metadata'].search([])
'attachment_ids': self.env['ir.attachment.metadata'].browse(False)
} }
def test_00_sftp_import(self): def test_00_sftp_import(self):
@ -80,6 +85,9 @@ class TestNewSource(common.TransactionCase):
self.assertEqual(len(search_file), 1) self.assertEqual(len(search_file), 1)
self.assertEqual(b64decode(search_file[0].datas), 'import') self.assertEqual(b64decode(search_file[0].datas), 'import')
self.assertEqual('remove', FakeSFTP[-1]['method']) self.assertEqual('remove', FakeSFTP[-1]['method'])
self.assertEqual(
'./testfile', FakeSFTP[-1]['args'][0],
"Delete File must be './testfile'")
def test_03_sftp_import_move(self): def test_03_sftp_import_move(self):
with server_mock( with server_mock(
@ -98,7 +106,58 @@ class TestNewSource(common.TransactionCase):
self.assertEqual(b64decode(search_file[0].datas), 'import') self.assertEqual(b64decode(search_file[0].datas), 'import')
self.assertEqual('rename', FakeSFTP[-1]['method']) self.assertEqual('rename', FakeSFTP[-1]['method'])
def test_04_sftp_import_md5(self):
def test_04_sftp_import_rename(self):
with server_mock(
{'exists': True,
'makedir': True,
'open': self.test_file,
'listdir': ['testfile'],
'rename': True
}) as FakeSFTP:
_logger.info("Test sftp rename file")
self.config.update({
'after_import': 'rename',
'new_name': '${obj.name}.imported',
'path': '/home',
})
task = SftpImportTask(self.env, self.config)
task.run()
search_file = self.env['ir.attachment.metadata'].search(
(('name', '=', 'testfile'),))
self.assertEqual(len(search_file), 1)
self.assertEqual(b64decode(search_file[0].datas), 'import')
self.assertEqual('rename', FakeSFTP[2]['method'])
self.assertEqual('/home/testfile.imported',
FakeSFTP[2]['args'][1],
"File not renamed")
def test_05_sftp_import_move_rename(self):
with server_mock(
{'exists': True,
'makedir': True,
'open': self.test_file,
'listdir': ['testfile'],
'rename': True
}) as FakeSFTP:
_logger.info("Test sftp move and rename file")
self.config.update({
'after_import': 'rename',
'new_name': '${obj.name}.imported',
'path': '/home',
'move_path': '/home/processed',
})
task = SftpImportTask(self.env, self.config)
task.run()
search_file = self.env['ir.attachment.metadata'].search(
(('name', '=', 'testfile'),))
self.assertEqual(len(search_file), 1)
self.assertEqual(b64decode(search_file[0].datas), 'import')
self.assertEqual('rename', FakeSFTP[3]['method'])
self.assertEqual('/home/processed/testfile.imported',
FakeSFTP[3]['args'][1],
"File not renamed and moved")
def test_06_sftp_import_md5(self):
md5_file = ContextualStringIO() md5_file = ContextualStringIO()
md5_file.write(hashlib.md5('import').hexdigest()) md5_file.write(hashlib.md5('import').hexdigest())
md5_file.seek(0) md5_file.seek(0)

50
external_file_location/views/attachment_view.xml

@ -0,0 +1,50 @@
<?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_metadata.view_attachment_improved_form" />
<field name="arch" type="xml">
<field name="url" position="after">
<field name="sync_date"/>
<field name="state"/>
<field name="state_message"/>
<field name="task_id"/>
<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_metadata.view_external_attachment_tree" />
<field name="arch" type="xml">
<field name="file_type" position="after">
<field name="task_id"/>
<field name="location_id"/>
<field name="type"/>
<field name="create_date"/>
<field name="state"/>
</field>
</field>
</record>
<record id="view_external_attachment_search" model="ir.ui.view">
<field name="model">ir.attachment.metadata</field>
<field name="inherit_id" ref="attachment_metadata.view_external_attachment_search" />
<field name="arch" type="xml">
<field name="type" position="after">
<filter string="Pending" domain="[('state', '=', 'pending')]"/>
<filter string="Failed" domain="[('state', '=', 'failed')]"/>
<filter string="Done" domain="[('state', '=', 'done')]"/>
</field>
<filter string="Creation Month" position="after">
<filter string="State" domain="[]"
context="{'group_by': 'state'}"/>
</filter>
</field>
</record>
</data>
</openerp>

6
external_file_location/location_view.xml → external_file_location/views/location_view.xml

@ -42,7 +42,7 @@
<record id="view_location_tree" model="ir.ui.view"> <record id="view_location_tree" model="ir.ui.view">
<field name="model">external.file.location</field> <field name="model">external.file.location</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Location">
<tree string="File Location">
<field name="name" select="1"/> <field name="name" select="1"/>
<field name="protocol"/> <field name="protocol"/>
<field name="address"/> <field name="address"/>
@ -52,7 +52,7 @@
</record> </record>
<record id="action_location" model="ir.actions.act_window"> <record id="action_location" model="ir.actions.act_window">
<field name="name">Locations</field>
<field name="name">File Locations</field>
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
<field name="res_model">external.file.location</field> <field name="res_model">external.file.location</field>
<field name="view_type">form</field> <field name="view_type">form</field>
@ -60,7 +60,7 @@
</record> </record>
<menuitem id="menu_ir_location" <menuitem id="menu_ir_location"
parent="menu_file_exchange"
parent="base.menu_automation"
sequence="20" sequence="20"
action="action_location"/> action="action_location"/>

0
external_file_location/menu.xml → external_file_location/views/menu.xml

30
external_file_location/task_view.xml → external_file_location/views/task_view.xml

@ -1,7 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<openerp> <openerp>
<data> <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"> <record id="view_task_form" model="ir.ui.view">
<field name="model">external.file.task</field> <field name="model">external.file.task</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
@ -20,9 +23,28 @@
<span colspan="2"/> <span colspan="2"/>
<field name="filename" colspan="4" attrs="{'invisible':[('method_type','!=','import')], 'required':[('method_type', '=', 'import')]}"/> <field name="filename" colspan="4" attrs="{'invisible':[('method_type','!=','import')], 'required':[('method_type', '=', 'import')]}"/>
<field name="filepath" colspan="4" /> <field name="filepath" colspan="4" />
<field name="after_import" colspan="2" attrs="{'invisible':[('method_type','!=','import')]}"/>
<field name="move_path" colspan="2" attrs="{'invisible':['|', ('after_import','!=','move'), ('method_type','!=','import')]}"/>
<field name="md5_check" colspan="2" attrs="{'invisible':[('method_type','!=','import')]}"/>
</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':['|', '&amp;',
('after_import','!=','move'),
('after_import','!=','move_rename'),
('method_type','!=','import')]}"/>
<field name="new_name" colspan="4"
attrs="{'invisible': ['|', '&amp;',
('after_import','!=','rename'),
('after_import','!=','move_rename'),
('method_type','!=','import')]}"/>
</group>
<field name="md5_check" colspan="2"
attrs="{'invisible':
[('method_type','!=','import')]}"
/>
</group>
<group string="Data importation setting">
<field name="file_type" attrs="{'invisible':[('method_type','!=','import')]}"/>
</group> </group>
</sheet> </sheet>
</form> </form>
Loading…
Cancel
Save