Browse Source

[10.0][ADD] base_directory_file_download

pull/1590/head
Andrea 8 years ago
committed by Holger Brunn
parent
commit
fab898279b
  1. 78
      base_directory_file_download/README.rst
  2. 4
      base_directory_file_download/__init__.py
  3. 22
      base_directory_file_download/__manifest__.py
  4. 5
      base_directory_file_download/models/__init__.py
  5. 96
      base_directory_file_download/models/ir_filesystem_directory.py
  6. 57
      base_directory_file_download/models/ir_filesystem_file.py
  7. 6
      base_directory_file_download/security/groups.xml
  8. 2
      base_directory_file_download/security/ir.model.access.csv
  9. 4
      base_directory_file_download/tests/__init__.py
  10. 74
      base_directory_file_download/tests/test_directory_files_download.py
  11. 75
      base_directory_file_download/views/ir_filesystem_directory.xml

78
base_directory_file_download/README.rst

@ -0,0 +1,78 @@
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3
========================
Directory Files Download
========================
View and download the files contained in a directory on the server.
This functionality can have impacts on the security of your system,
since it allows to download the content of a directory.
Be careful when choosing the directory!
Notice that, for security reasons, files like symbolic links
and up-level references are ignored.
Configuration
=============
To configure this module, you need to:
#. Set the group "Download files of directory" for the users who need this functionality.
Usage
=====
To use this module, you need to:
#. Go to Settings -> Downloads -> Directory Content
#. Create a record specifying Name and Directory of the server
#. Save; a list of files contained in the selected directory is displayed
#. Download the file you need
#. In case the content of the directory is modified, refresh the list by clicking the button on the top-right of the form
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/10.0
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/server-tools/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smash it by providing detailed and welcomed feedback.
Credits
=======
Images
------
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
Contributors
------------
* Andrea Stirpe <a.stirpe@onestein.nl>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit https://odoo-community.org.

4
base_directory_file_download/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import models

22
base_directory_file_download/__manifest__.py

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Copyright 2017-2018 Onestein (<http://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
'name': 'Directory Files Download',
'summary': 'Download all files of a directory on server',
'author': 'Onestein, Odoo Community Association (OCA)',
'website': 'http://www.onestein.eu',
'category': 'Tools',
'version': '10.0.1.0.0',
'license': 'AGPL-3',
'depends': [
'base_setup',
],
'data': [
'security/groups.xml',
'security/ir.model.access.csv',
'views/ir_filesystem_directory.xml',
],
'installable': True,
}

5
base_directory_file_download/models/__init__.py

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import ir_filesystem_directory
from . import ir_filesystem_file

96
base_directory_file_download/models/ir_filesystem_directory.py

@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
# Copyright 2017-2018 Onestein (<http://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging
from os import listdir
from os.path import isfile, join, exists, normpath, realpath
from odoo import api, fields, models, _
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class IrFilesystemDirectory(models.Model):
_name = 'ir.filesystem.directory'
_description = 'Filesystem Directory'
name = fields.Char(required=True, copy=False)
directory = fields.Char()
file_ids = fields.One2many(
'ir.filesystem.file',
compute='_compute_file_ids',
string='Files'
)
file_count = fields.Integer(
compute='_compute_file_count',
string="# Files"
)
@api.multi
def get_dir(self):
self.ensure_one()
directory = self.directory or ''
# adds slash character at the end if missing
return join(directory, '')
@api.multi
def _compute_file_ids(self):
File = self.env['ir.filesystem.file']
for directory in self:
directory.file_ids = None
if directory.get_dir():
for file_name in directory._get_directory_files():
directory.file_ids += File.create({
'name': file_name,
'filename': file_name,
'stored_filename': file_name,
'directory_id': directory.id,
})
@api.onchange('directory')
def onchange_directory(self):
if self.directory and not exists(self.directory):
raise UserError(_('Directory does not exist'))
@api.multi
def _compute_file_count(self):
for directory in self:
directory.file_count = len(directory.file_ids)
@api.multi
def _get_directory_files(self):
def get_files(directory, files):
for file_name in listdir(directory):
full_path = join(directory, file_name)
# Symbolic links and up-level references are not considered
norm_path = normpath(realpath(full_path))
if norm_path in full_path:
if isfile(full_path) and file_name[0] != '.':
files.append(file_name)
self.ensure_one()
files = []
if self.get_dir() and exists(self.get_dir()):
try:
get_files(self.get_dir(), files)
except (IOError, OSError):
_logger.info(
"_get_directory_files reading %s",
self.get_dir(),
exc_info=True
)
return files
@api.multi
def reload(self):
self.onchange_directory()
@api.multi
def copy(self, default=None):
self.ensure_one()
default = dict(default or {}, name=_("%s (copy)") % self.name)
return super(IrFilesystemDirectory, self).copy(default=default)

57
base_directory_file_download/models/ir_filesystem_file.py

