[MERGE] Sync with trunk

bzr revid: tde@openerp.com-20140318093756-3bq7b4n5va33b3l2
This commit is contained in:
Thibault Delavallée 2014-03-18 10:37:56 +01:00
commit ada6d06f8e
9 changed files with 190 additions and 96 deletions

View File

@ -314,6 +314,23 @@ class Website(openerp.addons.web.controllers.main.Home):
'/website/image/<model>/<id>/<field>'
], auth="public", website=True)
def website_image(self, model, id, field, max_width=maxint, max_height=maxint):
""" Fetches the requested field and ensures it does not go above
(max_width, max_height), resizing it if necessary.
Resizing is bypassed if the object provides a $field_big, which will
be interpreted as a pre-resized version of the base field.
If the record is not found or does not have the requested field,
returns a placeholder image via :meth:`~.placeholder`.
Sets and checks conditional response parameters:
* :mailheader:`ETag` is always set (and checked)
* :mailheader:`Last-Modified is set iif the record has a concurrency
field (``__last_update``)
The requested field is assumed to be base64-encoded image data in
all cases.
"""
Model = request.registry[model]
response = werkzeug.wrappers.Response()
@ -322,15 +339,17 @@ class Website(openerp.addons.web.controllers.main.Home):
ids = Model.search(request.cr, request.uid,
[('id', '=', id)], context=request.context) \
or Model.search(request.cr, openerp.SUPERUSER_ID,
[('id', '=', id), ('website_published', '=', True)], context=request.context)
or Model.search(request.cr, openerp.SUPERUSER_ID,
[('id', '=', id), ('website_published', '=', True)], context=request.context)
if not ids:
return self.placeholder(response)
presized = '%s_big' % field
concurrency = '__last_update'
[record] = Model.read(request.cr, openerp.SUPERUSER_ID, [id],
[concurrency, field], context=request.context)
[concurrency, field, presized],
context=request.context)
if concurrency in record:
server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
@ -354,25 +373,28 @@ class Website(openerp.addons.web.controllers.main.Home):
if response.status_code == 304:
return response
data = record[field].decode('base64')
fit = int(max_width), int(max_height)
data = (record.get(presized) or record[field]).decode('base64')
buf = cStringIO.StringIO(data)
image = Image.open(buf)
image.load()
image = Image.open(cStringIO.StringIO(data))
response.mimetype = Image.MIME[image.format]
# record provides a pre-resized version of the base field, use that
# directly
if record.get(presized):
response.set_data(data)
return response
fit = int(max_width), int(max_height)
w, h = image.size
max_w, max_h = fit
if w < max_w and h < max_h:
response.data = data
response.set_data(data)
else:
image.thumbnail(fit, Image.ANTIALIAS)
image.save(response.stream, image.format)
# invalidate content-length computed by make_conditional as writing
# to response.stream does not do it (as of werkzeug 0.9.3)
# invalidate content-length computed by make_conditional as
# writing to response.stream does not do it (as of werkzeug 0.9.3)
del response.headers['Content-Length']
return response

View File

@ -580,10 +580,26 @@ class ir_attachment(osv.osv):
return hashlib.new('sha1', attachment_dict['datas']).hexdigest()
return None
def _datas_big(self, cr, uid, ids, name, arg, context=None):
result = dict.fromkeys(ids, False)
if context and context.get('bin_size'):
return result
for record in self.browse(cr, uid, ids, context=context):
if not record.datas: continue
try:
result[record.id] = openerp.tools.image_resize_image_big(record.datas)
except IOError: # apparently the error PIL.Image.open raises
pass
return result
_columns = {
'datas_checksum': fields.function(_datas_checksum, size=40,
string="Datas checksum", type='char', store=True, select=True),
'website_url': fields.function(_website_url_get, string="Attachment URL", type='char')
'website_url': fields.function(_website_url_get, string="Attachment URL", type='char'),
'datas_big': fields.function (_datas_big, type='binary', store=True,
string="Resized file content"),
}
def create(self, cr, uid, values, context=None):

View File

