[CHG] *: make binary fields default to attachment=True
Check that it makes custom binary fields into attachment as that's the main reason for the change: when users create binary fields via Studio, they're necessarily db-stored (as the interface doesn't allow altering the attachment attribute and it's unclear how we'd handle users switching it on/off every time), which significantly bloats their database (and burns storage & backup space), especially as the primary use case for binary fields is adding images and documents to records. * check that binary fields are properly created as attachment=True * add attachment=False on fields where that seems relevant (most but not all of the fields previously using the default) * remove occurrences of attachment=True closes odoo/odoo#29308
This commit is contained in:
@@ -25,7 +25,7 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
_name = 'account.bank.statement.import'
|
||||
_description = 'Import Bank Statement'
|
||||
|
||||
data_file = fields.Binary(string='Bank Statement File', required=True, help='Get you bank statements in electronic format from your bank and select them here.')
|
||||
data_file = fields.Binary(string='Bank Statement File', attachment=False, required=True, help='Get you bank statements in electronic format from your bank and select them here.')
|
||||
filename = fields.Char()
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -113,7 +113,7 @@ class Import(models.TransientModel):
|
||||
_transient_max_hours = 12.0
|
||||
|
||||
res_model = fields.Char('Model')
|
||||
file = fields.Binary('File', help="File to check and/or import, raw binary (not base64)")
|
||||
file = fields.Binary('File', help="File to check and/or import, raw binary (not base64)", attachment=False)
|
||||
file_name = fields.Char('File Name')
|
||||
file_type = fields.Char('File Type')
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class BaseImportModule(models.TransientModel):
|
||||
_name = "base.import.module"
|
||||
_description = "Import Module"
|
||||
|
||||
module_file = fields.Binary(string='Module .ZIP file', required=True)
|
||||
module_file = fields.Binary(string='Module .ZIP file', required=True, attachment=False)
|
||||
state = fields.Selection([('init', 'init'), ('done', 'done')], string='Status', readonly=True, default='init')
|
||||
import_message = fields.Char()
|
||||
force = fields.Boolean(string='Force init', help="Force init mode even if installed. (will update `noupdate='1'` records)")
|
||||
|
||||
@@ -41,13 +41,13 @@ class FleetVehicleModelBrand(models.Model):
|
||||
_order = 'name asc'
|
||||
|
||||
name = fields.Char('Make', required=True)
|
||||
image = fields.Binary("Logo", attachment=True,
|
||||
image = fields.Binary("Logo",
|
||||
help="This field holds the image used as logo for the brand, limited to 1024x1024px.")
|
||||
image_medium = fields.Binary("Medium-sized image", attachment=True,
|
||||
image_medium = fields.Binary("Medium-sized image",
|
||||
help="Medium-sized logo of the brand. It is automatically "
|
||||
"resized as a 128x128px image, with aspect ratio preserved. "
|
||||
"Use this field in form views or some kanban views.")
|
||||
image_small = fields.Binary("Small-sized image", attachment=True,
|
||||
image_small = fields.Binary("Small-sized image",
|
||||
help="Small-sized logo of the brand. It is automatically "
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "
|
||||
"Use this field anywhere a small image is required.")
|
||||
|
||||
@@ -66,7 +66,7 @@ class GamificationBadge(models.Model):
|
||||
name = fields.Char('Badge', required=True, translate=True)
|
||||
active = fields.Boolean('Active', default=True)
|
||||
description = fields.Text('Description', translate=True)
|
||||
image = fields.Binary("Image", attachment=True, help="This field holds the image used for the badge, limited to 256x256")
|
||||
image = fields.Binary("Image", help="This field holds the image used for the badge, limited to 256x256")
|
||||
|
||||
rule_auth = fields.Selection([
|
||||
('everyone', 'Everyone'),
|
||||
|
||||
@@ -168,15 +168,15 @@ class Employee(models.Model):
|
||||
|
||||
# image: all image fields are base64 encoded and PIL-supported
|
||||
image = fields.Binary(
|
||||
"Photo", default=_default_image, attachment=True,
|
||||
"Photo", default=_default_image,
|
||||
help="This field holds the image used as photo for the employee, limited to 1024x1024px.")
|
||||
image_medium = fields.Binary(
|
||||
"Medium-sized photo", attachment=True,
|
||||
"Medium-sized photo",
|
||||
help="Medium-sized photo of the employee. It is automatically "
|
||||
"resized as a 128x128px image, with aspect ratio preserved. "
|
||||
"Use this field in form views or some kanban views.")
|
||||
image_small = fields.Binary(
|
||||
"Small-sized photo", attachment=True,
|
||||
"Small-sized photo",
|
||||
help="Small-sized photo of the employee. It is automatically "
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "
|
||||
"Use this field anywhere a small image is required.")
|
||||
|
||||
@@ -43,13 +43,13 @@ class ImLivechatChannel(models.Model):
|
||||
nbr_channel = fields.Integer('Number of conversation', compute='_compute_nbr_channel', store=False, readonly=True)
|
||||
|
||||
# images fields
|
||||
image = fields.Binary('Image', default=_default_image, attachment=True,
|
||||
image = fields.Binary('Image', default=_default_image,
|
||||
help="This field holds the image used as photo for the group, limited to 1024x1024px.")
|
||||
image_medium = fields.Binary('Medium', attachment=True,
|
||||
image_medium = fields.Binary('Medium',
|
||||
help="Medium-sized photo of the group. It is automatically "\
|
||||
"resized as a 128x128px image, with aspect ratio preserved. "\
|
||||
"Use this field in form views or some kanban views.")
|
||||
image_small = fields.Binary('Thumbnail', attachment=True,
|
||||
image_small = fields.Binary('Thumbnail',
|
||||
help="Small-sized photo of the group. It is automatically "\
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "\
|
||||
"Use this field anywhere a small image is required.")
|
||||
|
||||
@@ -17,7 +17,7 @@ class AccountFrFec(models.TransientModel):
|
||||
|
||||
date_from = fields.Date(string='Start Date', required=True)
|
||||
date_to = fields.Date(string='End Date', required=True)
|
||||
fec_data = fields.Binary('FEC File', readonly=True)
|
||||
fec_data = fields.Binary('FEC File', readonly=True, attachment=False)
|
||||
filename = fields.Char(string='Filename', size=256, readonly=True)
|
||||
export_type = fields.Selection([
|
||||
('official', 'Official FEC report (posted entries only)'),
|
||||
|
||||
@@ -61,15 +61,15 @@ class LunchProduct(models.Model):
|
||||
|
||||
# image: all image fields are base64 encoded and PIL-supported
|
||||
image = fields.Binary(
|
||||
"Image", attachment=True,
|
||||
"Image",
|
||||
help="This field holds the image used as image for the product, limited to 1024x1024px.")
|
||||
image_medium = fields.Binary(
|
||||
"Medium-sized image", attachment=True,
|
||||
"Medium-sized image",
|
||||
help="Medium-sized image of the product. It is automatically "
|
||||
"resized as a 128x128px image, with aspect ratio preserved, "
|
||||
"only when the image exceeds one of those sizes. Use this field in form views or some kanban views.")
|
||||
image_small = fields.Binary(
|
||||
"Small-sized image", attachment=True,
|
||||
"Small-sized image",
|
||||
help="Small-sized image of the product. It is automatically "
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "
|
||||
"Use this field anywhere a small image is required.")
|
||||
|
||||
@@ -101,13 +101,13 @@ class Channel(models.Model):
|
||||
"Note that they will be able to manage their subscription manually "
|
||||
"if necessary.")
|
||||
# image: all image fields are base64 encoded and PIL-supported
|
||||
image = fields.Binary("Photo", default=_get_default_image, attachment=True,
|
||||
image = fields.Binary("Photo", default=_get_default_image,
|
||||
help="This field holds the image used as photo for the group, limited to 1024x1024px.")
|
||||
image_medium = fields.Binary('Medium-sized photo', attachment=True,
|
||||
image_medium = fields.Binary('Medium-sized photo',
|
||||
help="Medium-sized photo of the group. It is automatically "
|
||||
"resized as a 128x128px image, with aspect ratio preserved. "
|
||||
"Use this field in form views or some kanban views.")
|
||||
image_small = fields.Binary('Small-sized photo', attachment=True,
|
||||
image_small = fields.Binary('Small-sized photo',
|
||||
help="Small-sized photo of the group. It is automatically "
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "
|
||||
"Use this field anywhere a small image is required.")
|
||||
|
||||
@@ -150,15 +150,14 @@ class PaymentAcquirer(models.Model):
|
||||
module_state = fields.Selection(selection=ir_module.STATES, string='Installation State', related='module_id.state', readonly=False)
|
||||
|
||||
image = fields.Binary(
|
||||
"Image", attachment=True,
|
||||
help="This field holds the image used for this provider, limited to 1024x1024px")
|
||||
"Image", help="This field holds the image used for this provider, limited to 1024x1024px")
|
||||
image_medium = fields.Binary(
|
||||
"Medium-sized image", attachment=True,
|
||||
"Medium-sized image",
|
||||
help="Medium-sized image of this provider. It is automatically "
|
||||
"resized as a 128x128px image, with aspect ratio preserved. "
|
||||
"Use this field in form views or some kanban views.")
|
||||
image_small = fields.Binary(
|
||||
"Small-sized image", attachment=True,
|
||||
"Small-sized image",
|
||||
help="Small-sized image of this provider. It is automatically "
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "
|
||||
"Use this field anywhere a small image is required.")
|
||||
@@ -502,8 +501,7 @@ class PaymentIcon(models.Model):
|
||||
name = fields.Char(string='Name')
|
||||
acquirer_ids = fields.Many2many('payment.acquirer', string="Acquirers", help="List of Acquirers supporting this payment icon.")
|
||||
image = fields.Binary(
|
||||
"Image", attachment=True,
|
||||
help="This field holds the image used for this payment icon, limited to 1024x1024px")
|
||||
"Image", help="This field holds the image used for this payment icon, limited to 1024x1024px")
|
||||
|
||||
image_payment_form = fields.Binary(
|
||||
"Image displayed on the payment form", attachment=True)
|
||||
|
||||
@@ -20,13 +20,13 @@ class PosCategory(models.Model):
|
||||
# thumbnails for categories. However if we have a thumbnail for at least one
|
||||
# category, then we display a default image on the other, so that the
|
||||
# buttons have consistent styling.
|
||||
image = fields.Binary(attachment=True,
|
||||
image = fields.Binary(
|
||||
help="This field holds the image used as image for the cateogry, limited to 1024x1024px.")
|
||||
image_medium = fields.Binary(string="Medium-sized image", attachment=True,
|
||||
image_medium = fields.Binary(string="Medium-sized image",
|
||||
help="Medium-sized image of the category. It is automatically "
|
||||
"resized as a 128x128px image, with aspect ratio preserved. "
|
||||
"Use this field in form views or some kanban views.")
|
||||
image_small = fields.Binary(string="Small-sized image", attachment=True,
|
||||
image_small = fields.Binary(string="Small-sized image",
|
||||
help="Small-sized image of the category. It is automatically "
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "
|
||||
"Use this field anywhere a small image is required.")
|
||||
|
||||
@@ -11,7 +11,7 @@ class RestaurantFloor(models.Model):
|
||||
|
||||
name = fields.Char('Floor Name', required=True, help='An internal identification of the restaurant floor')
|
||||
pos_config_id = fields.Many2one('pos.config', string='Point of Sale')
|
||||
background_image = fields.Binary('Background Image', attachment=True, help='A background image used to display a floor layout in the point of sale interface')
|
||||
background_image = fields.Binary('Background Image', help='A background image used to display a floor layout in the point of sale interface')
|
||||
background_color = fields.Char('Background Color', help='The background color of the floor layout, (must be specified in a html-compatible format)', default='rgb(210, 210, 210)')
|
||||
table_ids = fields.One2many('restaurant.table', 'floor_id', string='Tables', help='The list of tables in this floor')
|
||||
sequence = fields.Integer('Sequence', help='Used to sort Floors', default=1)
|
||||
|
||||
@@ -114,7 +114,7 @@ class ProductProduct(models.Model):
|
||||
'product.template.attribute.value', string='Template Attribute Values', compute="_compute_product_template_attribute_value_ids")
|
||||
# image: all image fields are base64 encoded and PIL-supported
|
||||
image_variant = fields.Binary(
|
||||
"Variant Image", attachment=True,
|
||||
"Variant Image",
|
||||
help="This field holds the image used as image for the product variant, limited to 1024x1024px.")
|
||||
image = fields.Binary(
|
||||
"Big-sized image", compute='_compute_images', inverse='_set_image',
|
||||
|
||||
@@ -135,15 +135,14 @@ class ProductTemplate(models.Model):
|
||||
|
||||
# image: all image fields are base64 encoded and PIL-supported
|
||||
image = fields.Binary(
|
||||
"Image", attachment=True,
|
||||
help="This field holds the image used as image for the product, limited to 1024x1024px.")
|
||||
"Image", help="This field holds the image used as image for the product, limited to 1024x1024px.")
|
||||
image_medium = fields.Binary(
|
||||
"Medium-sized image", attachment=True,
|
||||
"Medium-sized image",
|
||||
help="Medium-sized image of the product. It is automatically "
|
||||
"resized as a 128x128px image, with aspect ratio preserved, "
|
||||
"only when the image exceeds one of those sizes. Use this field in form views or some kanban views.")
|
||||
image_small = fields.Binary(
|
||||
"Small-sized image", attachment=True,
|
||||
"Small-sized image",
|
||||
help="Small-sized image of the product. It is automatically "
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "
|
||||
"Use this field anywhere a small image is required.")
|
||||
|
||||
@@ -16,7 +16,7 @@ class ConverterTest(models.Model):
|
||||
float = fields.Float()
|
||||
numeric = fields.Float(digits=(16, 2))
|
||||
many2one = fields.Many2one('web_editor.converter.test.sub')
|
||||
binary = fields.Binary()
|
||||
binary = fields.Binary(attachment=False)
|
||||
date = fields.Date()
|
||||
datetime = fields.Datetime()
|
||||
selection = fields.Selection([
|
||||
|
||||
@@ -86,7 +86,7 @@ class Website(models.Model):
|
||||
social_youtube = fields.Char('Youtube Account', default=_default_social_youtube)
|
||||
social_googleplus = fields.Char('Google+ Account', default=_default_social_googleplus)
|
||||
social_instagram = fields.Char('Instagram Account', default=_default_social_instagram)
|
||||
social_default_image = fields.Binary(string="Default Social Share Image", attachment=True, help="If set, replaces the company logo as the default social share image.")
|
||||
social_default_image = fields.Binary(string="Default Social Share Image", help="If set, replaces the company logo as the default social share image.")
|
||||
|
||||
google_analytics_key = fields.Char('Google Analytics Key')
|
||||
google_management_client_id = fields.Char('Google Client ID')
|
||||
|
||||
@@ -88,7 +88,7 @@ class Track(models.Model):
|
||||
('0', 'Low'), ('1', 'Medium'),
|
||||
('2', 'High'), ('3', 'Highest')],
|
||||
'Priority', required=True, default='1')
|
||||
image = fields.Binary('Image', related='partner_id.image_medium', store=True, attachment=True, readonly=False)
|
||||
image = fields.Binary('Image', related='partner_id.image_medium', store=True, readonly=False)
|
||||
|
||||
@api.multi
|
||||
@api.depends('name')
|
||||
@@ -210,4 +210,4 @@ class Sponsor(models.Model):
|
||||
partner_id = fields.Many2one('res.partner', 'Sponsor/Customer', required=True)
|
||||
url = fields.Char('Sponsor Website')
|
||||
sequence = fields.Integer('Sequence', store=True, related='sponsor_type_id.sequence', readonly=False)
|
||||
image_medium = fields.Binary(string='Logo', related='partner_id.image_medium', store=True, attachment=True, readonly=False)
|
||||
image_medium = fields.Binary(string='Logo', related='partner_id.image_medium', store=True, readonly=False)
|
||||
|
||||
@@ -66,13 +66,13 @@ class ProductPublicCategory(models.Model):
|
||||
# category, then we display a default image on the other, so that the
|
||||
# buttons have consistent styling.
|
||||
# In this case, the default image is set by the js code.
|
||||
image = fields.Binary(attachment=True, help="This field holds the image used as image for the category, limited to 1024x1024px.")
|
||||
image = fields.Binary(help="This field holds the image used as image for the category, limited to 1024x1024px.")
|
||||
website_description = fields.Html('Category Description', sanitize_attributes=False, translate=html_translate)
|
||||
image_medium = fields.Binary(string='Medium-sized image', attachment=True,
|
||||
image_medium = fields.Binary(string='Medium-sized image',
|
||||
help="Medium-sized image of the category. It is automatically "
|
||||
"resized as a 128x128px image, with aspect ratio preserved. "
|
||||
"Use this field in form views or some kanban views.")
|
||||
image_small = fields.Binary(string='Small-sized image', attachment=True,
|
||||
image_small = fields.Binary(string='Small-sized image',
|
||||
help="Small-sized image of the category. It is automatically "
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "
|
||||
"Use this field anywhere a small image is required.")
|
||||
|
||||
@@ -772,7 +772,7 @@ class IrActionsActClient(models.Model):
|
||||
params = fields.Binary(compute='_compute_params', inverse='_inverse_params', string='Supplementary arguments',
|
||||
help="Arguments sent to the client along with "
|
||||
"the view tag")
|
||||
params_store = fields.Binary(string='Params storage', readonly=True)
|
||||
params_store = fields.Binary(string='Params storage', readonly=True, attachment=False)
|
||||
|
||||
@api.depends('params_store')
|
||||
def _compute_params(self):
|
||||
|
||||
@@ -294,7 +294,7 @@ class IrAttachment(models.Model):
|
||||
|
||||
# the field 'datas' is computed and may use the other fields below
|
||||
datas = fields.Binary(string='File Content', compute='_compute_datas', inverse='_inverse_datas')
|
||||
db_datas = fields.Binary('Database Data')
|
||||
db_datas = fields.Binary('Database Data', attachment=False)
|
||||
store_fname = fields.Char('Stored Filename')
|
||||
file_size = fields.Integer('File Size', readonly=True)
|
||||
checksum = fields.Char("Checksum/SHA1", size=40, index=True, readonly=True)
|
||||
|
||||
@@ -42,7 +42,7 @@ class Property(models.Model):
|
||||
value_float = fields.Float()
|
||||
value_integer = fields.Integer()
|
||||
value_text = fields.Text() # will contain (char, text)
|
||||
value_binary = fields.Binary()
|
||||
value_binary = fields.Binary(attachment=False)
|
||||
value_reference = fields.Char()
|
||||
value_datetime = fields.Datetime()
|
||||
type = fields.Selection([('char', 'Char'),
|
||||
|
||||
@@ -39,7 +39,7 @@ class Company(models.Model):
|
||||
logo = fields.Binary(related='partner_id.image', default=_get_logo, string="Company Logo", readonly=False)
|
||||
# logo_web: do not store in attachments, since the image is retrieved in SQL for
|
||||
# performance reasons (see addons/web/controllers/main.py, Binary.company_logo)
|
||||
logo_web = fields.Binary(compute='_compute_logo_web', store=True)
|
||||
logo_web = fields.Binary(compute='_compute_logo_web', store=True, attachment=False)
|
||||
currency_id = fields.Many2one('res.currency', string='Currency', required=True, default=lambda self: self._get_user_currency())
|
||||
user_ids = fields.Many2many('res.users', 'res_company_users_rel', 'cid', 'user_id', string='Accepted Users')
|
||||
account_no = fields.Char(string='Account No.')
|
||||
|
||||
@@ -222,13 +222,13 @@ class Partner(models.Model):
|
||||
company_name = fields.Char('Company Name')
|
||||
|
||||
# image: all image fields are base64 encoded and PIL-supported
|
||||
image = fields.Binary("Image", attachment=True,
|
||||
image = fields.Binary("Image",
|
||||
help="This field holds the image used as avatar for this contact, limited to 1024x1024px",)
|
||||
image_medium = fields.Binary("Medium-sized image", attachment=True,
|
||||
image_medium = fields.Binary("Medium-sized image",
|
||||
help="Medium-sized image of this contact. It is automatically "\
|
||||
"resized as a 128x128px image, with aspect ratio preserved. "\
|
||||
"Use this field in form views or some kanban views.")
|
||||
image_small = fields.Binary("Small-sized image", attachment=True,
|
||||
image_small = fields.Binary("Small-sized image",
|
||||
help="Small-sized image of this contact. It is automatically "\
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "\
|
||||
"Use this field anywhere a small image is required.")
|
||||
|
||||
@@ -246,14 +246,14 @@ class TestCustomFields(common.TransactionCase):
|
||||
self.registry.enter_test_mode(self.cr)
|
||||
self.addCleanup(self.registry.leave_test_mode)
|
||||
|
||||
def create_field(self, name):
|
||||
def create_field(self, name, *, field_type='char'):
|
||||
""" create a custom field and return it """
|
||||
model = self.env['ir.model'].search([('model', '=', self.MODEL)])
|
||||
field = self.env['ir.model.fields'].create({
|
||||
'model_id': model.id,
|
||||
'name': name,
|
||||
'field_description': name,
|
||||
'ttype': 'char',
|
||||
'ttype': field_type,
|
||||
})
|
||||
self.assertIn(name, self.env[self.MODEL]._fields)
|
||||
return field
|
||||
@@ -385,3 +385,12 @@ class TestCustomFields(common.TransactionCase):
|
||||
# uninstall mode: unlink dependant fields
|
||||
field.with_context(_force_unlink=True).unlink()
|
||||
self.assertFalse(dependant.exists())
|
||||
|
||||
def test_create_binary(self):
|
||||
""" binary custom fields should be created as attachment=True to avoid
|
||||
bloating the DB when creating e.g. image fields via studio
|
||||
"""
|
||||
self.create_field('x_image', field_type='binary')
|
||||
custom_binary = self.env[self.MODEL]._fields['x_image']
|
||||
|
||||
self.assertTrue(custom_binary.attachment)
|
||||
|
||||
@@ -25,7 +25,7 @@ class BaseLanguageExport(models.TransientModel):
|
||||
string='File Format', required=True, default='csv')
|
||||
modules = fields.Many2many('ir.module.module', 'rel_modules_langexport', 'wiz_id', 'module_id',
|
||||
string='Apps To Export', domain=[('state','=','installed')])
|
||||
data = fields.Binary('File', readonly=True)
|
||||
data = fields.Binary('File', readonly=True, attachment=False)
|
||||
state = fields.Selection([('choose', 'choose'), ('get', 'get')], # choose language or get the file
|
||||
default='choose')
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class BaseLanguageImport(models.TransientModel):
|
||||
name = fields.Char('Language Name', required=True)
|
||||
code = fields.Char('ISO Code', size=5, required=True,
|
||||
help="ISO Language and Country code, e.g. en_US")
|
||||
data = fields.Binary('File', required=True)
|
||||
data = fields.Binary('File', required=True, attachment=False)
|
||||
filename = fields.Char('File Name', required=True)
|
||||
overwrite = fields.Boolean('Overwrite Existing Terms',
|
||||
help="If you enable this option, existing translations (including custom ones) "
|
||||
|
||||
@@ -12,7 +12,7 @@ class test_model(models.Model):
|
||||
float = fields.Float()
|
||||
numeric = fields.Float(digits=(16, 2))
|
||||
many2one = fields.Many2one('test_converter.test_model.sub', group_expand='_gbf_m2o')
|
||||
binary = fields.Binary()
|
||||
binary = fields.Binary(attachment=False)
|
||||
date = fields.Date()
|
||||
datetime = fields.Datetime()
|
||||
selection = fields.Selection([
|
||||
|
||||
+1
-1
@@ -1742,7 +1742,7 @@ class Binary(Field):
|
||||
_slots = {
|
||||
'prefetch': False, # not prefetched by default
|
||||
'context_dependent': True, # depends on context (content or size)
|
||||
'attachment': False, # whether value is stored in attachment
|
||||
'attachment': True, # whether value is stored in attachment
|
||||
}
|
||||
|
||||
@property
|
||||
|
||||
Reference in New Issue
Block a user