mirror of
https://github.com/OCA/knowledge.git
synced 2025-07-26 10:28:40 -06:00
Merge remote-tracking branch 'ir_attachment_exchange/master' into 8.0-external_file_sync
This commit is contained in:
commit
3fc73f7fd1
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
|
||||||
|
|
46
external_file_location/__openerp__.py
Normal file
46
external_file_location/__openerp__.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# -*- 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',
|
||||||
|
'description': """
|
||||||
|
File exchange system with multiple protocol (SFTP, FTP, Filestore)
|
||||||
|
""",
|
||||||
|
'depends': [
|
||||||
|
'base',
|
||||||
|
'ir_attachment_metadata',
|
||||||
|
],
|
||||||
|
'data': [
|
||||||
|
'attachment_view.xml',
|
||||||
|
'menu.xml',
|
||||||
|
'location_view.xml',
|
||||||
|
'task_view.xml',
|
||||||
|
'cron.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'application': True,
|
||||||
|
}
|
20
external_file_location/abstract_task.py
Executable file
20
external_file_location/abstract_task.py
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from base64 import b64encode
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractTask(object):
|
||||||
|
|
||||||
|
def create_file(self, filename, data):
|
||||||
|
ir_attachment_id = self.env['ir.attachment'].create(
|
||||||
|
{
|
||||||
|
'name': filename,
|
||||||
|
'datas': b64encode(data),
|
||||||
|
'datas_fname': filename,
|
||||||
|
'task_id': self.task.id,
|
||||||
|
'location_id': self.task.location_id.id,
|
||||||
|
'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) 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 openerp import models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class IrAttachment(models.Model):
|
||||||
|
_inherit = 'ir.attachment'
|
||||||
|
|
||||||
|
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</field>
|
||||||
|
<field name="inherit_id" ref="base.view_attachment_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</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</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</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">kanban,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_view1" model="ir.actions.act_window.view">
|
||||||
|
<field eval="20" name="sequence"/>
|
||||||
|
<field name="view_mode">kanban</field>
|
||||||
|
<field name="view_id" ref="mail.view_document_file_kanban"/>
|
||||||
|
<field name="act_window_id" ref="action_attachment"/>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<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', ...]
|
||||||
|
"""
|
||||||
|
#import pdb; pdb.set_trace()
|
||||||
|
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
|
47
external_file_location/location.py
Normal file
47
external_file_location/location.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# -*- 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
|
||||||
|
from abstract_task import AbstractTask
|
||||||
|
from helper import itersubclasses
|
||||||
|
|
||||||
|
class Location(models.Model):
|
||||||
|
_name = 'external.file.location'
|
||||||
|
_description = 'Description'
|
||||||
|
|
||||||
|
name = fields.Char(string='Name')
|
||||||
|
protocol = fields.Selection(selection='_get_protocol')
|
||||||
|
address = fields.Char(string='Address')
|
||||||
|
port = fields.Integer()
|
||||||
|
login = fields.Char()
|
||||||
|
password = fields.Char()
|
||||||
|
task_ids = fields.One2many('external.file.task', 'location_id')
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
return res
|
||||||
|
|
64
external_file_location/location_view.xml
Normal file
64
external_file_location/location_view.xml
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?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="Location 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"/>
|
||||||
|
<field name="login" colspan="2"/>
|
||||||
|
<field name="password" password="1" colspan="2"/>
|
||||||
|
<separator string="Tasks" colspan="4"/>
|
||||||
|
<field name="task_ids" colspan="4" nolabel="1">
|
||||||
|
<tree>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="method"/>
|
||||||
|
<field name="filename"/>
|
||||||
|
<field name="filepath"/>
|
||||||
|
<button name="run" type="object" string="Run" icon="gtk-execute"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</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>
|
88
external_file_location/task.py
Normal file
88
external_file_location/task.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# -*- 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()
|
||||||
|
method = fields.Selection(selection='_get_method')
|
||||||
|
method_type = fields.Char()
|
||||||
|
filename = fields.Char()
|
||||||
|
filepath = fields.Char()
|
||||||
|
location_id = fields.Many2one('external.file.location', string='Location')
|
||||||
|
attachment_ids = fields.One2many('ir.attachment', 'task_id',
|
||||||
|
string='Attachment')
|
||||||
|
delete_file = fields.Boolean(string='Delete file')
|
||||||
|
move_file = fields.Boolean(string='Move file')
|
||||||
|
move_path = fields.Char(string='Move path')
|
||||||
|
|
||||||
|
def _get_method(self):
|
||||||
|
res = []
|
||||||
|
for cls in itersubclasses(AbstractTask):
|
||||||
|
if cls._synchronize_type:
|
||||||
|
cls_info = (cls._key + '_' + cls._synchronize_type,
|
||||||
|
cls._name + ' ' + cls._synchronize_type)
|
||||||
|
res.append(cls_info)
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.onchange('method')
|
||||||
|
def onchage_method(self):
|
||||||
|
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,
|
||||||
|
'delete_file': self.delete_file,
|
||||||
|
'move_file': self.move_file,
|
||||||
|
}
|
||||||
|
conn = method_class(self.env, config)
|
||||||
|
conn.run()
|
61
external_file_location/task_view.xml
Normal file
61
external_file_location/task_view.xml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?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">
|
||||||
|
<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="Product Name"/>
|
||||||
|
<h1><field name="name" class="oe_inline"/></h1>
|
||||||
|
</div>
|
||||||
|
<field name="method" colspan="2"/>
|
||||||
|
<field name="location_id" colspan="2"/>
|
||||||
|
<field name="filename" colspan="4" attrs="{'invisible':[('method_type','!=','import')]}"/>
|
||||||
|
<field name="filepath" colspan="4" />
|
||||||
|
<field name="move_file" colspan="2" attrs="{'invisible':[('method_type','!=','import')]}"/>
|
||||||
|
<field name="move_path" colspan="2" attrs="{'invisible':['|', ('move_file','=',False), ('method_type','!=','import')]}"/>
|
||||||
|
<field name="delete_file" colspan="2" attrs="{'invisible':[('method_type','!=','import')]}"/>
|
||||||
|
<group attrs="{'invisible':[('method_type','!=','export')]}" colspan="4">
|
||||||
|
<separator string="Attachments" colspan="4" />
|
||||||
|
<field name="attachment_ids" colspan="4" nolabel="1"/>
|
||||||
|
</group>
|
||||||
|
<button name="run" type="object" string="Run" icon="gtk-execute"/>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<record id="action_task" model="ir.actions.act_window">
|
||||||
|
<field name="name">Tasks</field>
|
||||||
|
<field name="type">ir.actions.act_window</field>
|
||||||
|
<field name="res_model">external.file.task</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_id" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_external_file_task"
|
||||||
|
parent="menu_file_exchange"
|
||||||
|
sequence="20"
|
||||||
|
action="action_task"/>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
23
external_file_location/tasks/__init__.py
Normal file
23
external_file_location/tasks/__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 ftp
|
73
external_file_location/tasks/filestore.py
Executable file
73
external_file_location/tasks/filestore.py
Executable file
@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from ..backend import AbstractTask
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from tempfile import TemporaryFile
|
||||||
|
|
||||||
|
|
||||||
|
class FileStore(AbstractTask):
|
||||||
|
_key = "filestore"
|
||||||
|
_name = "Filestore"
|
||||||
|
_synchronize_type = None
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
# super(FilestoreConnection, self).__init__(host, user, pwd, port, allow_dir_creation)
|
||||||
|
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.filename = config.get('filename', '')
|
||||||
|
self.path = config.get('path', '')
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
if self.path:
|
||||||
|
filepath = "{}/{}".format(self.path, self.filename)
|
||||||
|
else:
|
||||||
|
filepath = self.filename
|
||||||
|
return open(filepath, 'r+b')
|
||||||
|
|
||||||
|
def put(self):
|
||||||
|
if self.path:
|
||||||
|
filepath = "{}/{}".format(self.path, self.filename)
|
||||||
|
else:
|
||||||
|
filepath = self.filename
|
||||||
|
output = open(filepath, 'w+b')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def search(self):
|
||||||
|
if self.path:
|
||||||
|
filepath = "{}/{}".format(self.path, self.filename)
|
||||||
|
else:
|
||||||
|
filepath = self.filename
|
||||||
|
connection_list_result = os.listdir(filepath)
|
||||||
|
return [x for x in connection_list_result if filename in x]
|
||||||
|
|
||||||
|
|
||||||
|
class ImportFileStore(FileStore):
|
||||||
|
_synchronize_type = "import"
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
self.connect()
|
||||||
|
file = self.get(self.filename)
|
||||||
|
self.close()
|
||||||
|
return file
|
||||||
|
|
||||||
|
class ExportFileStore(FileStore):
|
||||||
|
_synchronize_type = "export"
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
self.connect()
|
||||||
|
self.put(self.filename)
|
||||||
|
self.close()
|
||||||
|
|
182
external_file_location/tasks/ftp.py
Normal file
182
external_file_location/tasks/ftp.py
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
# -*- 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
|
||||||
|
from base64 import b64decode
|
||||||
|
import ftputil
|
||||||
|
import ftputil.session
|
||||||
|
from ftputil.error import FTPIOError
|
||||||
|
import logging
|
||||||
|
from base64 import b64encode
|
||||||
|
import hashlib
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class FtpTask(AbstractTask):
|
||||||
|
|
||||||
|
_key = 'ftp'
|
||||||
|
_name = 'FTP'
|
||||||
|
_synchronize_type = 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', '.')
|
||||||
|
self.move_path = config.get('move_path', '')
|
||||||
|
self.move_file = config.get('move_file', False)
|
||||||
|
self.delete_file = config.get('delete_file', False)
|
||||||
|
self.attachment_ids = config.get('attachment_ids', False)
|
||||||
|
self.task = config.get('task', False)
|
||||||
|
self.ext_hash = False
|
||||||
|
|
||||||
|
|
||||||
|
class FtpImportTask(FtpTask):
|
||||||
|
"""FTP Configuration options:
|
||||||
|
- host, user, password, port
|
||||||
|
- download_directory: directory on the FTP server where files are
|
||||||
|
downloaded from
|
||||||
|
- move_directory: If present, files will be moved to this directory
|
||||||
|
on the FTP server after download.
|
||||||
|
- delete_files: If true, files will be deleted on the FTP server
|
||||||
|
after download.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_synchronize_type = 'import'
|
||||||
|
|
||||||
|
def _handle_new_source(self, ftp_conn, download_directory, file_name,
|
||||||
|
move_directory):
|
||||||
|
"""open and read given file into create_file method,
|
||||||
|
move file if move_directory is given"""
|
||||||
|
with ftp_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 download_directory + '/' + file_name
|
||||||
|
|
||||||
|
def _move_file(self, ftp_conn, source, target):
|
||||||
|
"""Moves a file on the FTP server"""
|
||||||
|
_logger.info('Moving file %s %s' % (source, target))
|
||||||
|
ftp_conn.rename(source, target)
|
||||||
|
|
||||||
|
def _delete_file(self, ftp_conn, source):
|
||||||
|
"""Deletes a file from the FTP server"""
|
||||||
|
_logger.info('Deleting file %s' % source)
|
||||||
|
ftp_conn.remove(source)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_hash(file_name, ftp_conn):
|
||||||
|
with ftp_conn.open(file_name, 'rb') as f:
|
||||||
|
return hashlib.md5(f.read()).hexdigest()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
port_session_factory = ftputil.session.session_factory(
|
||||||
|
port=self.port)
|
||||||
|
with ftputil.FTPHost(self.host, self.user,
|
||||||
|
self.pwd,
|
||||||
|
session_factory=port_session_factory) as ftp_conn:
|
||||||
|
|
||||||
|
path = self.path or '.'
|
||||||
|
file_list = ftp_conn.listdir(path)
|
||||||
|
downloaded_files = []
|
||||||
|
for ftpfile in file_list:
|
||||||
|
source_name = self._source_name(path, self.file_name)
|
||||||
|
if ftp_conn.path.isfile(source_name) and \
|
||||||
|
ftpfile == self.file_name:
|
||||||
|
self.ext_hash = self._get_hash(source_name, ftp_conn)
|
||||||
|
self._handle_new_source(
|
||||||
|
ftp_conn,
|
||||||
|
path,
|
||||||
|
self.file_name,
|
||||||
|
self.move_path)
|
||||||
|
downloaded_files.append(self.file_name)
|
||||||
|
|
||||||
|
# Move/delete files only after all files have been processed.
|
||||||
|
if self.delete_file:
|
||||||
|
for ftpfile in downloaded_files:
|
||||||
|
self._delete_file(ftp_conn,
|
||||||
|
self._source_name(path,
|
||||||
|
ftpfile))
|
||||||
|
elif self.move_path:
|
||||||
|
if not ftp_conn.path.exists(self.move_path):
|
||||||
|
ftp_conn.mkdir(self.move_path)
|
||||||
|
for ftpfile in downloaded_files:
|
||||||
|
self._move_file(
|
||||||
|
ftp_conn,
|
||||||
|
self._source_name(path, ftpfile),
|
||||||
|
self._source_name(self.move_path, ftpfile))
|
||||||
|
|
||||||
|
|
||||||
|
class FtpExportTask(FtpTask):
|
||||||
|
"""FTP Configuration options:
|
||||||
|
- host, user, password, port
|
||||||
|
- upload_directory: directory on the FTP server where files are
|
||||||
|
uploaded to
|
||||||
|
"""
|
||||||
|
|
||||||
|
_synchronize_type = 'export'
|
||||||
|
|
||||||
|
def _handle_existing_target(self, ftp_conn, target_name, filedata):
|
||||||
|
raise Exception("%s already exists" % target_name)
|
||||||
|
|
||||||
|
def _handle_new_target(self, ftp_conn, target_name, filedata):
|
||||||
|
try:
|
||||||
|
with ftp_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 FTPIOError:
|
||||||
|
self.attachment_id.state = 'failed'
|
||||||
|
self.attachment_id.state_message = (
|
||||||
|
'The directory doesn\'t exist or had insufficient rights')
|
||||||
|
|
||||||
|
def _target_name(self, ftp_conn, upload_directory, filename):
|
||||||
|
return upload_directory + '/' + filename
|
||||||
|
|
||||||
|
def _upload_file(self, host, port, user, pwd, path, filename, filedata):
|
||||||
|
upload_directory = path or '.'
|
||||||
|
port_session_factory = ftputil.session.session_factory(port=port)
|
||||||
|
with ftputil.FTPHost(host, user, pwd,
|
||||||
|
session_factory=port_session_factory) as ftp_conn:
|
||||||
|
target_name = self._target_name(ftp_conn,
|
||||||
|
upload_directory,
|
||||||
|
filename)
|
||||||
|
if ftp_conn.path.isfile(target_name):
|
||||||
|
self._handle_existing_target(ftp_conn, target_name, filedata)
|
||||||
|
else:
|
||||||
|
self._handle_new_target(ftp_conn, target_name, filedata)
|
||||||
|
|
||||||
|
def run(self, async=True):
|
||||||
|
for attachment in self.attachment_ids:
|
||||||
|
if attachment.state in ('pending', 'failed'):
|
||||||
|
self.attachment_id = attachment
|
||||||
|
self._upload_file(self.host, self.port, self.user, self.pwd,
|
||||||
|
self.path,
|
||||||
|
attachment.datas_fname,
|
||||||
|
b64decode(attachment.datas))
|
58
external_file_location/tasks/ftp_backup.py
Executable file
58
external_file_location/tasks/ftp_backup.py
Executable file
@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from tempfile import TemporaryFile
|
||||||
|
from ftplib import FTP
|
||||||
|
|
||||||
|
class FTPConnection(object):
|
||||||
|
|
||||||
|
def __init__(self, host, user, pwd, port=None, allow_dir_creation=False):
|
||||||
|
super(FTPConnection, self).__init__(host, user, pwd, port, allow_dir_creation)
|
||||||
|
if not port:
|
||||||
|
self.port = 21
|
||||||
|
self.protocol = "FTP"
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
self.connection = FTP(self.location, self.port)
|
||||||
|
self.connection.login(self.user, self.pwd)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.connection.close()
|
||||||
|
|
||||||
|
def get(self, filename, path=None):
|
||||||
|
if path:
|
||||||
|
filepath = "{}/{}".format(path, filename)
|
||||||
|
else:
|
||||||
|
filepath = filename
|
||||||
|
outfile = TemporaryFile('w+b')
|
||||||
|
self.connection.retrbinary('RETR ' + filepath, outfile.write)
|
||||||
|
return outfile
|
||||||
|
|
||||||
|
def put(self, fileobject, filename, path=None):
|
||||||
|
if path:
|
||||||
|
filepath = "{}/{}".format(path, filename)
|
||||||
|
else:
|
||||||
|
filepath = filename
|
||||||
|
self.connection.storbinary('STOR ' + filepath, fileobject)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def search(self, filename, path=None):
|
||||||
|
if path:
|
||||||
|
filepath = "{}/{}".format(path, filename)
|
||||||
|
else:
|
||||||
|
filepath = filename
|
||||||
|
connection_list_result = self.connection.nlst()
|
||||||
|
return [x for x in connection_list_result if filename in x]
|
||||||
|
|
||||||
|
|
||||||
|
def move(self, filename, oldpath, newpath):
|
||||||
|
self.connection.rename(
|
||||||
|
os.path.join(oldpath, filename),
|
||||||
|
os.path.join(newpath, filename)
|
||||||
|
)
|
||||||
|
|
||||||
|
def rename(self, oldfilename, newfilename, path=None):
|
||||||
|
return NotImplemented
|
59
external_file_location/tasks/sftp.py
Executable file
59
external_file_location/tasks/sftp.py
Executable file
@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import paramiko
|
||||||
|
import os
|
||||||
|
from tempfile import TemporaryFile
|
||||||
|
|
||||||
|
|
||||||
|
class SFTPConnection(AbstractConnection):
|
||||||
|
|
||||||
|
def __init__(self, host, user, pwd, port=None, allow_dir_creation=False):
|
||||||
|
super(SFTPConnection, self).__init__(host, user, pwd, port, allow_dir_creation)
|
||||||
|
if not port:
|
||||||
|
self.port = 22
|
||||||
|
self.protocol = "STFP"
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
self.ssh = paramiko.SSHClient()
|
||||||
|
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
self.ssh.connect(self.host, self.port, self.user, self.pwd, compress=True)
|
||||||
|
self.connection = self.ssh.open_sftp()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.connection.close()
|
||||||
|
|
||||||
|
def get(self, filename, path=None):
|
||||||
|
if path:
|
||||||
|
remotefile = "{}/{}".format(path, filename)
|
||||||
|
else:
|
||||||
|
remotefile = filename
|
||||||
|
localfile = filename
|
||||||
|
newfile = open(filename, 'w')
|
||||||
|
self.connection.getfo(remotefile, newfile)
|
||||||
|
return newfile
|
||||||
|
|
||||||
|
def put(self, fileobject, filename, path=None):
|
||||||
|
if path:
|
||||||
|
remotefile = "{}/{}".format(path, filename)
|
||||||
|
else:
|
||||||
|
remotefile = filename
|
||||||
|
if self.allow_dir_creation:
|
||||||
|
self.connection.mkdirs(path)
|
||||||
|
oldfile = open(fileobj, 'r')
|
||||||
|
self.connection.putfo(oldfile, remotefile)
|
||||||
|
|
||||||
|
def search(self, filename, path=None):
|
||||||
|
if path:
|
||||||
|
self.connection.chdir(path)
|
||||||
|
file_list = self.connection.listdir()
|
||||||
|
return [x for x in file_list if filename in x]
|
||||||
|
|
||||||
|
def move(self, filename, oldpath, newpath):
|
||||||
|
self.connection.rename(os.path.join(oldpath, filename), os.path.join(newpath, filename))
|
||||||
|
|
||||||
|
def rename(self, oldfilename, newfilename, path=None):
|
||||||
|
if not path:
|
||||||
|
path = ''
|
||||||
|
self.connection.rename(os.path.join(path, oldfilename), os.path.join(path, newfilename))
|
23
ir_attachment_metadata/__init__.py
Normal file
23
ir_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
|
45
ir_attachment_metadata/__openerp__.py
Normal file
45
ir_attachment_metadata/__openerp__.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# -*- 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': 'ir_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',
|
||||||
|
],
|
||||||
|
'data': [
|
||||||
|
'attachment_view.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'application': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
42
ir_attachment_metadata/attachment.py
Normal file
42
ir_attachment_metadata/attachment.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# -*- 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 openerp.exceptions import Warning
|
||||||
|
import hashlib
|
||||||
|
from base64 import b64decode
|
||||||
|
|
||||||
|
|
||||||
|
class IrAttachment(models.Model):
|
||||||
|
_inherit = 'ir.attachment'
|
||||||
|
|
||||||
|
internal_hash = fields.Char(store=True, compute='_compute_hash')
|
||||||
|
external_hash = fields.Char()
|
||||||
|
|
||||||
|
@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."))
|
18
ir_attachment_metadata/attachment_view.xml
Normal file
18
ir_attachment_metadata/attachment_view.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<record id="view_attachment_improved_form" model="ir.ui.view">
|
||||||
|
<field name="model">ir.attachment</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>
|
Loading…
Reference in New Issue
Block a user