@ -102,6 +102,10 @@ header a.navbar-brand img {
max-height: 50px;
}
#wrapwrap p:empty:after {
content: "\2060";
}
/* ----- Snippets Styles ----- */
.readable {
font-size: 120%;
@ -116,7 +120,7 @@ header a.navbar-brand img {
}
/* ----- BOOTSTRAP FIX ----- */
.container .container {
.container .container, .readable .container {
padding-left: 0;
padding-right: 0;
width: auto;
@ -217,7 +221,6 @@ footer {
/* -- Hack for removing double scrollbar from mobile preview -- */
div#mobile-preview.modal {
overflow: hidden;
overflow-y: hidden;
}
ul.nav-stacked > li > a {
@ -284,6 +287,7 @@ ul.nav-stacked > li > a {
section, .carousel, .parallax, .row, .hr, .blockquote {
min-height: 30px;
}
.carousel, .parallax, .blockquote {
overflow: hidden;
}

View File

@ -68,6 +68,12 @@ header
img
max-height: 50px
// add {WORD JOINER} (equivalent to deprecated ZERO WIDTH NO-BREAK SPACE) at
// the beginning of paragraphs so they don't "disappear" after saving when
// used solely for spacing.
#wrapwrap p:empty:after
content: '\2060'
/* ----- Snippets Styles ----- */
.readable
@ -78,14 +84,12 @@ header
/* ----- EDITOR ----- */
.css_non_editable_mode_hidden
display: none
/* ----- BOOTSTRAP FIX ----- */
.container
.container, .readable
.container
padding-left: 0
padding-right: 0
@ -180,7 +184,6 @@ footer
/* -- Hack for removing double scrollbar from mobile preview -- */
div#mobile-preview.modal
overflow: hidden
overflow-y: hidden
ul.nav-stacked > li > a
padding: 2px 15px

View File

@ -419,7 +419,7 @@
$target.data("overlay").remove();
$target.removeData("overlay");
}
$target.find("[data-snippet-id]").each(function () {
$target.find(website.snippet.globalSelector).each(function () {
var $snippet = $(this);
$snippet.removeData("snippet-editor");
if ($snippet.data("overlay")) {
@ -623,7 +623,7 @@
this.required = this.$el.data("required");
this.set_active();
this.$el.find('li[data-class] a').on('mouseover mouseout click', _.bind(this._mouse, this));
this.$el.find('li[data-value] a').on('mouseover mouseout click', _.bind(this._mouse, this));
this.$target.on('snippet-style-reset', _.bind(this.set_active, this));
this.start();
@ -643,9 +643,9 @@
var $prev, $next;
if (event.type === 'mouseout') {
$prev = $(event.currentTarget).parent();
$next = this.$el.find("li[data-class].active");
$next = this.$el.find("li[data-value].active");
} else {
$prev = this.$el.find("li[data-class].active");
$prev = this.$el.find("li[data-value].active");
$next = $(event.currentTarget).parent();
}
if (!$prev.length) {
@ -676,6 +676,30 @@
// start is call just after the init
start: function () {
},
/* onFocus
* This method is called when the user click inside the snippet in the dom
*/
onFocus : function () {
},
/* onFocus
* This method is called when the user click outside the snippet in the dom, after a focus
*/
onBlur : function () {
},
/* on_clone
* This method is called when the snippet is cloned ($clone is allready inserted)
*/
on_clone: function ($clone) {
},
/* on_remove
* This method is called when the snippet is removed (dom is removing after this call)
*/
on_remove: function () {
},
/*
* drop_and_build_snippet
* This method is called just after that a thumbnail is drag and dropped into a drop zone
@ -685,6 +709,7 @@
},
/* select
* called when a user select an item
* li must have data-value attribute
* variables: np = {$next, $prev}
* $next is false if they are no next item selected
* $prev is false if they are no previous item selected
@ -693,10 +718,10 @@
var self = this;
// add or remove html class
if (np.$prev) {
this.$target.removeClass(np.$prev.data('class' || ""));
this.$target.removeClass(np.$prev.data('value' || ""));
}
if (np.$next) {
this.$target.addClass(np.$next.data('class') || "");
this.$target.addClass(np.$next.data('value') || "");
}
},
/* preview
@ -710,10 +735,10 @@
// add or remove html class
if (np.$prev) {
this.$target.removeClass(np.$prev.data('class') || "");
this.$target.removeClass(np.$prev.data('value') || "");
}
if (np.$next) {
this.$target.addClass(np.$next.data('class') || "");
this.$target.addClass(np.$next.data('value') || "");
}
},
/* set_active
@ -723,14 +748,14 @@
set_active: function () {
var self = this;
this.$el.find('li').removeClass("active");
var $active = this.$el.find('li[data-class]')
var $active = this.$el.find('li[data-value]')
.filter(function () {
var $li = $(this);
return ($li.data('class') && self.$target.hasClass($li.data('class')));
return ($li.data('value') && self.$target.hasClass($li.data('value')));
})
.first()
.addClass("active");
this.$el.find('li:has(li[data-class].active)').addClass("active");
this.$el.find('li:has(li[data-value].active)').addClass("active");
},
/* clean_for_save
* function called just before save vue
@ -749,7 +774,7 @@
start: function () {
this._super();
var src = this._get_bg();
this.$el.find("li[data-class].active.oe_custom_bg").data("src", src);
this.$el.find("li[data-value].active.oe_custom_bg").data("src", src);
},
select: function(event, np) {
var self = this;
@ -765,7 +790,7 @@
});
editor.on('cancel', self, function () {
if (!np.$prev || np.$prev.data("src") === "") {
self.$target.removeClass(np.$next.data("class"));
self.$target.removeClass(np.$next.data("value"));
self.$target.trigger("snippet-style-change", [self, np]);
}
});
@ -775,7 +800,7 @@
}
} else {
this._set_bg(false);
this.$target.removeClass(np.$prev.data("class"));
this.$target.removeClass(np.$prev.data("value"));
}
},
preview: function (event, np) {
@ -788,41 +813,54 @@
var self = this;
var bg = self.$target.css("background-image");
this.$el.find('li').removeClass("active");
var $active = this.$el.find('li[data-class]')
var $active = this.$el.find('li[data-value]')
.filter(function () {
var $li = $(this);
return ($li.data('src') && bg.indexOf($li.data('src')) >= 0) ||
(!$li.data('src') && self.$target.hasClass($li.data('class')));
(!$li.data('src') && self.$target.hasClass($li.data('value')));
})
.first();
if (!$active.length) {
$active = this.$target.css("background-image") !== 'none' ?
this.$el.find('li[data-class].oe_custom_bg') :
this.$el.find('li[data-class=""]');
this.$el.find('li[data-value].oe_custom_bg') :
this.$el.find('li[data-value=""]');
}
$active.addClass("active");
this.$el.find('li:has(li[data-class].active)').addClass("active");
this.$el.find('li:has(li[data-value].active)').addClass("active");
}
});
website.snippet.options.slider = website.snippet.Option.extend({
unique_id: function () {
var id = 0;
$(".carousel").each(function () {
var cid = 1 + parseInt($(this).attr("id").replace(/[^0123456789]/g, ''),10);
if (id < cid) id = cid;
});
return "myCarousel" + id;
},
drop_and_build_snippet: function() {
var id = $(".carousel").length;
this.id = "myCarousel" + id;
this.id = this.unique_id();
this.$target.attr("id", this.id);
this.$target.find(".carousel-control").attr("href", "#myCarousel" + id);
this.$target.find("[data-target]").attr("data-target", "#myCarousel" + id);
this.$target.find("[data-slide]").attr("href", "#" + this.id);
this.$target.find("[data-slide-to]").attr("data-target", "#" + this.id);
this.rebind_event();
},
on_clone: function ($clone) {
var id = this.unique_id();
$clone.attr("id", id);
$clone.find("[data-slide]").attr("href", "#" + id);
$clone.find("[data-slide-to]").attr("data-target", "#" + id);
},
// rebind event to active carousel on edit mode
rebind_event: function () {
var self = this;
this.$target.find('.carousel-indicators [data-target]').off('click').on('click', function () {
this.$target.find('.carousel-indicators [data-slide-to]').off('click').on('click', function () {
self.$target.carousel(+$(this).data('slide-to')); });
this.$target.attr('contentEditable', 'false');
this.$target.find('.oe_structure, .content>.row').attr('contentEditable', 'true');
this.$target.find('.oe_structure, .content>.row, [data-slide]').attr('contentEditable', 'true');
},
clean_for_save: function () {
this._super();
@ -905,7 +943,7 @@
},
load_style_options : function () {
this._super();
$(".snippet-style-size li[data-class='']").remove();
$(".snippet-style-size li[data-value='']").remove();
},
start : function () {
var self = this;
@ -923,11 +961,11 @@
$active.css("background-image", self.$target.css("background-image"));
}
if (np.$prev) {
$active.removeClass(np.$prev.data("class"));
$active.removeClass(np.$prev.data("value"));
}
if (np.$next) {
$active.addClass(np.$next.data("class"));
add_class(np.$next.data("class"));
$active.addClass(np.$next.data("value"));
add_class(np.$next.data("value"));
}
});
this.$target.on('slid', function () { // slide.bs.carousel
@ -949,11 +987,13 @@
var bg = this.$target.data("snippet-option-ids").background;
if (!bg) return $clone;
var $styles = bg.$el.find("li[data-class]:not(.oe_custom_bg)");
var $styles = bg.$el.find("li[data-value]:not(.oe_custom_bg)");
var styles_index = $styles.index($styles.filter(".active")[0]);
$styles.removeClass("active");
var $select = $($styles[styles_index >= $styles.length-1 ? 0 : styles_index+1]);
$select.addClass("active");
$clone.css("background-image", $select.data("src") ? "url('"+ $select.data("src") +"')" : "");
$clone.addClass($select.data("class") || "");
$clone.addClass($select.data("value") || "");
return $clone;
},
@ -1187,25 +1227,22 @@
this._super();
},
hide_remove_button: function() {
this.$overlay.find('.oe_snippet_remove').toggleClass("hidden",
!this.$target.siblings().length && this.$target.parents("[data-snippet-id]:first").find("[data-snippet-id='colmd']").length > 1);
this.$overlay.find('.oe_snippet_remove').toggleClass("hidden", !this.$target.siblings().length);
},
onFocus : function () {
this._super();
this.hide_remove_button();
},
on_clone: function () {
var $clone = this.$target.clone(false);
on_clone: function ($clone) {
var _class = $clone.attr("class").replace(/\s*(col-lg-offset-|col-md-offset-)([0-9-]+)/g, '');
$clone.attr("class", _class);
this.$target.after($clone);
this.hide_remove_button();
return false;
},
on_remove: function () {
if (!this.$target.siblings().length) {
var $parent = this.$target.parents("[data-snippet-id]:first");
if($parent.find("[data-snippet-id='colmd']").length > 1) {
var $parent = this.$target.parents(".row:first");
if($parent.find("[class*='col-md']").length > 1) {
return false;
} else {
if (!$parent.data("snippet-editor")) {
@ -1455,7 +1492,7 @@
get_parent_block: function () {
var self = this;
var $button = this.$overlay.find('.oe_snippet_parent');
var $parent = this.$target.parents("[data-snippet-id]:first");
var $parent = this.$target.parents(website.snippet.globalSelector).first();
if ($parent.length) {
$button.removeClass("hidden");
$button.off("click").on('click', function (event) {
@ -1483,12 +1520,18 @@
on_clone: function () {
var $clone = this.$target.clone(false);
this.$target.after($clone);
for (var i in this.styles){
this.styles[i].on_clone($clone);
}
return false;
},
on_remove: function () {
this.onBlur();
var index = _.indexOf(this.BuildingBlock.snippets, this.$target.get(0));
for (var i in this.styles){
this.styles[i].on_remove();
}
delete this.BuildingBlock.snippets[index];
this.$target.remove();
this.$overlay.remove();
@ -1511,12 +1554,18 @@
*/
onFocus : function () {
this.$overlay.addClass('oe_active');
for (var i in this.styles){
this.styles[i].onFocus();
}
},
/* onFocus
* This method is called when the user click outside the snippet in the dom, after a focus
*/
onBlur : function () {
for (var i in this.styles){
this.styles[i].onBlur();
}
this.$overlay.removeClass('oe_active');
},

View File

@ -35,7 +35,7 @@
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Insert building blocks"),
content: _t("To add content in a page, you can insert building blocks."),
content: _t("Click here to insert blocks of centent in the page."),
popover: { fixed: true },
},
{
@ -101,13 +101,13 @@
element: '.modal:has(#mobile-viewport) button[data-dismiss=modal]',
placement: 'right',
title: _t("Check Mobile Preview"),
content: _t("Scroll in the mobile preview to test the rendering. Once it's ok, close this dialog."),
content: _t("Scroll to check rendering and then close the mobile preview."),
popover: { next: _t("Continue") },
},
{
waitNot: '.modal',
element: '#content-menu-button',
placement: 'bottom',
placement: 'left',
title: _t("Add new pages and menus"),
content: _t("The 'Content' menu allows you to add pages or add the top menu."),
popover: { next: _t("Close Tutorial") },

View File

@ -135,7 +135,7 @@ class TestConvertBack(common.TransactionCase):
rendered = self.registry('website.qweb').render_tag_field(
e, {'field': field_value}, '', ir_qweb.QWebContext(self.cr, self.uid, {
'record': record,
}))
}, context={'inherit_branding': True}))
element = html.fromstring(
rendered, parser=html.HTMLParser(encoding='utf-8'))
@ -216,7 +216,7 @@ class TestConvertBack(common.TransactionCase):
rendered = self.registry('website.qweb').render_tag_field(
e, {'field': field_value}, '', ir_qweb.QWebContext(self.cr, self.uid, {
'record': record,
}))
}, context={'inherit_branding': True}))
element = html.fromstring(rendered, parser=html.HTMLParser(encoding='utf-8'))
# emulate edition

View File

@ -860,7 +860,7 @@
<li class="dropdown-submenu">
<a tabindex="-1" href="#">Style</a>
<ul class="dropdown-menu">
<li data-class="readable"><a>Narrow</a></li>
<li data-value="readable"><a>Narrow</a></li>
</ul>
</li>
</div>
@ -873,51 +873,51 @@
<li class="dropdown-submenu">
<a tabindex="-2" href="#">Uniform Color</a>
<ul class="dropdown-menu">
<li data-class='oe_dark'><a>Darken</a></li>
<li data-class='oe_green'><a>Green</a></li>
<li data-class='oe_red'><a>Red</a></li>
<li data-class='oe_blue_light'><a>Turquoise</a></li>
<li data-class='oe_blue'><a>Dark Blue</a></li>
<li data-class='oe_orange'><a>Orange</a></li>
<li data-class='oe_purple'><a>Purple</a></li>
<li data-class='oe_black'><a>Black</a></li>
<li data-value='oe_dark'><a>Darken</a></li>
<li data-value='oe_green'><a>Green</a></li>
<li data-value='oe_red'><a>Red</a></li>
<li data-value='oe_blue_light'><a>Turquoise</a></li>
<li data-value='oe_blue'><a>Dark Blue</a></li>
<li data-value='oe_orange'><a>Orange</a></li>
<li data-value='oe_purple'><a>Purple</a></li>
<li data-value='oe_black'><a>Black</a></li>
</ul>
</li>
<li class="dropdown-submenu">
<a tabindex="-2" href="#">People</a>
<ul class="dropdown-menu">
<li data-class="oe_img_bg" data-src="/website/static/src/img/parallax/parallax_bg.jpg"><a>Sunflower</a></li>
<li data-class="oe_img_bg" data-src="/website/static/src/img/banner/business_guy.jpg"><a>Business Guy</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/parallax/parallax_bg.jpg"><a>Sunflower</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/banner/business_guy.jpg"><a>Business Guy</a></li>
</ul>
</li>
<li class="dropdown-submenu">
<a tabindex="-2" href="#">Landscape</a>
<ul class="dropdown-menu">
<li data-class="oe_img_bg" data-src="/website/static/src/img/banner/flower_field.jpg"><a>Flowers Field</a></li>
<li data-class="oe_img_bg" data-src="/website/static/src/img/banner/landscape.jpg"><a>Landscape</a></li>
<li data-class="oe_img_bg" data-src="/website/static/src/img/banner/mountains.jpg"><a>Mountains</a></li>
<li data-class="oe_img_bg" data-src="/website/static/src/img/banner/greenfields.jpg"><a>Greenfields</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/banner/flower_field.jpg"><a>Flowers Field</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/banner/landscape.jpg"><a>Landscape</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/banner/mountains.jpg"><a>Mountains</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/banner/greenfields.jpg"><a>Greenfields</a></li>
</ul>
</li>
<li class="dropdown-submenu">
<a tabindex="-2" href="#">Various</a>
<ul class="dropdown-menu">
<li data-class="oe_img_bg" data-src="/website/static/src/img/banner/aqua.jpg"><a>Aqua</a></li>
<li data-class="oe_img_bg" data-src="/website/static/src/img/banner/baby_blue.jpg"><a>Baby Blue</a></li>
<li data-class="oe_img_bg" data-src="/website/static/src/img/banner/black.jpg"><a>Black</a></li>
<li data-class="oe_img_bg" data-src="/website/static/src/img/banner/color_splash.jpg"><a>Color Splash</a></li>
<li data-class="oe_img_bg" data-src="/website/static/src/img/banner/mango.jpg"><a>Mango</a></li>
<li data-class="oe_img_bg" data-src="/website/static/src/img/banner/orange_red.jpg"><a>Orange Red</a></li>
<li data-class="oe_img_bg" data-src="/website/static/src/img/banner/flower.jpg"><a>Purple</a></li>
<li data-class="oe_img_bg" data-src="/website/static/src/img/banner/velour.jpg"><a>Velour</a></li>
<li data-class="oe_img_bg" data-src="/website/static/src/img/banner/wood.jpg"><a>Wood</a></li>
<li data-class="oe_img_bg" data-src="/website/static/src/img/banner/yellow_green.jpg"><a>Yellow Green</a></li>
<li data-class="oe_img_bg" data-src="/website/static/src/img/parallax/quote.png"><a>Quote</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/banner/aqua.jpg"><a>Aqua</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/banner/baby_blue.jpg"><a>Baby Blue</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/banner/black.jpg"><a>Black</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/banner/color_splash.jpg"><a>Color Splash</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/banner/mango.jpg"><a>Mango</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/banner/orange_red.jpg"><a>Orange Red</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/banner/flower.jpg"><a>Purple</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/banner/velour.jpg"><a>Velour</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/banner/wood.jpg"><a>Wood</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/banner/yellow_green.jpg"><a>Yellow Green</a></li>
<li data-value="oe_img_bg" data-src="/website/static/src/img/parallax/quote.png"><a>Quote</a></li>
</ul>
</li>
<li data-class=""><a>None</a></li>
<li data-value=""><a>None</a></li>
<li><a style="background: none; padding: 5px; border-top: 1px solid #ddd;"></a></li>
<li class="oe_custom_bg" data-class="oe_img_bg"><a><b>Choose an image...</b></a></li>
<li class="oe_custom_bg" data-value="oe_img_bg"><a><b>Choose an image...</b></a></li>
</ul>
</li>
</div>
@ -927,9 +927,9 @@
<li class="dropdown-submenu" data-required="true">
<a tabindex="-1" href="#">Layout</a>
<ul class="dropdown-menu">
<li data-class="text_only"><a>Text Only</a></li>
<li data-class="image_text"><a>Image - Text</a></li>
<li data-class="text_image"><a>Text - Image</a></li>
<li data-value="text_only"><a>Text Only</a></li>
<li data-value="image_text"><a>Image - Text</a></li>
<li data-value="text_image"><a>Text - Image</a></li>
</ul>
</li>
</div>

View File

@ -200,8 +200,8 @@
</table>
<t t-if="not bins">
<div class="text-center text-muted">
<h3 class="css_editable_display">No product defined.</h3>
<t groups="base.group_website_publisher">
<h3>No product found.</h3>
<t groups="base.group_website_publisher" t-ignore="true">
<p groups="base.group_sale_manager">Use the <i>'Content'</i> top menu to create a new product.</p>
</t>
</div>