@@ -123,38 +123,31 @@ class ProductProduct(models.Model):
|
||||
# all image fields are base64 encoded and PIL-supported
|
||||
|
||||
# all image_raw fields are technical and should not be displayed to the user
|
||||
image_raw_original = fields.Binary("Raw Original Image")
|
||||
image_raw_original = fields.Image("Raw Original Image")
|
||||
|
||||
# resized fields stored (as attachment) for performance
|
||||
image_raw_big = fields.Binary("Raw Big-sized Image", compute='_compute_images', store=True)
|
||||
image_raw_large = fields.Binary("Raw Large-sized Image", compute='_compute_images', store=True)
|
||||
image_raw_medium = fields.Binary("Raw Medium-sized Image", compute='_compute_images', store=True)
|
||||
image_raw_small = fields.Binary("Raw Small-sized Image", compute='_compute_images', store=True)
|
||||
image_raw_big = fields.Image("Raw Big-sized Image", related="image_raw_original", max_width=1024, max_height=1024, store=True)
|
||||
image_raw_large = fields.Image("Raw Large-sized Image", related="image_raw_original", max_width=256, max_height=256, store=True)
|
||||
image_raw_medium = fields.Image("Raw Medium-sized Image", related="image_raw_original", max_width=128, max_height=128, store=True)
|
||||
image_raw_small = fields.Image("Raw Small-sized Image", related="image_raw_original", max_width=64, max_height=64, store=True)
|
||||
|
||||
can_image_raw_be_zoomed = fields.Boolean("Can image raw be zoomed", compute='_compute_images', store=True)
|
||||
|
||||
# Computed fields that are used to create a fallback to the template if
|
||||
# necessary, it's recommended to display those fields to the user.
|
||||
image_original = fields.Binary("Original Image", compute='_compute_image_original', inverse='_set_image_original', help="Image in its original size, as it was uploaded.")
|
||||
image_big = fields.Binary("Big-sized Image", compute='_compute_image_big', help="1024px * 1024px")
|
||||
image_large = fields.Binary("Large-sized Image", compute='_compute_image_large', help="256px * 256px")
|
||||
image_medium = fields.Binary("Medium-sized Image", compute='_compute_image_medium', help="128px * 128px")
|
||||
image_small = fields.Binary("Small-sized Image", compute='_compute_image_small', help="64px * 64px")
|
||||
image_original = fields.Image("Original Image", compute='_compute_image_original', inverse='_set_image_original', help="Image in its original size, as it was uploaded.")
|
||||
image_big = fields.Image("Big-sized Image", compute='_compute_image_big', help="1024px * 1024px")
|
||||
image_large = fields.Image("Large-sized Image", compute='_compute_image_large', help="256px * 256px")
|
||||
image_medium = fields.Image("Medium-sized Image", compute='_compute_image_medium', help="128px * 128px")
|
||||
image_small = fields.Image("Small-sized Image", compute='_compute_image_small', help="64px * 64px")
|
||||
can_image_be_zoomed = fields.Boolean("Can image be zoomed", compute='_compute_can_image_be_zoomed')
|
||||
|
||||
image = fields.Binary("Image", compute='_compute_image', inverse='_set_image')
|
||||
image = fields.Image("Image", compute='_compute_image', inverse='_set_image')
|
||||
|
||||
@api.depends('image_raw_original')
|
||||
def _compute_images(self):
|
||||
for record in self:
|
||||
image = record.image_raw_original
|
||||
# for performance: avoid calling unnecessary methods when falsy
|
||||
images = image and tools.image_get_resized_images(image, big_name=False)
|
||||
record.image_raw_big = image and tools.image_get_resized_images(image,
|
||||
large_name=False, medium_name=False, small_name=False)['image']
|
||||
record.image_raw_large = image and images['image_large']
|
||||
record.image_raw_medium = image and images['image_medium']
|
||||
record.image_raw_small = image and images['image_small']
|
||||
record.can_image_raw_be_zoomed = image and tools.is_image_size_above(image)
|
||||
|
||||
def _compute_image_original(self):
|
||||
|
||||
@@ -10,29 +10,22 @@ class ImageMixin(models.AbstractModel):
|
||||
|
||||
# all image fields are base64 encoded and PIL-supported
|
||||
|
||||
image_original = fields.Binary("Original Image", help="Image in its original size, as it was uploaded.")
|
||||
image_original = fields.Image("Original Image", help="Image in its original size, as it was uploaded.")
|
||||
|
||||
# resized fields stored (as attachment) for performance
|
||||
image_big = fields.Binary("Big-sized Image", compute='_compute_images', store=True, help="1024px * 1024px")
|
||||
image_large = fields.Binary("Large-sized Image", compute='_compute_images', store=True, help="256px * 256px")
|
||||
image_medium = fields.Binary("Medium-sized Image", compute='_compute_images', store=True, help="128px * 128px")
|
||||
image_small = fields.Binary("Small-sized Image", compute='_compute_images', store=True, help="64px * 64px")
|
||||
image_big = fields.Image("Big-sized Image", related="image_original", max_width=1024, max_height=1024, store=True, help="1024px * 1024px")
|
||||
image_large = fields.Image("Large-sized Image", related="image_original", max_width=256, max_height=256, store=True, help="256px * 256px")
|
||||
image_medium = fields.Image("Medium-sized Image", related="image_original", max_width=128, max_height=128, store=True, help="128px * 128px")
|
||||
image_small = fields.Image("Small-sized Image", related="image_original", max_width=64, max_height=64, store=True, help="64px * 64px")
|
||||
|
||||
can_image_be_zoomed = fields.Boolean("Can image raw be zoomed", compute='_compute_images', store=True)
|
||||
|
||||
image = fields.Binary("Image", compute='_compute_image', inverse='_set_image')
|
||||
image = fields.Image("Image", compute='_compute_image', inverse='_set_image')
|
||||
|
||||
@api.depends('image_original')
|
||||
def _compute_images(self):
|
||||
for record in self:
|
||||
image = record.image_original
|
||||
# for performance: avoid calling unnecessary methods when falsy
|
||||
images = image and tools.image_get_resized_images(image, big_name=False)
|
||||
record.image_big = image and tools.image_get_resized_images(image,
|
||||
large_name=False, medium_name=False, small_name=False)['image']
|
||||
record.image_large = image and images['image_large']
|
||||
record.image_medium = image and images['image_medium']
|
||||
record.image_small = image and images['image_small']
|
||||
record.can_image_be_zoomed = image and tools.is_image_size_above(image)
|
||||
|
||||
@api.depends('image_big')
|
||||
|
||||
@@ -32,3 +32,4 @@ access_test_new_api_field_with_caps,access_test_new_api_field_with_caps,model_te
|
||||
access_test_new_api_req_m2o,access_test_new_api_req_m2o,model_test_new_api_req_m2o,,1,1,1,1
|
||||
access_test_new_api_attachment,access_test_new_api_attachment,model_test_new_api_attachment,,1,1,1,1
|
||||
access_test_new_api_attachment_host,access_test_new_api_attachment_host,model_test_new_api_attachment_host,,1,1,1,1
|
||||
access_test_new_api_model_image,access_test_new_api_model_image,model_test_new_api_model_image,,1,1,1,1
|
||||
|
||||
|
@@ -480,6 +480,18 @@ class ComputeCascade(models.Model):
|
||||
record.baz = "<%s>" % (record.bar or "")
|
||||
|
||||
|
||||
class ModelImage(models.Model):
|
||||
_name = 'test_new_api.model_image'
|
||||
_description = 'Test Image field'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
|
||||
image = fields.Image()
|
||||
image_512 = fields.Image("Image 512", related='image', max_width=512, max_height=512, store=True, readonly=False)
|
||||
image_256 = fields.Image("Image 256", related='image', max_width=256, max_height=256, store=False, readonly=False)
|
||||
image_128 = fields.Image("Image 128", max_width=128, max_height=128)
|
||||
|
||||
|
||||
class BinarySvg(models.Model):
|
||||
_name = 'test_new_api.binary_svg'
|
||||
_description = 'Test SVG upload'
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#
|
||||
# test cases for new-style fields
|
||||
#
|
||||
import base64
|
||||
from datetime import date, datetime, time
|
||||
import io
|
||||
from PIL import Image
|
||||
|
||||
from odoo import fields
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
@@ -1330,6 +1333,110 @@ class TestFields(common.TransactionCase):
|
||||
self.assertEqual(field.related, ('monetary_id', 'amount'))
|
||||
self.assertEqual(field.currency_field, 'base_currency_id')
|
||||
|
||||
def test_94_image(self):
|
||||
f = io.BytesIO()
|
||||
Image.new('RGB', (4000, 2000), '#4169E1').save(f, 'PNG')
|
||||
f.seek(0)
|
||||
image_w = base64.b64encode(f.read())
|
||||
|
||||
f = io.BytesIO()
|
||||
Image.new('RGB', (2000, 4000), '#4169E1').save(f, 'PNG')
|
||||
f.seek(0)
|
||||
image_h = base64.b64encode(f.read())
|
||||
|
||||
record = self.env['test_new_api.model_image'].create({
|
||||
'name': 'image',
|
||||
'image': image_w,
|
||||
'image_128': image_w,
|
||||
})
|
||||
|
||||
# test create (no resize)
|
||||
self.assertEqual(record.image, image_w)
|
||||
# test create (resize, width limited)
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_128))).size, (128, 64))
|
||||
# test create related store (resize, width limited)
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_512))).size, (512, 256))
|
||||
# test create related no store (resize, width limited)
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_256))).size, (256, 128))
|
||||
|
||||
record.write({
|
||||
'image': image_h,
|
||||
'image_128': image_h,
|
||||
})
|
||||
|
||||
# test write (no resize)
|
||||
self.assertEqual(record.image, image_h)
|
||||
# test write (resize, height limited)
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_128))).size, (64, 128))
|
||||
# test write related store (resize, height limited)
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_512))).size, (256, 512))
|
||||
# test write related no store (resize, height limited)
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_256))).size, (128, 256))
|
||||
|
||||
record = self.env['test_new_api.model_image'].create({
|
||||
'name': 'image',
|
||||
'image': image_h,
|
||||
'image_128': image_h,
|
||||
})
|
||||
|
||||
# test create (no resize)
|
||||
self.assertEqual(record.image, image_h)
|
||||
# test create (resize, height limited)
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_128))).size, (64, 128))
|
||||
# test create related store (resize, height limited)
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_512))).size, (256, 512))
|
||||
# test create related no store (resize, height limited)
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_256))).size, (128, 256))
|
||||
|
||||
record.write({
|
||||
'image': image_w,
|
||||
'image_128': image_w,
|
||||
})
|
||||
|
||||
# test write (no resize)
|
||||
self.assertEqual(record.image, image_w)
|
||||
# test write (resize, width limited)
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_128))).size, (128, 64))
|
||||
# test write related store (resize, width limited)
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_512))).size, (512, 256))
|
||||
# test write related store (resize, width limited)
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_256))).size, (256, 128))
|
||||
|
||||
# test create inverse store
|
||||
record = self.env['test_new_api.model_image'].create({
|
||||
'name': 'image',
|
||||
'image_512': image_w,
|
||||
})
|
||||
record.invalidate_cache(fnames=['image_512'], ids=record.ids)
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_512))).size, (512, 256))
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image))).size, (4000, 2000))
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_256))).size, (256, 128))
|
||||
# test write inverse store
|
||||
record.write({
|
||||
'image_512': image_h,
|
||||
})
|
||||
record.invalidate_cache(fnames=['image_512'], ids=record.ids)
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_512))).size, (256, 512))
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image))).size, (2000, 4000))
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_256))).size, (128, 256))
|
||||
|
||||
# test create inverse no store
|
||||
record = self.env['test_new_api.model_image'].create({
|
||||
'name': 'image',
|
||||
'image_256': image_w,
|
||||
})
|
||||
record.invalidate_cache(fnames=['image_256'], ids=record.ids)
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_512))).size, (512, 256))
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image))).size, (4000, 2000))
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_256))).size, (256, 128))
|
||||
# test write inverse no store
|
||||
record.write({
|
||||
'image_256': image_h,
|
||||
})
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_512))).size, (256, 512))
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image))).size, (2000, 4000))
|
||||
self.assertEqual(Image.open(io.BytesIO(base64.b64decode(record.image_256))).size, (128, 256))
|
||||
|
||||
|
||||
class TestX2many(common.TransactionCase):
|
||||
def test_definition_many2many(self):
|
||||
|
||||
+28
-1
@@ -23,7 +23,7 @@ except ImportError:
|
||||
import psycopg2
|
||||
|
||||
from .tools import float_repr, float_round, frozendict, html_sanitize, human_size, pg_varchar, \
|
||||
ustr, OrderedSet, pycompat, sql, date_utils, unique, IterableGenerator
|
||||
ustr, OrderedSet, pycompat, sql, date_utils, unique, IterableGenerator, image_process
|
||||
from .tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT
|
||||
from .tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
|
||||
from .tools.translate import html_translate, _
|
||||
@@ -1906,6 +1906,33 @@ class Binary(Field):
|
||||
atts.unlink()
|
||||
|
||||
|
||||
class Image(Binary):
|
||||
_slots = {
|
||||
'max_width': 0,
|
||||
'max_height': 0,
|
||||
}
|
||||
|
||||
def create(self, record_values):
|
||||
new_record_values = []
|
||||
for record, value in record_values:
|
||||
new_record_values.append((record, self._image_process(value)))
|
||||
super(Image, self).create(new_record_values)
|
||||
|
||||
def write(self, records, value):
|
||||
value = self._image_process(value)
|
||||
super(Image, self).write(records, value)
|
||||
|
||||
def _image_process(self, value):
|
||||
if value and (self.max_width or self.max_height):
|
||||
value = image_process(value, size=(self.max_width, self.max_height))
|
||||
return value
|
||||
|
||||
def _compute_related(self, records):
|
||||
super(Image, self)._compute_related(records)
|
||||
for record in records:
|
||||
record[self.name] = self._image_process(record[self.name])
|
||||
|
||||
|
||||
class Selection(Field):
|
||||
"""
|
||||
:param selection: specifies the possible values for this field.
|
||||
|
||||
Reference in New Issue
Block a user