204 lines
7.1 KiB
Python
204 lines
7.1 KiB
Python
# © 2014 Serv. Tecnol. Avanzados (http://www.serviciosbaeza.com)
|
|
# Pedro M. Baeza <pedro.baeza@serviciosbaeza.com>
|
|
# © 2015 Antiun Ingeniería S.L. - Jairo Llopis
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
|
|
|
import base64
|
|
import logging
|
|
import os
|
|
from urllib.error import ContentTooShortError
|
|
from urllib.request import urlretrieve
|
|
|
|
from odoo import _, api, exceptions, fields, models, tools
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Image(models.Model):
|
|
_name = "base_multi_image.image"
|
|
# TODO: when migrating to 15.0 use image.mixin
|
|
_order = "sequence, owner_model, owner_id, id"
|
|
_description = """ image model for multiple image functionality """
|
|
_sql_constraints = [
|
|
(
|
|
"uniq_name_owner",
|
|
"UNIQUE(owner_id, owner_model, name)",
|
|
_("A document can have only one image with the same name."),
|
|
),
|
|
]
|
|
|
|
# This Integer is really a split Many2one
|
|
owner_id = fields.Integer("Owner", required=True)
|
|
owner_model = fields.Char(required=True)
|
|
owner_ref_id = fields.Reference(
|
|
selection="_selection_owner_ref_id",
|
|
string="Referenced Owner",
|
|
compute="_compute_owner_ref_id",
|
|
store=True,
|
|
)
|
|
storage = fields.Selection(
|
|
[
|
|
("url", "URL"),
|
|
("file", "OS file"),
|
|
("db", "Database"),
|
|
("filestore", "Filestore"),
|
|
],
|
|
required=True,
|
|
default="filestore",
|
|
)
|
|
name = fields.Char("Image title", translate=True)
|
|
filename = fields.Char()
|
|
extension = fields.Char("File extension", readonly=True)
|
|
attachment_id = fields.Many2one(
|
|
"ir.attachment", string="Attachment", domain="[('index_content', '=', 'image')]"
|
|
)
|
|
file_db_store = fields.Binary("Image stored in database")
|
|
path = fields.Char("Image path", help="Image path")
|
|
url = fields.Char("Image remote URL")
|
|
image_main = fields.Image("Full-sized image", compute="_compute_image")
|
|
image_medium = fields.Image(
|
|
"Medium-sized image",
|
|
related="image_main",
|
|
max_width=128,
|
|
max_height=128,
|
|
help="Medium-sized image. It is automatically resized as a "
|
|
"128 x 128 px image, with aspect ratio preserved, only when the "
|
|
"image exceeds one of those sizes. Use this field in form views "
|
|
"or kanban views.",
|
|
)
|
|
image_small = fields.Image(
|
|
"Small-sized image",
|
|
related="image_main",
|
|
max_width=64,
|
|
max_height=64,
|
|
help="Small-sized image. It is automatically resized as a 64 x 64 px "
|
|
"image, with aspect ratio preserved. Use this field anywhere a "
|
|
"small image is required.",
|
|
)
|
|
comments = fields.Text(translate=True)
|
|
sequence = fields.Integer(default=10)
|
|
show_technical = fields.Boolean(compute="_compute_show_technical")
|
|
|
|
@api.model
|
|
@tools.ormcache("self")
|
|
def _selection_owner_ref_id(self):
|
|
"""Allow any model; after all, this field is readonly."""
|
|
return [(r.model, r.name) for r in self.env["ir.model"].search([])]
|
|
|
|
@api.depends("owner_model", "owner_id")
|
|
def _compute_owner_ref_id(self):
|
|
"""Get a reference field based on the split model and id fields."""
|
|
for s in self:
|
|
if s.owner_model:
|
|
s.owner_ref_id = "{0.owner_model},{0.owner_id}".format(s)
|
|
|
|
@api.depends("storage", "path", "file_db_store", "url")
|
|
def _compute_image(self):
|
|
"""Get image data from the right storage type."""
|
|
for s in self:
|
|
s.image_main = getattr(s, "_get_image_from_%s" % s.storage)()
|
|
|
|
@api.depends("owner_id", "owner_model")
|
|
def _compute_show_technical(self):
|
|
"""Know if you need to show the technical fields."""
|
|
self.show_technical = all(
|
|
"default_owner_%s" % f not in self.env.context for f in ("id", "model")
|
|
)
|
|
|
|
def _get_image_from_filestore(self):
|
|
return self.attachment_id.datas
|
|
|
|
def _get_image_from_db(self):
|
|
return self.file_db_store
|
|
|
|
def _get_image_from_file(self):
|
|
if self.path and os.path.exists(self.path):
|
|
try:
|
|
with open(self.path, "rb") as f:
|
|
return base64.b64encode(f.read())
|
|
except Exception as e:
|
|
_logger.error(
|
|
"Can not open the image %s, error : %s", self.path, e, exc_info=True
|
|
)
|
|
else:
|
|
_logger.error("The image %s doesn't exist ", self.path)
|
|
|
|
return False
|
|
|
|
def _get_image_from_url(self):
|
|
return self._get_image_from_url_cached(self.url)
|
|
|
|
@api.model
|
|
@tools.ormcache("url")
|
|
def _get_image_from_url_cached(self, url):
|
|
"""Allow to download an image and cache it by its URL."""
|
|
if url:
|
|
try:
|
|
(filename, header) = urlretrieve(url)
|
|
with open(filename, "rb") as f:
|
|
return base64.b64encode(f.read())
|
|
except ContentTooShortError:
|
|
_logger.error("URL %s cannot be fetched", url, exc_info=True)
|
|
|
|
return False
|
|
|
|
@api.model
|
|
def _make_name_pretty(self, name):
|
|
return name.replace("_", " ").capitalize()
|
|
|
|
@api.onchange("url")
|
|
def _onchange_url(self):
|
|
if self.url:
|
|
filename = self.url.split("/")[-1]
|
|
self.name, self.extension = os.path.splitext(filename)
|
|
self.name = self._make_name_pretty(self.name)
|
|
|
|
@api.onchange("path")
|
|
def _onchange_path(self):
|
|
if self.path:
|
|
self.name, self.extension = os.path.splitext(os.path.basename(self.path))
|
|
self.name = self._make_name_pretty(self.name)
|
|
|
|
@api.onchange("filename")
|
|
def _onchange_filename(self):
|
|
if self.filename:
|
|
self.name, self.extension = os.path.splitext(self.filename)
|
|
self.name = self._make_name_pretty(self.name)
|
|
|
|
@api.onchange("attachment_id")
|
|
def _onchange_attachmend_id(self):
|
|
if self.attachment_id:
|
|
self.name = self.attachment_id.res_name
|
|
|
|
@api.constrains("storage", "url")
|
|
def _check_url(self):
|
|
for record in self:
|
|
if record.storage == "url" and not record.url:
|
|
raise exceptions.ValidationError(
|
|
_("You must provide an URL for the image.")
|
|
)
|
|
|
|
@api.constrains("storage", "path")
|
|
def _check_path(self):
|
|
for record in self:
|
|
if record.storage == "file" and not record.path:
|
|
raise exceptions.ValidationError(
|
|
_("You must provide a file path for the image.")
|
|
)
|
|
|
|
@api.constrains("storage", "file_db_store")
|
|
def _check_store(self):
|
|
for record in self:
|
|
if record.storage == "db" and not record.file_db_store:
|
|
raise exceptions.ValidationError(
|
|
_("You must provide an attached file for the image.")
|
|
)
|
|
|
|
@api.constrains("storage", "attachment_id")
|
|
def _check_attachment_id(self):
|
|
for record in self:
|
|
if record.storage == "filestore" and not record.attachment_id:
|
|
raise exceptions.ValidationError(
|
|
_("You must provide an attachment for the image.")
|
|
)
|