[IMP] website: display sample data for dynamic snippets
Before this commit when a dynamic snippet was fully configured but returned no data, the rendered section remained empty in edit mode. After this commit when a dynamic snippet is fully configured but has no data to display, some sample data is generated in edit mode to give a feel of how the page will look like when data will be available. task-2446024 https://github.com/odoo/odoo/pull/65176
This commit is contained in:
@@ -270,11 +270,11 @@ class Website(Home):
|
||||
}
|
||||
|
||||
@http.route('/website/snippet/filters', type='json', auth='public', website=True)
|
||||
def get_dynamic_filter(self, filter_id, template_key, limit=None, search_domain=None):
|
||||
def get_dynamic_filter(self, filter_id, template_key, limit=None, search_domain=None, with_sample=False):
|
||||
dynamic_filter = request.env['website.snippet.filter'].sudo().search(
|
||||
[('id', '=', filter_id)] + request.website.website_domain()
|
||||
)
|
||||
return dynamic_filter and dynamic_filter.render(template_key, limit, search_domain) or ''
|
||||
return dynamic_filter and dynamic_filter.render(template_key, limit, search_domain, with_sample) or ''
|
||||
|
||||
@http.route('/website/snippet/options_filters', type='json', auth='user', website=True)
|
||||
def get_dynamic_snippet_filters(self):
|
||||
|
||||
@@ -727,6 +727,7 @@
|
||||
<t t-set="fields" t-value="list(record['fields'].values())"/>
|
||||
<t t-set="fieldsImg" t-value="list(record['image_fields'].values())"/>
|
||||
<div class="card h-100" t-att-data-url="record['fields']['call_to_action_url']">
|
||||
<h4 t-if="is_sample" class="o_ribbon_right bg-warning text-uppercase">Sample</h4>
|
||||
<img t-if="len(fieldsImg) > 0" class="card-img-top p-3" loading="lazy" t-att-src="fieldsImg[0]"/>
|
||||
<div class="card-body">
|
||||
<h5 t-if="len(fields) > 0" class="card-title">
|
||||
@@ -752,6 +753,7 @@
|
||||
<t t-set="fields" t-value="list(record['fields'].values())"/>
|
||||
<t t-set="fieldsImg" t-value="list(record['image_fields'].values())"/>
|
||||
<div class="card h-100" t-att-data-url="record['fields']['call_to_action_url']">
|
||||
<h4 t-if="is_sample" class="o_ribbon_right bg-warning text-uppercase">Sample</h4>
|
||||
<h5 t-if="len(fields) > 0" class="card-header">
|
||||
<t t-raw="fields[0]"/>
|
||||
</h5>
|
||||
|
||||
@@ -8,6 +8,7 @@ from odoo.osv import expression
|
||||
from odoo.tools import html_escape as escape
|
||||
from lxml import etree as ET
|
||||
import logging
|
||||
from random import randint
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -49,17 +50,22 @@ class WebsiteSnippetFilter(models.Model):
|
||||
if not field_name.strip():
|
||||
raise ValidationError(_("Empty field name in %r") % (record.field_names))
|
||||
|
||||
def render(self, template_key, limit, search_domain=[]):
|
||||
def render(self, template_key, limit, search_domain=None, with_sample=False):
|
||||
"""Renders the website dynamic snippet items"""
|
||||
self.ensure_one()
|
||||
assert '.dynamic_filter_template_' in template_key, _("You can only use template prefixed by dynamic_filter_template_ ")
|
||||
if search_domain is None:
|
||||
search_domain = []
|
||||
|
||||
if self.website_id and self.env['website'].get_current_website() != self.website_id:
|
||||
return ''
|
||||
|
||||
records = self._prepare_values(limit, search_domain)
|
||||
is_sample = with_sample and not records
|
||||
if is_sample:
|
||||
records = self._prepare_sample()
|
||||
View = self.env['ir.ui.view'].sudo().with_context(inherit_branding=False)
|
||||
content = View._render_template(template_key, dict(records=records)).decode('utf-8')
|
||||
content = View._render_template(template_key, dict(records=records, is_sample=is_sample)).decode('utf-8')
|
||||
return [ET.tostring(el) for el in ET.fromstring('<root>%s</root>' % content).getchildren()]
|
||||
|
||||
def _prepare_values(self, limit=None, search_domain=None):
|
||||
@@ -95,6 +101,100 @@ class WebsiteSnippetFilter(models.Model):
|
||||
_logger.warning("The provided domain %s in 'ir.actions.server' generated a MissingError in '%s'", search_domain, self._name)
|
||||
return []
|
||||
|
||||
def _get_field_name_and_type(self, model, field_name):
|
||||
"""
|
||||
Separates the name and the widget type
|
||||
|
||||
@param model: Model to which the field belongs, without it type is deduced from field_name
|
||||
@param field_name: Name of the field possibly followed by a colon and a forced field type
|
||||
|
||||
@return Tuple containing the field name and the field type
|
||||
"""
|
||||
field_name, _, field_widget = field_name.partition(":")
|
||||
field = model._fields.get(field_name) if model else None
|
||||
if field:
|
||||
field_type = field.type
|
||||
elif 'image' in field_name:
|
||||
field_type = 'image'
|
||||
elif 'price' in field_name:
|
||||
field_type = 'monetary'
|
||||
else:
|
||||
field_type = 'text'
|
||||
return field_name, field_widget or field_type
|
||||
|
||||
def _prepare_sample(self, length=4):
|
||||
"""
|
||||
Generates sample data and returns it the right format for render.
|
||||
|
||||
@param length: Number of sample records to generate
|
||||
|
||||
@return Array of objets with a value associated to each name in field_names
|
||||
"""
|
||||
if not length:
|
||||
return []
|
||||
sample = []
|
||||
model = self.env[self.filter_id.model_id] if self.filter_id else (
|
||||
self.action_server_id.model_id if self.action_server_id else None)
|
||||
sample_data = self._get_hardcoded_sample(model)
|
||||
for index in range(0, length):
|
||||
single_sample_data = sample_data[index % len(sample_data)].copy()
|
||||
self._fill_sample(single_sample_data, model, index)
|
||||
data = self._get_rendering_data_structure()
|
||||
for field_name in self.field_names.split(","):
|
||||
field_name, field_widget = self._get_field_name_and_type(model, field_name)
|
||||
value = single_sample_data[field_name]
|
||||
if field_widget == 'binary':
|
||||
data['image_fields'][field_name] = self.escape_falsy_as_empty(value)
|
||||
elif field_widget == 'image':
|
||||
data['image_fields'][field_name] = value
|
||||
elif field_widget == 'monetary':
|
||||
FieldMonetary = self.env['ir.qweb.field.monetary']
|
||||
website_currency = self._get_website_currency()
|
||||
data['fields'][field_name] = FieldMonetary.value_to_html(
|
||||
value,
|
||||
{'display_currency': website_currency}
|
||||
)
|
||||
elif 'ir.qweb.field.%s' % field_widget in self.env:
|
||||
data['fields'][field_name] = self.env['ir.qweb.field.%s' % field_widget].value_to_html(
|
||||
value, {})
|
||||
else:
|
||||
data['fields'][field_name] = self.escape_falsy_as_empty(value)
|
||||
data['fields']['call_to_action_url'] = ''
|
||||
sample.append(data)
|
||||
return sample
|
||||
|
||||
def _fill_sample(self, sample, model, index):
|
||||
"""
|
||||
Fills the sample for the given model
|
||||
|
||||
@param sample: Data structure to fill with values for each name in field_names
|
||||
@param model: Model to which the sample belongs
|
||||
@param index: Index of the sample within the dataset
|
||||
"""
|
||||
for field_name in self.field_names.split(","):
|
||||
field_name, field_widget = self._get_field_name_and_type(model, field_name)
|
||||
if field_name not in sample:
|
||||
if field_widget == 'binary':
|
||||
sample[field_name] = None
|
||||
elif field_widget == 'image':
|
||||
sample[field_name] = '/web/image'
|
||||
elif field_widget == 'monetary':
|
||||
sample[field_name] = randint(100, 10000) / 10.0
|
||||
elif field_widget in ('integer', 'float'):
|
||||
sample[field_name] = index
|
||||
else:
|
||||
sample[field_name] = _('Sample %s', index + 1)
|
||||
|
||||
def _get_hardcoded_sample(self, model):
|
||||
"""
|
||||
Returns a hard-coded sample
|
||||
|
||||
@param model: Model of the currently rendered view
|
||||
|
||||
@return Sample data records with field values
|
||||
"""
|
||||
return [{}]
|
||||
|
||||
@api.model
|
||||
def _get_rendering_data_structure(self):
|
||||
return {
|
||||
|
||||
@@ -110,7 +110,8 @@ const DynamicSnippet = publicWidget.Widget.extend({
|
||||
'filter_id': parseInt(this.$el.get(0).dataset.filterId),
|
||||
'template_key': this.$el.get(0).dataset.templateKey,
|
||||
'limit': parseInt(this.$el.get(0).dataset.numberOfRecords),
|
||||
'search_domain': this._getSearchDomain()
|
||||
'search_domain': this._getSearchDomain(),
|
||||
'with_sample': this.editableMode,
|
||||
},
|
||||
})
|
||||
.then(
|
||||
|
||||
@@ -10,3 +10,30 @@ class WebsiteSnippetFilter(models.Model):
|
||||
def _get_website_currency(self):
|
||||
pricelist = self.env['website'].get_current_website().get_current_pricelist()
|
||||
return pricelist.currency_id
|
||||
|
||||
def _get_hardcoded_sample(self, model):
|
||||
samples = super()._get_hardcoded_sample(model)
|
||||
if model and model.model == 'product.product':
|
||||
data = [{
|
||||
'image_512': '/product/static/img/product_chair.png',
|
||||
'display_name': _('Chair'),
|
||||
'description_sale': _('Sit comfortably'),
|
||||
}, {
|
||||
'image_512': '/product/static/img/product_lamp.png',
|
||||
'display_name': _('Lamp'),
|
||||
'description_sale': _('Lightbulb sold separately'),
|
||||
}, {
|
||||
'image_512': '/product/static/img/product_product_20-image.png',
|
||||
'display_name': _('Whiteboard'),
|
||||
'description_sale': _('With three feet'),
|
||||
}, {
|
||||
'image_512': '/product/static/img/product_product_27-image.png',
|
||||
'display_name': _('Drawer'),
|
||||
'description_sale': _('On wheels'),
|
||||
}]
|
||||
merged = []
|
||||
for index in range(0, max(len(samples), len(data))):
|
||||
merged.append({**samples[index % len(samples)], **data[index % len(data)]})
|
||||
# merge definitions
|
||||
samples = merged
|
||||
return samples
|
||||
|
||||
Reference in New Issue
Block a user