[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:
Benoit Socias
2021-02-16 10:17:58 +00:00
parent f379e2e373
commit 0cede3fa14
5 changed files with 135 additions and 5 deletions
+2 -2
View File
@@ -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):
+2
View File
@@ -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>
+102 -2
View File
@@ -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