[12.0] Add base_fileurl_field

This commit is contained in:
Akim Juillerat
2019-03-13 19:18:07 +01:00
parent a7207cc3e7
commit d83aca68e6
13 changed files with 297 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
from . import fields
+21
View File
@@ -0,0 +1,21 @@
# Copyright 2012-2019 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
{
"name": "Base FileURL Field",
"summary": "Implements of FileURL type fields",
"category": "Technical Settings",
"description": """
This module adds a new field type FileURL to odoo.
FileURL is an extension of field type Binary, with the aim to store its
value on any kind external storage.
It's been built with the focus on Amazon S3 but could be used with
other storage solution as long as it extends the functionaly of
base_attachment_object_storage.
""",
"version": "12.0.1.0.0",
"depends": [
"base_attachment_object_storage",
],
"auto_install": False,
"installable": True,
}
+102
View File
@@ -0,0 +1,102 @@
# Copyright 2012-2019 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
import unicodedata
from odoo import fields
fields.Field.__doc__ += """
.. _field-fileurl:
.. rubric:: FileURL fields
FileURL fields is intended to store Binary data on an external storage
with the possibility to be accessed outside of odoo.
:param storage_location: Required external storage that must be
activated on the system (cf base_attachment_storage)
:param storage_path: Path to be used as a prefix to the filename in the
storage solution (must be used with filename)
:param filename: Field on the same model which stores the filename.
Will be used to set fname on ir.attachment and, if storage_path is
defined, will be passed to force the storage key.
"""
class FileURL(fields.Binary):
_slots = {
'attachment': True, # Override default with True
'storage_location': '', # External storage activated on the system (cf base_attachment_storage) # noqa
'storage_path': '', # Path to be used as storage key (prefix of filename) # noqa
'filename': '', # Field to use to store the filename on ir.attachment
}
def create(self, record_values):
assert self.attachment
if not record_values:
return
# create the attachments that store the values
env = record_values[0][0].env
with env.norecompute():
for record, value in record_values:
if not value:
continue
vals = {
'name': self.name,
'res_model': self.model_name,
'res_field': self.name,
'res_id': record.id,
'type': 'binary',
'datas': value,
}
fname = False
if self.filename:
fname = record[self.filename]
vals['datas_fname'] = fname
if fname and self.storage_path:
storage_key = self._build_storage_key(
record[self.filename]
)
if not fname:
storage_key = False
env['ir.attachment'].sudo().with_context(
binary_field_real_user=env.user,
storage_location=self.storage_location,
force_storage_key=storage_key,
).create(vals)
def write(self, records, value):
for record in records:
storage_key = False
if self.filename:
fname = record[self.filename]
if fname and self.storage_path:
storage_key = self._build_storage_key(
record[self.filename])
super().write(
records.with_context(
storage_location=self.storage_location,
force_storage_key=storage_key,
),
value
)
return True
def _setup_regular_base(self, model):
super()._setup_regular_base(model)
if self.storage_path:
assert self.filename is not None, \
"Field %s defines storage_path without filename" % self
def _build_storage_key(self, filename):
return '/'.join([
self.storage_path.rstrip('/'),
unicodedata.normalize('NFKC', filename)
])
fields.FileURL = FileURL
+3
View File
@@ -0,0 +1,3 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from . import models
+16
View File
@@ -0,0 +1,16 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
{
'name': 'test base fileurl fields',
'version': '12.0.1.0.0',
'category': 'Tests',
'description': """A module to verify fileurl field.""",
'depends': [
'base_fileurl_field'
],
'data': [
"views/res_partner.xml",
],
'installable': True,
'auto_install': False,
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+1
View File
@@ -0,0 +1 @@
This is a simple text file.
@@ -0,0 +1 @@
from . import res_partner
@@ -0,0 +1,44 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class ResPartner(models.Model):
_inherit = 'res.partner'
name = fields.Char()
url_file = fields.FileURL(
storage_location='s3',
filename='url_file_fname',
storage_path='partner'
)
url_file_fname = fields.Char()
url_image = fields.FileURL(
storage_location='s3',
filename='url_image_fname',
storage_path='partner_image',
)
url_image_fname = fields.Char()
@api.constrains('url_file', 'url_file_fname')
def _check_url_file_fname(self):
rec = self.search([('url_file_fname', '=', self.url_file_fname)])
if len(rec) > 1:
raise ValidationError(_(
"This file name is already used on an existing record. "
"Please use another file name or delete the url_file on :\n"
"Model: %s Id: %s" % (self._name, rec.id)
))
@api.constrains('url_image', 'url_image_fname')
def _check_url_image_fname(self):
rec = self.search([('url_image_fname', '=', self.url_image_fname)])
if len(rec) > 1:
raise ValidationError(_(
"This file name is already used on an existing record. "
"Please use another file name or delete the url_image on :\n"
"Model: %s Id: %s" % (self._name, rec.id)
))
@@ -0,0 +1,2 @@
from . import ir_attachment
from . import test_fileurl_fields
@@ -0,0 +1,44 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
import logging
from odoo import _, api, exceptions, models
_logger = logging.getLogger(__name__)
FAKE_S3_BUCKET = {}
class IrAttachment(models.Model):
_inherit = "ir.attachment"
def _get_stores(self):
l = ['s3']
l += super(IrAttachment, self)._get_stores()
return l
@api.model
def _store_file_read(self, fname, bin_size=False):
if fname.startswith('s3://'):
return FAKE_S3_BUCKET.get(fname)
else:
return super(IrAttachment, self)._store_file_read(fname, bin_size)
@api.model
def _store_file_write(self, key, bin_data):
location = self.env.context.get('storage_location') or self._storage()
if location == 's3':
FAKE_S3_BUCKET[key] = bin_data
filename = 's3://fake_bucket/%s' % key
else:
_super = super(IrAttachment, self)
filename = _super._store_file_write(key, bin_data)
return filename
@api.model
def _store_file_delete(self, fname):
if fname.startswith('s3://'):
FAKE_S3_BUCKET.pop(fname)
else:
super(IrAttachment, self)._store_file_delete(fname)
@@ -0,0 +1,39 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
import base64
from odoo.tests import TransactionCase
from odoo.modules.module import get_module_resource
from odoo.exceptions import ValidationError
class TestFileUrlFields(TransactionCase):
def test_fileurl_fields(self):
file_path = get_module_resource('test_base_fileurl_field', 'data',
'sample.txt')
image_path = get_module_resource('test_base_fileurl_field', 'data',
'pattern.png')
partner = self.env.ref('base.main_partner')
with open(file_path, 'rb') as f:
with open(image_path, 'rb') as i:
partner.write({
'url_file': base64.b64encode(f.read()),
'url_file_fname': 'sample.txt',
'url_image': base64.b64encode(i.read()),
'url_image_fname': 'pattern.png',
})
with open(file_path, 'rb') as f:
self.assertEqual(base64.decodebytes(partner.url_file), f.read())
with open(image_path, 'rb') as image:
self.assertEqual(base64.decodebytes(partner.url_image), i.read())
partner2 = self.env.ref('base.partner_admin')
with open(file_path, 'rb') as f:
with self.assertRaises(ValidationError):
partner2.write({
'url_file': base64.b64encode(f.read()),
'url_file_fname': 'sample.txt',
})
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_partner_form_inherit" model="ir.ui.view">
<field name="name">res.partner.form.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="arch" type="xml">
<xpath expr="//notebook" position="inside">
<page name="fileurl_test" string="FileURL Test fields">
<group string="Default widget">
<field name="url_file" filename="url_file_fname" />
<field name="url_file_fname" invisible="1"/>
</group>
<group string="Image widget">
<field name="url_image" widget="image" filename="url_image_fname" />
<field name="url_image_fname" invisible="1"/>
</group>
</page>
</xpath>
</field>
</record>
</odoo>