@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
# Copyright 2017-2018 Onestein (<http://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging
import os
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import human_size
_logger = logging.getLogger(__name__)
class IrFilesystemDirectoryLine(models.TransientModel):
_name = 'ir.filesystem.file'
name = fields.Char(required=True)
filename = fields.Char()
file_content = fields.Binary(compute='_compute_file')
stored_filename = fields.Char()
directory_id = fields.Many2one(
'ir.filesystem.directory',
string='Directory'
)
@api.multi
def _file_read(self, fname, bin_size=False):
def file_not_found(fname):
raise UserError(_(
'''Error while reading file %s.
Maybe it was removed or permission is changed.
Please refresh the list.''' % fname))
self.ensure_one()
r = ''
directory = self.directory_id.get_dir()
full_path = directory + fname
if not (directory and os.path.isfile(full_path)):
file_not_found(fname)
try:
if bin_size:
r = human_size(os.path.getsize(full_path))
else:
r = open(full_path, 'rb').read().encode('base64')
except (IOError, OSError):
_logger.info("_read_file reading %s", fname, exc_info=True)
return r
@api.depends('stored_filename')
def _compute_file(self):
bin_size = self._context.get('bin_size')
for line in self:
if line.stored_filename:
content = line._file_read(line.stored_filename, bin_size)
line.file_content = content

6
base_directory_file_download/security/groups.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="res.groups" id="group_filesystem_directory">
<field name="name">Download files of directory</field>
</record>
</odoo>

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

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_ir_filesystem_directory,ir_filesystem_directory,model_ir_filesystem_directory,group_filesystem_directory,1,1,1,1

4
base_directory_file_download/tests/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import test_directory_files_download

74
base_directory_file_download/tests/test_directory_files_download.py

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
# Copyright 2017-2018 Onestein (<http://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import os
from tempfile import gettempdir
from odoo.tests import common
from odoo.exceptions import UserError
class TestBaseDirectoryFilesDownload(common.TransactionCase):
def test_01_create(self):
test_dir = self.env['ir.filesystem.directory'].create({
'name': 'Test Directory 1',
'directory': gettempdir()
})
# test method get_dir()
full_dir = test_dir.get_dir()
self.assertEqual(full_dir[-1], '/')
# test computed field file_ids
self.assertGreaterEqual(len(test_dir.file_ids), 0)
# test count list of directory
self.assertEqual(len(test_dir.file_ids), test_dir.file_count)
# test reload list of directory
test_dir.reload()
self.assertEqual(len(test_dir.file_ids), test_dir.file_count)
# test content of files
for file in test_dir.file_ids:
filename = file.stored_filename
directory = test_dir.get_dir()
with open(os.path.join(directory, filename), 'rb') as f:
content = f.read().encode('base64')
self.assertEqual(file.file_content, content)
# test onchange directory (to not existing)
test_dir.directory = '/txxx'
with self.assertRaises(UserError):
test_dir.onchange_directory()
self.assertEqual(len(test_dir.file_ids), 0)
with self.assertRaises(UserError):
test_dir.reload()
self.assertEqual(len(test_dir.file_ids), 0)
def test_02_copy(self):
test_dir = self.env['ir.filesystem.directory'].create({
'name': 'Test Orig',
'directory': gettempdir()
})
# test copy
dir_copy = test_dir.copy()
self.assertEqual(dir_copy.name, 'Test Orig (copy)')
self.assertEqual(len(dir_copy.file_ids), test_dir.file_count)
self.assertEqual(dir_copy.file_count, test_dir.file_count)
def test_03_not_existing_directory(self):
test_dir = self.env['ir.filesystem.directory'].create({
'name': 'Test Not Existing Directory',
'directory': '/tpd'
})
self.assertEqual(len(test_dir.file_ids), 0)
self.assertEqual(len(test_dir.file_ids), test_dir.file_count)
# test onchange directory (to existing)
test_dir.directory = gettempdir()
self.assertGreaterEqual(len(test_dir.file_ids), 0)
self.assertEqual(len(test_dir.file_ids), test_dir.file_count)

75
base_directory_file_download/views/ir_filesystem_directory.xml

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="ir_filesystem_directory_form" model="ir.ui.view">
<field name="model">ir.filesystem.directory</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="reload" type="object"
string="Files:"
class="oe_stat_button" icon="fa-repeat">
<field name="file_count" />
</button>
</div>
<h1>
<field name="name"/>
</h1>
<group>
<field name="directory" required="1"/>
</group>
<notebook name="notebook">
<page name="files" string="Files">
<field name="file_ids" readonly="1">
<form>
<group>
<group>
<field name="name"/>
</group>
<group>
<field name="file_content" filename="filename"/>
<field name="filename" invisible="1" class="oe_inline oe_right"/>
</group>
</group>
</form>
<tree>
<field name="name"/>
<field name="file_content" filename="filename"/>
<field name="filename" invisible="1"/>
</tree>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="ir_filesystem_directory_tree" model="ir.ui.view">
<field name="model">ir.filesystem.directory</field>
<field name="arch" type="xml">
<tree string="Directory">
<field name="name" />
<field name="directory" />
</tree>
</field>
</record>
<record id="ir_filesystem_directory_action" model="ir.actions.act_window">
<field name="name">Directory Content</field>
<field name="res_model">ir.filesystem.directory</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_ir_filesystem"
name="Downloads"
parent="base.menu_administration"/>
<menuitem id="menu_ir_filesystem_directory"
action="ir_filesystem_directory_action"
groups="group_filesystem_directory"
parent="menu_ir_filesystem" />
</odoo>
Loading…
Cancel
Save