mirror of
https://github.com/OCA/knowledge.git
synced 2025-07-26 18:38:41 -06:00
Merge 693c723f7f
into 4a1228e26f
This commit is contained in:
commit
2d960e979a
@ -37,6 +37,8 @@ install:
|
|||||||
- pip install --upgrade paramiko
|
- pip install --upgrade paramiko
|
||||||
- pip install --upgrade pyth
|
- pip install --upgrade pyth
|
||||||
- travis_install_nightly
|
- travis_install_nightly
|
||||||
|
- sudo pip install paramiko
|
||||||
|
- sudo pip install fs
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- travis_run_tests
|
- travis_run_tests
|
||||||
|
51
attachment_metadata/README.rst
Normal file
51
attachment_metadata/README.rst
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||||
|
:alt: License
|
||||||
|
|
||||||
|
Attachment Metadata
|
||||||
|
======================
|
||||||
|
|
||||||
|
This module was written to extend the functionality of ir.attachment
|
||||||
|
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
Installable without any requirements
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
THe module just add some field to ir.attachment
|
||||||
|
|
||||||
|
For further information, please visit:
|
||||||
|
|
||||||
|
* https://www.odoo.com/forum/help-1
|
||||||
|
|
||||||
|
Known issues / Roadmap
|
||||||
|
======================
|
||||||
|
|
||||||
|
Credits
|
||||||
|
=======
|
||||||
|
|
||||||
|
* Joel Grand-Guillaume Camptocamp
|
||||||
|
* initOS <http://initos.com>
|
||||||
|
* Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Sebastien BEAU <sebastian.beau@akretion.com>
|
||||||
|
|
||||||
|
Maintainer
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
|
||||||
|
.. image:: http://odoo-community.org/logo.png
|
||||||
|
:alt: Odoo Community Association
|
||||||
|
:target: http://odoo-community.org
|
||||||
|
|
||||||
|
This module is maintained by the OCA.
|
||||||
|
|
||||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.
|
||||||
|
|
||||||
|
To contribute to this module, please visit http://odoo-community.org.
|
23
attachment_metadata/__init__.py
Normal file
23
attachment_metadata/__init__.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Module for OpenERP
|
||||||
|
# Copyright (C) 2015 Akretion (http://www.akretion.com).
|
||||||
|
# @author Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from . import attachment
|
43
attachment_metadata/__openerp__.py
Normal file
43
attachment_metadata/__openerp__.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Module for OpenERP
|
||||||
|
# Copyright (C) 2015 Akretion (http://www.akretion.com).
|
||||||
|
# @author Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
{'name': 'attachment_metadata',
|
||||||
|
'version': '0.0.1',
|
||||||
|
'author': 'Akretion',
|
||||||
|
'website': 'www.akretion.com',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'category': 'Generic Modules',
|
||||||
|
'description': """
|
||||||
|
Add some useful field to ir.attachment object like:
|
||||||
|
internal and external hash for coherence verification
|
||||||
|
""",
|
||||||
|
'depends': [
|
||||||
|
'base',
|
||||||
|
'mail'
|
||||||
|
],
|
||||||
|
'data': [
|
||||||
|
'attachment_view.xml',
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'application': False,
|
||||||
|
}
|
47
attachment_metadata/attachment.py
Normal file
47
attachment_metadata/attachment.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Module for OpenERP
|
||||||
|
# Copyright 2011-2012 Camptocamp SA
|
||||||
|
# @author: Joel Grand-Guillaume
|
||||||
|
# Copyright (C) 2015 Akretion (http://www.akretion.com).
|
||||||
|
# @author Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from openerp import models, fields, api, _
|
||||||
|
from openerp.exceptions import Warning
|
||||||
|
import hashlib
|
||||||
|
from base64 import b64decode
|
||||||
|
|
||||||
|
|
||||||
|
class IrAttachmentMetadata(models.Model):
|
||||||
|
_name = 'ir.attachment.metadata'
|
||||||
|
_inherits = {'ir.attachment': 'attachment_id'}
|
||||||
|
|
||||||
|
internal_hash = fields.Char(store=True, compute='_compute_hash')
|
||||||
|
external_hash = fields.Char()
|
||||||
|
attachment_id = fields.Many2one('ir.attachment', required=True,
|
||||||
|
ondelete='cascade')
|
||||||
|
|
||||||
|
@api.depends('datas', 'external_hash')
|
||||||
|
def _compute_hash(self):
|
||||||
|
if self.datas:
|
||||||
|
self.internal_hash = hashlib.md5(b64decode(self.datas)).hexdigest()
|
||||||
|
if self.external_hash and self.internal_hash != self.external_hash:
|
||||||
|
raise Warning(_('File corrupted'),
|
||||||
|
_("Something was wrong with the retreived file, "
|
||||||
|
"please relaunch the task."))
|
17
attachment_metadata/attachment_view.xml
Normal file
17
attachment_metadata/attachment_view.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?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="base.view_attachment_form" />
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="url" position="after">
|
||||||
|
<field name="internal_hash"/>
|
||||||
|
<field name="external_hash"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
2
attachment_metadata/security/ir.model.access.csv
Normal file
2
attachment_metadata/security/ir.model.access.csv
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_attachment_metadata_user,ir.attachment.metadata.user,model_ir_attachment_metadata,base.group_user,1,0,0,0
|
|
59
external_file_location/README.rst
Normal file
59
external_file_location/README.rst
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||||
|
:alt: License
|
||||||
|
|
||||||
|
External File Location
|
||||||
|
======================
|
||||||
|
|
||||||
|
This module was written to extend the functionality of ir.attachment to support remote communication and allow you to import/export file to a remote server
|
||||||
|
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
To install this module, you need to:
|
||||||
|
|
||||||
|
* fs python module
|
||||||
|
* Paramiko python module
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
To use this module, you need to:
|
||||||
|
|
||||||
|
* Add a location with your server infos
|
||||||
|
* Create a task with your file info and remote communication method
|
||||||
|
* A cron task will trigger each task
|
||||||
|
|
||||||
|
For further information, please visit:
|
||||||
|
|
||||||
|
* https://www.odoo.com/forum/help-1
|
||||||
|
|
||||||
|
Known issues / Roadmap
|
||||||
|
======================
|
||||||
|
|
||||||
|
|
||||||
|
Credits
|
||||||
|
=======
|
||||||
|
|
||||||
|
* Joel Grand-Guillaume Camptocamp
|
||||||
|
* initOS <http://initos.com>
|
||||||
|
* Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Sebastien BEAU <sebastian.beau@akretion.com>
|
||||||
|
|
||||||
|
Maintainer
|
||||||
|
----------
|
||||||
|
|
||||||
|
* Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
|
||||||
|
.. image:: http://odoo-community.org/logo.png
|
||||||
|
:alt: Odoo Community Association
|
||||||
|
:target: http://odoo-community.org
|
||||||
|
|
||||||
|
This module is maintained by the OCA.
|
||||||
|
|
||||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.
|
||||||
|
|
||||||
|
To contribute to this module, please visit http://odoo-community.org.
|
27
external_file_location/__init__.py
Normal file
27
external_file_location/__init__.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Module for OpenERP
|
||||||
|
# Copyright (C) 2014 Akretion (http://www.akretion.com).
|
||||||
|
# @author Sébastien BEAU <sebastien.beau@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from . import attachment
|
||||||
|
from . import location
|
||||||
|
from . import task
|
||||||
|
from . import tasks
|
||||||
|
from . import tests
|
49
external_file_location/__openerp__.py
Normal file
49
external_file_location/__openerp__.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Module for OpenERP
|
||||||
|
# Copyright (C) 2015 Akretion (http://www.akretion.com).
|
||||||
|
# @author Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'external_file_location',
|
||||||
|
'version': '0.0.1',
|
||||||
|
'author': 'Akretion',
|
||||||
|
'website': 'www.akretion.com',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'category': 'Generic Modules',
|
||||||
|
'depends': [
|
||||||
|
'attachment_metadata',
|
||||||
|
],
|
||||||
|
'external_dependencies': {
|
||||||
|
'python': [
|
||||||
|
'fs',
|
||||||
|
'paramiko',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'data': [
|
||||||
|
'menu.xml',
|
||||||
|
'attachment_view.xml',
|
||||||
|
'location_view.xml',
|
||||||
|
'task_view.xml',
|
||||||
|
'cron.xml',
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'application': True,
|
||||||
|
}
|
26
external_file_location/abstract_task.py
Normal file
26
external_file_location/abstract_task.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from base64 import b64encode
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractTask(object):
|
||||||
|
|
||||||
|
_name = None
|
||||||
|
_key = None
|
||||||
|
_synchronize_type = None
|
||||||
|
_default_port = None
|
||||||
|
_hide_login = False
|
||||||
|
_hide_password = False
|
||||||
|
_hide_port = False
|
||||||
|
|
||||||
|
def create_file(self, filename, data):
|
||||||
|
ir_attachment_id = self.env['ir.attachment.metadata'].create({
|
||||||
|
'name': filename,
|
||||||
|
'datas': b64encode(data),
|
||||||
|
'datas_fname': filename,
|
||||||
|
'task_id': self.task and self.task.id or False,
|
||||||
|
'location_id': self.task and self.task.location_id.id or False,
|
||||||
|
'external_hash': self.ext_hash
|
||||||
|
})
|
||||||
|
return ir_attachment_id
|
39
external_file_location/attachment.py
Normal file
39
external_file_location/attachment.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Module for OpenERP
|
||||||
|
# Copyright (C) 2015 Akretion (http://www.akretion.com).
|
||||||
|
# @author Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from openerp import models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class IrAttachmentMetadata(models.Model):
|
||||||
|
_inherit = 'ir.attachment.metadata'
|
||||||
|
|
||||||
|
sync_date = fields.Datetime()
|
||||||
|
state = fields.Selection([
|
||||||
|
('pending', 'Pending'),
|
||||||
|
('failed', 'Failed'),
|
||||||
|
('done', 'Done'),
|
||||||
|
], readonly=False, required=True, default='pending')
|
||||||
|
state_message = fields.Text()
|
||||||
|
task_id = fields.Many2one('external.file.task', string='Task')
|
||||||
|
location_id = fields.Many2one('external.file.location', string='Location',
|
||||||
|
related='task_id.location_id', store=True
|
||||||
|
)
|
99
external_file_location/attachment_view.xml
Normal file
99
external_file_location/attachment_view.xml
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<?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>
|
18
external_file_location/cron.xml
Normal file
18
external_file_location/cron.xml
Normal file
@ -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</field>
|
||||||
|
<field name="args">([])</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
60
external_file_location/helper.py
Normal file
60
external_file_location/helper.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Author: Joel Grand-Guillaume
|
||||||
|
# Copyright 2011-2012 Camptocamp SA
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def itersubclasses(cls, _seen=None):
|
||||||
|
"""
|
||||||
|
itersubclasses(cls)
|
||||||
|
Generator over all subclasses of a given class, in depth first order.
|
||||||
|
>>> list(itersubclasses(int)) == [bool]
|
||||||
|
True
|
||||||
|
>>> class A(object): pass
|
||||||
|
>>> class B(A): pass
|
||||||
|
>>> class C(A): pass
|
||||||
|
>>> class D(B,C): pass
|
||||||
|
>>> class E(D): pass
|
||||||
|
>>>
|
||||||
|
>>> for cls in itersubclasses(A):
|
||||||
|
... print(cls.__name__)
|
||||||
|
B
|
||||||
|
D
|
||||||
|
E
|
||||||
|
C
|
||||||
|
>>> # get ALL (new-style) classes currently defined
|
||||||
|
>>> [cls.__name__ for cls in itersubclasses(object)] #doctest: +ELLIPSIS
|
||||||
|
['type', ...'tuple', ...]
|
||||||
|
"""
|
||||||
|
if not isinstance(cls, type):
|
||||||
|
raise TypeError('itersubclasses must be called with '
|
||||||
|
'new-style classes, not %.100r' % cls
|
||||||
|
)
|
||||||
|
if _seen is None:
|
||||||
|
_seen = set()
|
||||||
|
try:
|
||||||
|
subs = cls.__subclasses__()
|
||||||
|
except TypeError: # fails only when cls is type
|
||||||
|
subs = cls.__subclasses__(cls)
|
||||||
|
for sub in subs:
|
||||||
|
if sub not in _seen:
|
||||||
|
_seen.add(sub)
|
||||||
|
yield sub
|
||||||
|
for sub in itersubclasses(sub, _seen):
|
||||||
|
yield sub
|
69
external_file_location/location.py
Normal file
69
external_file_location/location.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Module for OpenERP
|
||||||
|
# Copyright (C) 2015 Akretion (http://www.akretion.com).
|
||||||
|
# @author Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from openerp import models, fields, api
|
||||||
|
from .abstract_task import AbstractTask
|
||||||
|
from .helper import itersubclasses
|
||||||
|
|
||||||
|
|
||||||
|
class Location(models.Model):
|
||||||
|
_name = 'external.file.location'
|
||||||
|
_description = 'Description'
|
||||||
|
|
||||||
|
name = fields.Char(string='Name', required=True)
|
||||||
|
protocol = fields.Selection(selection='_get_protocol', required=True)
|
||||||
|
address = fields.Char(string='Address', required=True)
|
||||||
|
port = fields.Integer()
|
||||||
|
login = fields.Char()
|
||||||
|
password = fields.Char()
|
||||||
|
task_ids = fields.One2many('external.file.task', 'location_id')
|
||||||
|
hide_login = fields.Boolean()
|
||||||
|
hide_password = fields.Boolean()
|
||||||
|
hide_port = fields.Boolean()
|
||||||
|
|
||||||
|
def _get_protocol(self):
|
||||||
|
res = []
|
||||||
|
for cls in itersubclasses(AbstractTask):
|
||||||
|
if not cls._synchronize_type:
|
||||||
|
cls_info = (cls._key, cls._name)
|
||||||
|
res.append(cls_info)
|
||||||
|
elif not cls._synchronize_type and cls._key and cls._name:
|
||||||
|
pass
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.onchange('protocol')
|
||||||
|
def onchange_protocol(self):
|
||||||
|
for cls in itersubclasses(AbstractTask):
|
||||||
|
if cls._key == self.protocol:
|
||||||
|
self.port = cls._default_port
|
||||||
|
if cls._hide_login:
|
||||||
|
self.hide_login = True
|
||||||
|
else:
|
||||||
|
self.hide_login = False
|
||||||
|
if cls._hide_password:
|
||||||
|
self.hide_password = True
|
||||||
|
else:
|
||||||
|
self.hide_password = False
|
||||||
|
if cls._hide_port:
|
||||||
|
self.hide_port = True
|
||||||
|
else:
|
||||||
|
self.hide_port = False
|
69
external_file_location/location_view.xml
Normal file
69
external_file_location/location_view.xml
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?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"/>
|
||||||
|
<field name="port" colspan="2" attrs="{'invisible': [('hide_port', '=', True)], 'required': [('hide_port', '=', False)]}"/>
|
||||||
|
<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"/>
|
||||||
|
<field name="filename"/>
|
||||||
|
<field name="filepath"/>
|
||||||
|
<button name="run" type="object" string="Run" icon="gtk-execute"/>
|
||||||
|
</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="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">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="menu_file_exchange"
|
||||||
|
sequence="20"
|
||||||
|
action="action_location"/>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
||||||
|
|
12
external_file_location/menu.xml
Normal file
12
external_file_location/menu.xml
Normal file
@ -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>
|
3
external_file_location/security/ir.model.access.csv
Normal file
3
external_file_location/security/ir.model.access.csv
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_external_file_location_user,external.file.location.user,model_external_file_location,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
|
|
99
external_file_location/task.py
Normal file
99
external_file_location/task.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Module for OpenERP
|
||||||
|
# Copyright (C) 2015 Akretion (http://www.akretion.com).
|
||||||
|
# @author Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from openerp import models, fields, api
|
||||||
|
from .helper import itersubclasses
|
||||||
|
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 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 cls._synchronize_type and \
|
||||||
|
cls._key + '_' + cls._synchronize_type == self.method:
|
||||||
|
method_class = cls
|
||||||
|
config = {
|
||||||
|
'host': self.location_id.address,
|
||||||
|
'user': self.location_id.login,
|
||||||
|
'pwd': self.location_id.password,
|
||||||
|
'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()
|
46
external_file_location/task_view.xml
Normal file
46
external_file_location/task_view.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<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" type="object" string="Run" icon="gtk-execute"/>
|
||||||
|
</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" 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="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>
|
||||||
|
</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"/>
|
||||||
|
<field name="filename"/>
|
||||||
|
<field name="filepath"/>
|
||||||
|
<button name="run" type="object" string="Run" icon="gtk-execute"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
26
external_file_location/tasks/__init__.py
Normal file
26
external_file_location/tasks/__init__.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Module for OpenERP
|
||||||
|
# Copyright (C) 2015 Akretion (http://www.akretion.com).
|
||||||
|
# @author Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from . import abstract_fs
|
||||||
|
from . import ftp
|
||||||
|
from . import sftp
|
||||||
|
from . import filestore
|
139
external_file_location/tasks/abstract_fs.py
Normal file
139
external_file_location/tasks/abstract_fs.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 initOS GmbH & Co. KG (<http://www.initos.com>).
|
||||||
|
# @author Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
from ..abstract_task import AbstractTask
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractFSTask(AbstractTask):
|
||||||
|
|
||||||
|
_name = None
|
||||||
|
_key = None
|
||||||
|
_synchronize_type = None
|
||||||
|
_default_port = None
|
||||||
|
|
||||||
|
def __init__(self, env, config):
|
||||||
|
self.env = env
|
||||||
|
self.host = config.get('host', '')
|
||||||
|
self.user = config.get('user', '')
|
||||||
|
self.pwd = config.get('pwd', '')
|
||||||
|
self.port = config.get('port', '')
|
||||||
|
self.allow_dir_creation = config.get('allow_dir_creation', '')
|
||||||
|
self.file_name = config.get('file_name', '')
|
||||||
|
self.path = config.get('path') or '.'
|
||||||
|
self.move_path = config.get('move_path', '')
|
||||||
|
self.after_import = config.get('after_import', False)
|
||||||
|
self.attachment_ids = config.get('attachment_ids', False)
|
||||||
|
self.task = config.get('task', False)
|
||||||
|
self.ext_hash = False
|
||||||
|
self.md5_check = config.get('md5_check', False)
|
||||||
|
|
||||||
|
def _handle_new_source(self, fs_conn, download_directory, file_name,
|
||||||
|
move_directory):
|
||||||
|
"""open and read given file into create_file method,
|
||||||
|
move file if move_directory is given"""
|
||||||
|
with fs_conn.open(self._source_name(download_directory, file_name),
|
||||||
|
"rb") as fileobj:
|
||||||
|
data = fileobj.read()
|
||||||
|
return self.create_file(file_name, data)
|
||||||
|
|
||||||
|
def _source_name(self, download_directory, file_name):
|
||||||
|
"""helper to get the full name"""
|
||||||
|
return os.path.join(download_directory, file_name)
|
||||||
|
|
||||||
|
def _move_file(self, fs_conn, source, target):
|
||||||
|
"""Moves a file on the server"""
|
||||||
|
_logger.info('Moving file %s %s' % (source, target))
|
||||||
|
fs_conn.rename(source, target)
|
||||||
|
if self.md5_check:
|
||||||
|
fs_conn.rename(source + '.md5', target + '.md5')
|
||||||
|
|
||||||
|
def _delete_file(self, fs_conn, source):
|
||||||
|
"""Deletes a file from the server"""
|
||||||
|
_logger.info('Deleting file %s' % source)
|
||||||
|
fs_conn.remove(source)
|
||||||
|
if self.md5_check:
|
||||||
|
fs_conn.remove(source + '.md5')
|
||||||
|
|
||||||
|
def _get_hash(self, file_name, fs_conn):
|
||||||
|
hash_file_name = file_name + '.md5'
|
||||||
|
with fs_conn.open(hash_file_name, 'rb') as f:
|
||||||
|
return f.read().rstrip('\r\n')
|
||||||
|
|
||||||
|
def _get_files(self, conn, path):
|
||||||
|
process_files = []
|
||||||
|
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))
|
||||||
|
return process_files
|
||||||
|
|
||||||
|
def _process_file(self, conn, file_to_process):
|
||||||
|
if self.md5_check:
|
||||||
|
self.ext_hash = self._get_hash(file_to_process[1], conn)
|
||||||
|
self._handle_new_source(
|
||||||
|
conn,
|
||||||
|
self.path,
|
||||||
|
self.file_name,
|
||||||
|
self.move_path)
|
||||||
|
|
||||||
|
# Move/delete files only after all files have been processed.
|
||||||
|
if self.after_import == 'delete':
|
||||||
|
self._delete_file(conn, file_to_process[1])
|
||||||
|
elif self.after_import == 'move':
|
||||||
|
if not conn.exists(self.move_path):
|
||||||
|
conn.makedir(self.move_path)
|
||||||
|
self._move_file(
|
||||||
|
conn,
|
||||||
|
file_to_process[1],
|
||||||
|
self._source_name(self.move_path, file_to_process[0]))
|
||||||
|
|
||||||
|
def _handle_existing_target(self, fs_conn, target_name, filedata):
|
||||||
|
raise Exception("%s already exists" % target_name)
|
||||||
|
|
||||||
|
def _handle_new_target(self, fs_conn, target_name, filedata):
|
||||||
|
try:
|
||||||
|
with fs_conn.open(target_name, mode='wb') as fileobj:
|
||||||
|
fileobj.write(filedata)
|
||||||
|
_logger.info('wrote %s, size %d', target_name, len(filedata))
|
||||||
|
self.attachment_id.state = 'done'
|
||||||
|
self.attachment_id.state_message = ''
|
||||||
|
except IOError:
|
||||||
|
self.attachment_id.state = 'failed'
|
||||||
|
self.attachment_id.state_message = (
|
||||||
|
'The directory doesn\'t exist or had insufficient rights')
|
||||||
|
|
||||||
|
def _target_name(self, fs_conn, upload_directory, filename):
|
||||||
|
return os.path.join(upload_directory, filename)
|
||||||
|
|
||||||
|
def _upload_file(self, conn, host, port, user, pwd,
|
||||||
|
path, filename, filedata):
|
||||||
|
upload_directory = path or '.'
|
||||||
|
target_name = self._target_name(conn,
|
||||||
|
upload_directory,
|
||||||
|
filename)
|
||||||
|
if conn.isfile(target_name):
|
||||||
|
self._handle_existing_target(conn, target_name, filedata)
|
||||||
|
else:
|
||||||
|
self._handle_new_target(conn, target_name, filedata)
|
67
external_file_location/tasks/filestore.py
Normal file
67
external_file_location/tasks/filestore.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 initOS GmbH & Co. KG (<http://www.initos.com>).
|
||||||
|
# @author Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
from .abstract_fs import AbstractFSTask
|
||||||
|
from base64 import b64decode
|
||||||
|
from fs import osfs
|
||||||
|
import logging
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class FileStoreTask(AbstractFSTask):
|
||||||
|
|
||||||
|
_key = 'filestore'
|
||||||
|
_name = 'File Store'
|
||||||
|
_synchronize_type = None
|
||||||
|
_default_port = None
|
||||||
|
_hide_login = True
|
||||||
|
_hide_password = True
|
||||||
|
_hide_port = True
|
||||||
|
|
||||||
|
|
||||||
|
class FileStoreImportTask(FileStoreTask):
|
||||||
|
|
||||||
|
_synchronize_type = 'import'
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
with osfs.OSFS(self.host) as fs_conn:
|
||||||
|
files_to_process = self._get_files(fs_conn, self.path)
|
||||||
|
for file_to_process in files_to_process:
|
||||||
|
self._process_file(fs_conn, file_to_process)
|
||||||
|
|
||||||
|
|
||||||
|
class FileStoreExportTask(FileStoreTask):
|
||||||
|
|
||||||
|
_synchronize_type = 'export'
|
||||||
|
|
||||||
|
def run(self, async=True):
|
||||||
|
for attachment in self.attachment_ids:
|
||||||
|
if attachment.state in ('pending', 'failed'):
|
||||||
|
self.attachment_id = attachment
|
||||||
|
with osfs.OSFS(self.host) as fs_conn:
|
||||||
|
self._upload_file(fs_conn,
|
||||||
|
self.host,
|
||||||
|
self.port,
|
||||||
|
self.user,
|
||||||
|
self.pwd,
|
||||||
|
self.path,
|
||||||
|
attachment.datas_fname,
|
||||||
|
b64decode(attachment.datas))
|
65
external_file_location/tasks/ftp.py
Normal file
65
external_file_location/tasks/ftp.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 initOS GmbH & Co. KG (<http://www.initos.com>).
|
||||||
|
# @author Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
from .abstract_fs import AbstractFSTask
|
||||||
|
from base64 import b64decode
|
||||||
|
from fs import ftpfs
|
||||||
|
import logging
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class FtpTask(AbstractFSTask):
|
||||||
|
|
||||||
|
_key = 'ftp'
|
||||||
|
_name = 'FTP'
|
||||||
|
_synchronize_type = None
|
||||||
|
_default_port = 21
|
||||||
|
_hide_login = False
|
||||||
|
_hide_password = False
|
||||||
|
_hide_port = False
|
||||||
|
|
||||||
|
|
||||||
|
class FtpImportTask(FtpTask):
|
||||||
|
|
||||||
|
_synchronize_type = 'import'
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
with ftpfs.FTPFS(self.host, self.user, self.pwd,
|
||||||
|
port=self.port) as ftp_conn:
|
||||||
|
files_to_process = self._get_files(ftp_conn, self.path)
|
||||||
|
for file_to_process in files_to_process:
|
||||||
|
self._process_file(ftp_conn, file_to_process)
|
||||||
|
|
||||||
|
|
||||||
|
class FtpExportTask(FtpTask):
|
||||||
|
|
||||||
|
_synchronize_type = 'export'
|
||||||
|
|
||||||
|
def run(self, async=True):
|
||||||
|
for attachment in self.attachment_ids:
|
||||||
|
if attachment.state in ('pending', 'failed'):
|
||||||
|
self.attachment_id = attachment
|
||||||
|
with ftpfs.FTPFS(self.host, self.user, self.pwd,
|
||||||
|
port=self.port) as ftp_conn:
|
||||||
|
self._upload_file(ftp_conn, self.host, self.port,
|
||||||
|
self.user, self.pwd, self.path,
|
||||||
|
attachment.datas_fname,
|
||||||
|
b64decode(attachment.datas))
|
71
external_file_location/tasks/sftp.py
Normal file
71
external_file_location/tasks/sftp.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 initOS GmbH & Co. KG (<http://www.initos.com>).
|
||||||
|
# @author Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
from .abstract_fs import AbstractFSTask
|
||||||
|
from base64 import b64decode
|
||||||
|
from fs import sftpfs
|
||||||
|
import logging
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SftpTask(AbstractFSTask):
|
||||||
|
|
||||||
|
_key = 'sftp'
|
||||||
|
_name = 'SFTP'
|
||||||
|
_synchronize_type = None
|
||||||
|
_default_port = 22
|
||||||
|
_hide_login = False
|
||||||
|
_hide_password = False
|
||||||
|
_hide_port = False
|
||||||
|
|
||||||
|
|
||||||
|
class SftpImportTask(SftpTask):
|
||||||
|
|
||||||
|
_synchronize_type = 'import'
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
connection_string = "{}:{}".format(self.host, self.port)
|
||||||
|
root = "/home/{}".format(self.user)
|
||||||
|
with sftpfs.SFTPFS(connection=connection_string,
|
||||||
|
root_path=root,
|
||||||
|
username=self.user,
|
||||||
|
password=self.pwd) as sftp_conn:
|
||||||
|
files_to_process = self._get_files(sftp_conn, self.path)
|
||||||
|
for file_to_process in files_to_process:
|
||||||
|
self._process_file(sftp_conn, file_to_process)
|
||||||
|
|
||||||
|
|
||||||
|
class SftpExportTask(SftpTask):
|
||||||
|
|
||||||
|
_synchronize_type = 'export'
|
||||||
|
|
||||||
|
def run(self, async=True):
|
||||||
|
for attachment in self.attachment_ids:
|
||||||
|
if attachment.state in ('pending', 'failed'):
|
||||||
|
self.attachment_id = attachment
|
||||||
|
connection_string = "{}:{}".format(self.host, self.port)
|
||||||
|
with sftpfs.SFTPFS(connection=connection_string,
|
||||||
|
username=self.user,
|
||||||
|
password=self.pwd) as sftp_conn:
|
||||||
|
datas = b64decode(attachment.datas)
|
||||||
|
self._upload_file(sftp_conn, self.host, self.port,
|
||||||
|
self.user, self.pwd, self.path,
|
||||||
|
attachment.datas_fname, datas)
|
24
external_file_location/tests/__init__.py
Normal file
24
external_file_location/tests/__init__.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Module for OpenERP
|
||||||
|
# Copyright (C) 2015 Akretion (http://www.akretion.com).
|
||||||
|
# @author Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from . import mock_server
|
||||||
|
from . import test_sftp
|
75
external_file_location/tests/mock_server.py
Normal file
75
external_file_location/tests/mock_server.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Copyright (C) 2015 Akretion (http://www.akretion.com).
|
||||||
|
# @author Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
import 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(response):
|
||||||
|
with mock.patch('fs.sftpfs.SFTPFS', ConnMock(response)) as SFTPFS:
|
||||||
|
yield SFTPFS._calls
|
141
external_file_location/tests/test_sftp.py
Normal file
141
external_file_location/tests/test_sftp.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Module for OpenERP
|
||||||
|
# Copyright (C) 2015 Akretion (http://www.akretion.com).
|
||||||
|
# @author Valentin CHEMIERE <valentin.chemiere@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
import openerp.tests.common as common
|
||||||
|
from ..tasks.sftp import SftpImportTask
|
||||||
|
from ..tasks.sftp import SftpExportTask
|
||||||
|
from .mock_server import (server_mock)
|
||||||
|
from .mock_server import MultiResponse
|
||||||
|
from StringIO import StringIO
|
||||||
|
from base64 import b64decode
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
|
||||||
|
class ContextualStringIO(StringIO):
|
||||||
|
"""
|
||||||
|
snippet from http://bit.ly/1HfH6uW (stackoverflow)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
self.close()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class TestNewSource(common.TransactionCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestNewSource, self).setUp()
|
||||||
|
self.test_file = ContextualStringIO()
|
||||||
|
self.test_file.write('import')
|
||||||
|
self.test_file.seek(0)
|
||||||
|
self.config = \
|
||||||
|
{'file_name': 'testfile',
|
||||||
|
'user': 'test',
|
||||||
|
'password': 'test',
|
||||||
|
'host': 'test',
|
||||||
|
'port': 22,
|
||||||
|
'attachment_ids': self.env['ir.attachment.metadata'].search([])
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_00_sftp_import(self):
|
||||||
|
with server_mock(
|
||||||
|
{'exists': True,
|
||||||
|
'makedir': True,
|
||||||
|
'open': self.test_file,
|
||||||
|
'listdir': ['testfile']
|
||||||
|
}):
|
||||||
|
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')
|
||||||
|
|
||||||
|
def test_01_sftp_export(self):
|
||||||
|
with server_mock(
|
||||||
|
{'isfile': False,
|
||||||
|
'open': self.test_file,
|
||||||
|
}) as FakeSFTP:
|
||||||
|
task = SftpExportTask(self.env, self.config)
|
||||||
|
task.run()
|
||||||
|
if FakeSFTP:
|
||||||
|
self.assertEqual('open', FakeSFTP[-1]['method'])
|
||||||
|
|
||||||
|
def test_02_sftp_import_delete(self):
|
||||||
|
with server_mock(
|
||||||
|
{'exists': True,
|
||||||
|
'makedir': True,
|
||||||
|
'open': self.test_file,
|
||||||
|
'listdir': ['testfile'],
|
||||||
|
'remove': True
|
||||||
|
}) as FakeSFTP:
|
||||||
|
self.config.update({'after_import': 'delete'})
|
||||||
|
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('remove', FakeSFTP[-1]['method'])
|
||||||
|
|
||||||
|
def test_03_sftp_import_move(self):
|
||||||
|
with server_mock(
|
||||||
|
{'exists': True,
|
||||||
|
'makedir': True,
|
||||||
|
'open': self.test_file,
|
||||||
|
'listdir': ['testfile'],
|
||||||
|
'rename': True
|
||||||
|
}) as FakeSFTP:
|
||||||
|
self.config.update({'after_import': 'move', 'move_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[-1]['method'])
|
||||||
|
|
||||||
|
def test_04_sftp_import_md5(self):
|
||||||
|
md5_file = ContextualStringIO()
|
||||||
|
md5_file.write(hashlib.md5('import').hexdigest())
|
||||||
|
md5_file.seek(0)
|
||||||
|
with server_mock(
|
||||||
|
{'exists': True,
|
||||||
|
'makedir': True,
|
||||||
|
'open': MultiResponse({
|
||||||
|
1: self.test_file,
|
||||||
|
0: md5_file
|
||||||
|
}),
|
||||||
|
'listdir': ['testfile', 'testfile.md5'],
|
||||||
|
}) as FakeSFTP:
|
||||||
|
self.config.update({'md5_check': True})
|
||||||
|
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('open', FakeSFTP[-1]['method'])
|
||||||
|
self.assertEqual('open', FakeSFTP[1]['method'])
|
||||||
|
self.assertEqual(('./testfile.md5', 'rb'), FakeSFTP[1]['args'])
|
24
file_email/__init__.py
Normal file
24
file_email/__init__.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# file_email for OpenERP
|
||||||
|
# Copyright (C) 2012-TODAY Akretion <http://www.akretion.com>.
|
||||||
|
# @author Sébastien BEAU <sebastien.beau@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from . import attachment_metadata
|
||||||
|
from . import fetchmail
|
44
file_email/__openerp__.py
Normal file
44
file_email/__openerp__.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# file_email for OpenERP
|
||||||
|
# Copyright (C) 2012-TODAY Akretion <http://www.akretion.com>.
|
||||||
|
# @author Sébastien BEAU <sebastien.beau@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'file_email',
|
||||||
|
'version': '0.1',
|
||||||
|
'category': 'Generic Modules/Others',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'description': """Abstract module for importing and processing the
|
||||||
|
attachment of an email. The attachment of the email will be imported
|
||||||
|
as a attachment_metadata and then in your custom module you can process it.
|
||||||
|
An example of processing can be found in account_statement_email
|
||||||
|
""",
|
||||||
|
'author': 'Akretion',
|
||||||
|
'website': 'http://www.akretion.com/',
|
||||||
|
'depends': ['external_file_location', 'fetchmail'],
|
||||||
|
'init_xml': [],
|
||||||
|
'update_xml': [
|
||||||
|
"fetchmail_view.xml",
|
||||||
|
"attachment_metadata_view.xml",
|
||||||
|
],
|
||||||
|
'demo_xml': [],
|
||||||
|
'installable': True,
|
||||||
|
'active': False,
|
||||||
|
}
|
139
file_email/attachment_metadata.py
Normal file
139
file_email/attachment_metadata.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# file_email for OpenERP
|
||||||
|
# Copyright (C) 2012-TODAY Akretion <http://www.akretion.com>.
|
||||||
|
# @author Sébastien BEAU <sebastien.beau@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from openerp import models, fields, api
|
||||||
|
import base64
|
||||||
|
|
||||||
|
|
||||||
|
class IrAttachmentMetadata(models.Model):
|
||||||
|
_inherit = "ir.attachment.metadata"
|
||||||
|
|
||||||
|
fetchmail_server_id = fields.Many2one('fetchmail.server',
|
||||||
|
string='Email Server')
|
||||||
|
|
||||||
|
def message_process(self, cr, uid, model, message, custom_values=None,
|
||||||
|
save_original=False, strip_attachments=False,
|
||||||
|
thread_id=None, context=None):
|
||||||
|
if context is None:
|
||||||
|
context = {}
|
||||||
|
context['no_post'] = True
|
||||||
|
return True
|
||||||
|
|
||||||
|
def message_post(self, cr, uid, thread_id, body='', subject=None,
|
||||||
|
type='notification', subtype=None, parent_id=False,
|
||||||
|
attachments=None, content_subtype='html', context=None,
|
||||||
|
**kwargs):
|
||||||
|
if context.get('no_post'):
|
||||||
|
return None
|
||||||
|
return True
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_attachment_metadata_data(self, condition, msg, att):
|
||||||
|
values = {
|
||||||
|
'file_type': condition.server_id.file_type,
|
||||||
|
'name': msg['subject'],
|
||||||
|
'direction': 'input',
|
||||||
|
'date': msg['date'],
|
||||||
|
'ext_id': msg['message_id'],
|
||||||
|
'datas_fname': att[0],
|
||||||
|
'datas': base64.b64encode(att[1]),
|
||||||
|
'state': 'pending'
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def prepare_data_from_basic_condition(self, condition, msg):
|
||||||
|
vals = {}
|
||||||
|
if (condition.from_email in msg['from']
|
||||||
|
and condition.mail_subject in msg['subject']):
|
||||||
|
for att in msg['attachments']:
|
||||||
|
if condition.file_extension in att[0]:
|
||||||
|
vals = self._get_attachment_metadata_data(condition,
|
||||||
|
msg, att)
|
||||||
|
break
|
||||||
|
return vals
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _prepare_data_for_attachment_metadata(self, msg):
|
||||||
|
"""Method to prepare the data for creating a attachment metadata.
|
||||||
|
:param msg: a dictionnary with the email data
|
||||||
|
:type: dict
|
||||||
|
|
||||||
|
:return: a list of dictionnary that containt
|
||||||
|
the attachment metadata data
|
||||||
|
:rtype: list
|
||||||
|
"""
|
||||||
|
res = []
|
||||||
|
server_id = self._context.get('fetchmail_server_id', False)
|
||||||
|
file_condition_obj = self.env['ir.attachment.metadata.condition']
|
||||||
|
cond_ids = file_condition_obj.search([('server_id', '=', server_id)])
|
||||||
|
if cond_ids:
|
||||||
|
for cond in cond_ids:
|
||||||
|
if cond.type == 'normal':
|
||||||
|
vals = self.prepare_data_from_basic_condition(cond, msg)
|
||||||
|
else:
|
||||||
|
vals = getattr(self, cond.type)(cond, msg)
|
||||||
|
if vals:
|
||||||
|
res.append(vals)
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def message_new(self, msg, custom_values):
|
||||||
|
created_ids = []
|
||||||
|
res = self._prepare_data_for_attachment_metadata(msg)
|
||||||
|
if res:
|
||||||
|
for vals in res:
|
||||||
|
default = self._context.get('default_attachment_metadata_vals')
|
||||||
|
if default:
|
||||||
|
for key in default:
|
||||||
|
if key not in vals:
|
||||||
|
vals[key] = default[key]
|
||||||
|
created_ids.append(self.create(vals))
|
||||||
|
self._cr.commit()
|
||||||
|
return created_ids[0].id
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class IrAttachmentMetadataCondition(models.Model):
|
||||||
|
_name = "ir.attachment.metadata.condition"
|
||||||
|
_description = "Attachment Metadata Conditions"
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_attachment_metadata_condition_type(self):
|
||||||
|
return self.get_attachment_metadata_condition_type()
|
||||||
|
|
||||||
|
def get_attachment_metadata_condition_type(self):
|
||||||
|
return [('normal', 'Normal')]
|
||||||
|
|
||||||
|
from_email = fields.Char(string='Email', size=64)
|
||||||
|
mail_subject = fields.Char(size=64)
|
||||||
|
type = fields.Selection(
|
||||||
|
selection='_get_attachment_metadata_condition_type',
|
||||||
|
help="Create your own type if the normal type \
|
||||||
|
do not correspond to your need",
|
||||||
|
required=True,
|
||||||
|
default='normal'
|
||||||
|
)
|
||||||
|
file_extension = fields.Char(size=64,
|
||||||
|
help="File extension or file name",
|
||||||
|
required=True)
|
||||||
|
server_id = fields.Many2one('fetchmail.server', string='Server Mail')
|
65
file_email/attachment_metadata_view.xml
Normal file
65
file_email/attachment_metadata_view.xml
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- SEARCH -->
|
||||||
|
<record id="attachment_metadata_view_search" model="ir.ui.view">
|
||||||
|
<field name="name">ir.attachment.metadata.search</field>
|
||||||
|
<field name="model">ir.attachment.metadata</field>
|
||||||
|
<field name="type">search</field>
|
||||||
|
<field name="inherit_id" ref="external_file_location.view_external_attachment_search"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="type" position="after">
|
||||||
|
<field name="fetchmail_server_id"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Views and menus for the new object prepare.ir.attachment.metadata -->
|
||||||
|
<record id="view_attachment_metadata_condition_tree" model="ir.ui.view">
|
||||||
|
<field name="name">view_attachment_metadata_condition_tree</field>
|
||||||
|
<field name="model">ir.attachment.metadata.condition</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Configuration for ir.attachment.metadata">
|
||||||
|
<field name="from_email"/>
|
||||||
|
<field name="mail_subject"/>
|
||||||
|
<field name="type"/>
|
||||||
|
<field name="file_extension"/>
|
||||||
|
<field name="server_id"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_attachment_metadata_condition_form" model="ir.ui.view">
|
||||||
|
<field name="name">view_attachment_metadata_condition_form</field>
|
||||||
|
<field name="model">ir.attachment.metadata.condition</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Configuration for ir.attachment.metadata">
|
||||||
|
<sheet>
|
||||||
|
<group col="4">
|
||||||
|
<field name="from_email" attrs="{'required': [('type', '=', 'normal')]}"/>
|
||||||
|
<field name="mail_subject" attrs="{'required': [('type', '=', 'normal')]}"/>
|
||||||
|
<field name="type"/>
|
||||||
|
<field name="file_extension"/>
|
||||||
|
<field name="server_id"/>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_attachment_metadata_configuration" model="ir.actions.act_window">
|
||||||
|
<field name="name">Configuration</field>
|
||||||
|
<field name="res_model">ir.attachment.metadata.condition</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_attachment_metadata_configuration"
|
||||||
|
action="action_attachment_metadata_configuration"
|
||||||
|
parent="external_file_location.menu_file_exchange"
|
||||||
|
sequence="50" />
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
||||||
|
|
69
file_email/fetchmail.py
Normal file
69
file_email/fetchmail.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# file_email for OpenERP
|
||||||
|
# Copyright (C) 2013 Akretion (http://www.akretion.com).
|
||||||
|
# @author Sébastien BEAU <sebastien.beau@akretion.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from openerp import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class fetchmail_server(models.Model):
|
||||||
|
_inherit = 'fetchmail.server'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def get_file_type(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_file_type(self):
|
||||||
|
return self.get_file_type()
|
||||||
|
|
||||||
|
def company_default_get(self):
|
||||||
|
company_id = (self.env['res.company'].
|
||||||
|
_company_default_get('fetchmail.server'))
|
||||||
|
return self.env['res.company'].browse(company_id)
|
||||||
|
|
||||||
|
file_type = fields.Selection(
|
||||||
|
selection='_get_file_type',
|
||||||
|
help='The file type will show some special option')
|
||||||
|
company_id = fields.Many2one(
|
||||||
|
'res.company',
|
||||||
|
string='Company',
|
||||||
|
required=True,
|
||||||
|
default=company_default_get
|
||||||
|
) # Why this field do not exist by default?
|
||||||
|
attachment_metadata_condition_ids = fields.One2many(
|
||||||
|
'ir.attachment.metadata.condition', 'server_id', string='Attachment')
|
||||||
|
|
||||||
|
@api.one
|
||||||
|
def get_context_for_server(self):
|
||||||
|
if self._context is None:
|
||||||
|
ctx = {}
|
||||||
|
else:
|
||||||
|
ctx = self._context.copy()
|
||||||
|
ctx['default_attachment_metadata_vals'] = {}
|
||||||
|
ctx['default_company_id'] = self.company_id.id
|
||||||
|
ctx['default_fetchmail_server_id'] = self.id
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def fetch_mail(self):
|
||||||
|
for server in self:
|
||||||
|
super(fetchmail_server, server).fetch_mail()
|
||||||
|
return True
|
22
file_email/fetchmail_view.xml
Normal file
22
file_email/fetchmail_view.xml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_email_server_form">
|
||||||
|
<field name="model">fetchmail.server</field>
|
||||||
|
<field name="inherit_id" ref="fetchmail.view_email_server_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="date" position="after">
|
||||||
|
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
|
||||||
|
</field>
|
||||||
|
<field name="object_id" position="after">
|
||||||
|
<field name="file_type"/> <!-- TODO attrs="{'invisible': [('object_id','!=', 'file.document')]}"/>-->
|
||||||
|
</field>
|
||||||
|
<field name="action_id" position="after">
|
||||||
|
<field name="attachment_metadata_condition_ids" nolabel="1" colspan="4"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
Loading…
Reference in New Issue
Block a user