[MERGE] Merged trunk-website-al

bzr revid: psa@tinyerp.com-20131018134347-gbvtw3diw1lrli6k
This commit is contained in:
Paramjit Singh Sahota 2013-10-18 19:13:47 +05:30
commit a8519350b9
16 changed files with 254 additions and 202 deletions

View File

@ -14,6 +14,7 @@ import traceback
from openerp.tools.safe_eval import safe_eval
from openerp.exceptions import AccessError, AccessDenied
import werkzeug
from openerp.addons.base.ir.ir_qweb import QWebException
import logging
logger = logging.getLogger(__name__)
@ -197,6 +198,16 @@ class website(osv.osv):
qweb_context['error'] = err[1]
logger.warn("Website Rendering Error.\n\n%s" % traceback.format_exc())
return self.render(cr, uid, ids, 'website.401', qweb_context)
except (QWebException,), err:
qweb_context['traceback'] = traceback.format_exc()
qweb_context['template'] = err.template
qweb_context['message'] = err.message
qweb_context['node'] = err.node and err.node.toxml()
logger.error("Website Rendering Error.\n%(message)s\n%(node)s\n\n%(traceback)s" % qweb_context)
return view.render(
cr, uid,
'website.500' if qweb_context['editable'] else 'website.404',
qweb_context, context=context)
except Exception:
logger.exception("Website Rendering Error.")
qweb_context['traceback'] = traceback.format_exc()
@ -393,3 +404,13 @@ class base_language_install(osv.osv):
'target': 'self'
}
return action
class SeoMetadata(osv.Model):
_name = 'website.seo.metadata'
_description = 'SEO metadata'
_columns = {
'website_meta_title': fields.char("Website meta title", size=70, translate=True),
'website_meta_description': fields.text("Website meta description", size=160, translate=True),
'website_meta_keywords': fields.char("Website meta keywords", translate=True),
}

View File

@ -305,87 +305,53 @@ table.editorbar-panel td.selected {
}
/* ---- SNIPPETS DROP ZONES ---- */
#dark_for_dropzone {
width: 100%;
position: absolute;
top: 0;
left: 0;
opacity: 0.4;
z-index: 1000;
pointer-events: none;
}
#dark_for_dropzone td {
background: black;
}
#dark_for_dropzone td.center.active {
background: transparent;
}
.oe_drop_zone, .oe_drop_zone_style {
border: none;
background: rgba(100, 255, 255, 0.9);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
-ms-border-radius: 4px;
-o-border-radius: 4px;
border-radius: 4px;
}
.oe_drop_zone div, .oe_drop_zone_style div {
margin: 0;
line-height: 48px;
text-align: center;
color: magenta;
font-weight: bold;
font-size: 24px;
}
.oe_drop_zone .oe_active, .oe_drop_zone_style .oe_active {
display: none;
}
.oe_drop_zone_style div {
line-height: 24px;
}
.oe_drop_zone {
.oe_drop_zone.oe_insert {
display: block;
height: 48px;
margin: 0px;
margin-top: -4px;
margin-bottom: -44px;
z-index: 1002;
-webkit-transition: margin 250ms linear;
-moz-transition: margin 250ms linear;
-o-transition: margin 250ms linear;
transition: margin 250ms linear;
width: 100%;
position: absolute;
z-index: 1000;
}
.oe_drop_zone:not(.oe_vertical):before {
.oe_drop_zone.oe_insert:not(.oe_vertical):before {
content: "";
display: block;
border-top: dashed 2px rgba(209, 178, 255, 0.72);
position: relative;
top: 0px;
}
.oe_drop_zone.oe_hover:before {
.oe_drop_zone.oe_insert.oe_hover:before {
border-top: dashed 2px rgba(116, 255, 161, 0.72);
}
.oe_drop_zone.oe_vertical {
.oe_drop_zone.oe_insert.oe_vertical {
width: 48px;
float: left;
position: relative;
margin: 4px -24px !important;
margin: 0px -24px !important;
}
.oe_drop_zone.oe_vertical div {
display: none;
}
.oe_drop_zone.oe_overlay {
.oe_drop_zone.oe_insert.oe_overlay {
border-radius: 3px;
background: rgba(153, 0, 255, 0.5);
}
.oe_drop_zone.oe_drop_active {
background: rgba(100, 255, 255, 0.3);
.oe_drop_zone, .oe_drop_zone_style {
border: none;
background: rgba(153, 0, 255, 0.3);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
-ms-border-radius: 4px;
-o-border-radius: 4px;
border-radius: 4px;
}
.oe_drop_zone.oe_drop_active div {
display: none;
}
.oe_drop_zone.oe_drop_active .oe_active {
display: block;
.oe_drop_zone.oe_hover, .oe_drop_zone_style.oe_hover {
background: rgba(0, 255, 133, 0.3);
z-index: 1001;
}
.oe_drop_zone_style {

View File

@ -242,45 +242,16 @@ table.editorbar-panel
/* ---- SNIPPETS DROP ZONES ---- */
#dark_for_dropzone
width: 100%
position: absolute
top: 0
left: 0
opacity: 0.4
z-index: 1000
pointer-events: none
td
background: #000
&.center.active
background: transparent
.oe_drop_zone, .oe_drop_zone_style
border: none
background: rgba(100, 255, 255, .9)
+border-radius(4px)
div
margin: 0
line-height: 48px
text-align: center
color: #FF00FF
font-weight: bold
font-size: 24px
.oe_active
display: none
.oe_drop_zone_style div
line-height: 24px
.oe_drop_zone
.oe_drop_zone.oe_insert
display: block
height: 48px
margin: 0px
margin-top: -4px
margin-bottom: -44px
z-index: 1002
@include transition(margin 250ms linear)
width: 100%
position: absolute
z-index: 1000
&:not(.oe_vertical):before
content: ""
display: block
@ -293,17 +264,22 @@ table.editorbar-panel
width: 48px
float: left
position: relative
margin: 4px -24px !important
div
display: none
margin: 0px -24px !important
&.oe_overlay
border-radius: 3px
&.oe_drop_active
background: rgba(100, 255, 255, .3)
div
display: none
.oe_active
display: block
//@include background-image( repeating-linear-gradient(45deg, rgba(255,255,255,.1) ,rgba(255,255,255,.1) 35px, rgba(0,0,0,.1) 35px, rgba(0,0,0,.1) 75px))
//background-size: 100px 100px
background: rgba(153, 0, 255,.5)
.oe_drop_zone, .oe_drop_zone_style
border: none
//@include background-image( repeating-linear-gradient(45deg, rgba(255,255,255,.1) ,rgba(255,255,255,.1) 35px, rgba(0,0,0,.1) 35px, rgba(0,0,0,.1) 75px))
//background-size: 100px 100px
background: rgba(153, 0, 255, .3)
+border-radius(4px)
&.oe_hover
background: rgba(0, 255, 133, .3)
z-index: 1001
.oe_drop_zone_style
color: white

View File

@ -216,4 +216,5 @@
[data-snippet-id] {
min-height: 10px;
min-width: 10px;
}
}

View File

@ -235,10 +235,7 @@ footer {
}
.oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child {
position: relative;
}
.oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child div, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child div {
display: none;
position: static;
}
.oe_structure.oe_empty:empty:before, [data-oe-type=html]:empty:before, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child:before, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child:before {

View File

@ -161,9 +161,7 @@ footer
height: 220px !important
.oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child
position: relative
div
display: none
position: static
.oe_structure.oe_empty:empty:before, [data-oe-type=html]:empty:before, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child:before, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child:before
content: 'Click Edit To Create Content'

View File

@ -311,6 +311,7 @@
},
});
var blocks_selector = _.keys(CKEDITOR.dtd.$block).join(',');
/* ----- RICH TEXT EDITOR ---- */
website.RTE = openerp.Widget.extend({
tagName: 'li',
@ -324,6 +325,103 @@
this._super.apply(this, arguments);
},
/**
* In Webkit-based browsers, triple-click will select a paragraph up to
* the start of the next "paragraph" including any empty space
* inbetween. When said paragraph is removed or altered, it nukes
* the empty space and brings part of the content of the next
* "paragraph" (which may well be e.g. an image) into the current one,
* completely fucking up layouts and breaking snippets.
*
* Try to fuck around with selections on triple-click to attempt to
* fix this garbage behavior.
*
* Note: for consistent behavior we may actually want to take over
* triple-clicks, in all browsers in order to ensure consistent cross-
* platform behavior instead of being at the mercy of rendering engines
* & platform selection quirks?
*/
webkitSelectionFixer: function (root) {
root.addEventListener('click', function (e) {
// only webkit seems to have a fucked up behavior, ignore others
// FIXME: $.browser goes away in jquery 1.9...
if (!$.browser.webkit) { return; }
// http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-eventgroupings-mouseevents
// The detail attribute indicates the number of times a mouse button has been pressed
// we just want the triple click
if (e.detail !== 3) { return; }
// Get closest block-level element to the triple-clicked
// element (using ckeditor's block list because why not)
var $closest_block = $(e.target).closest(blocks_selector);
// manually set selection range to the content of the
// triple-clicked block-level element, to avoid crossing over
// between block-level elements
document.getSelection().selectAllChildren($closest_block[0]);
});
},
tableNavigation: function (root) {
var self = this;
$(root).on('keydown', function (e) {
// ignore non-TAB
if (e.which !== 9) { return; }
if (self.handleTab(e)) {
e.preventDefault();
}
});
},
/**
* Performs whatever operation is necessary on a [TAB] hit, returns
* ``true`` if the event's default should be cancelled (if the TAB was
* handled by the function)
*/
handleTab: function (event) {
var forward = !event.shiftKey;
var root = window.getSelection().getRangeAt(0).commonAncestorContainer;
var $cell = $(root).closest('td,th');
if (!$cell.length) { return false; }
var cell = $cell[0];
// find cell in same row
var row = cell.parentNode;
var sibling = row.cells[cell.cellIndex + (forward ? 1 : -1)];
if (sibling) {
document.getSelection().selectAllChildren(sibling);
return true;
}
// find cell in previous/next row
var table = row.parentNode;
var sibling_row = table.rows[row.rowIndex + (forward ? 1 : -1)];
if (sibling_row) {
var new_cell = sibling_row.cells[forward ? 0 : sibling_row.cells.length - 1];
document.getSelection().selectAllChildren(new_cell);
return true;
}
// at edge cells, copy word/openoffice behavior: if going backwards
// from first cell do nothing, if going forwards from last cell add
// a row
if (forward) {
var row_size = row.cells.length;
var new_row = document.createElement('tr');
while(row_size--) {
var newcell = document.createElement('td');
// zero-width space
newcell.textContent = '\u200B';
new_row.appendChild(newcell);
}
table.appendChild(new_row);
document.getSelection().selectAllChildren(new_row.cells[0]);
}
return true;
},
start_edition: function ($elements) {
var self = this;
// create a single editor for the whole page
@ -331,6 +429,8 @@
$(root).on('dragstart', 'img', function (e) {
e.preventDefault();
});
this.webkitSelectionFixer(root);
this.tableNavigation(root);
var editor = this.editor = CKEDITOR.inline(root, self._config());
editor.on('instanceReady', function () {
editor.setReadOnly(false);
@ -341,6 +441,10 @@
self.setup_editables(root);
// disable firefox's broken table resizing thing
document.execCommand("enableObjectResizing", false, "false");
document.execCommand("enableInlineTableEditing", false, "false");
self.trigger('rte:ready');
});
},
@ -394,14 +498,14 @@
// - contextmenu & tabletools (disable contextual menu)
// - bunch of unused plugins
var plugins = [
'a11yhelp', 'basicstyles', 'bidi', 'blockquote',
'a11yhelp', 'basicstyles', 'blockquote',
'clipboard', 'colorbutton', 'colordialog', 'dialogadvtab',
'elementspath', 'enterkey', 'entities', 'filebrowser',
'find', 'floatingspace','format', 'htmlwriter', 'iframe',
'indentblock', 'indentlist', 'justify',
'list', 'pastefromword', 'pastetext', 'preview',
'removeformat', 'resize', 'save', 'selectall', 'stylescombo',
'tab', 'table', 'templates', 'toolbar', 'undo', 'wysiwygarea'
'table', 'templates', 'toolbar', 'undo', 'wysiwygarea'
];
return {
// FIXME

View File

@ -319,32 +319,17 @@
}
var $dark = $(openerp.qweb.render('website.snippet_dark_for_dropzone'))
.css('height', $("body")[0].scrollHeight + 'px');
$("body").append($dark);
$('.oe_drop_zone').droppable({
over: function(){
if( action === 'insert'){
dropped = true;
$(this).first().addClass("oe_drop_active").after($toInsert);
$dark.find("td:first")
.css('height', $toInsert.offset().top+'px');
$dark.find("tr:eq(1) td:first")
.css('width', $toInsert.offset().left+'px');
$dark.find("td.center")
.addClass("active")
.css('height', $toInsert.outerHeight()+'px')
.css('width', $toInsert.outerWidth()+'px');
$(this).first().after($toInsert);
}
},
out: function(){
if( action === 'insert'){
$(this).removeClass("oe_drop_active");
dropped = false;
$toInsert.detach();
$dark.find("td.center").removeClass("active");
$("body").scroll(); // trigger a scroll to reset position for jquery api
}
},
drop: function(){
@ -353,8 +338,15 @@
});
},
stop: function(ev, ui){
if (action === 'insert' && ! dropped) {
var el = $('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first()
if (el) {
el.after($toInsert)
dropped = true;
}
}
$('.oe_drop_zone').droppable('destroy').remove();
$("#dark_for_dropzone").remove();
if (dropped) {
var $target = false;
if(action === 'insert'){
@ -405,7 +397,7 @@
var sibling_selector = selector.siblings;
var vertical_child_selector = selector.vertical_children;
var zone_template = openerp.qweb.render('website.snippet_drop_zone');
var zone_template = "<div class='oe_drop_zone oe_insert'></div>";
if(child_selector){
self.dom_filter(child_selector).each(function (){
@ -425,7 +417,7 @@
var temp_left = 0;
$zone.find('> *:not(.oe_drop_zone):visible').each(function () {
var $col = $(this);
$template.css('height', ($col.outerHeight() + parseInt($col.css("margin-top")) + parseInt($col.css("margin-bottom")) - 8)+'px');
$template.css('height', ($col.outerHeight() + parseInt($col.css("margin-top")) + parseInt($col.css("margin-bottom")))+'px');
$lastinsert = $template.clone();
$(this).after($lastinsert);
@ -594,8 +586,7 @@
* Displayed into the overlay options on focus
*/
_readXMLData: function() {
var self = this;
this.$el = this.parent.$snippets.filter(function () { return $(this).data("snippet-id") == self.snippet_id; }).clone();
this.$el = this.parent.$snippets.siblings("[data-snippet-id='"+this.snippet_id+"']").clone();
this.$editor = this.$el.find(".oe_snippet_options");
var $options = this.$overlay.find(".oe_overlay_options");
this.$editor.prependTo($options.find(".oe_options ul"));
@ -630,28 +621,18 @@
});
},
_drag_and_drop_after_insert_dropzone: function (){},
_drag_and_drop_active_drop_zone: function (){
_drag_and_drop_active_drop_zone: function ($zones){
var self = this;
var $dark = $("#dark_for_dropzone");
$('.oe_drop_zone').droppable({
$zones.droppable({
over: function(){
$(this).first().addClass("oe_drop_active").after(self.$target);
$dark.find("td:first")
.css('height', self.$target.offset().top+'px');
$dark.find("tr:eq(1) td:first")
.css('width', self.$target.offset().left+'px');
$dark.find("td.center")
.addClass("active")
.css('height', self.$target.outerHeight()+'px')
.css('width', self.$target.outerWidth()+'px');
$(".oe_drop_zone.hide").removeClass("hide");
$(this).addClass("hide").first().after(self.$target);
self.dropped = true;
},
out: function(){
$(this).removeClass("oe_drop_active");
$dark.find("td.center").removeClass("active");
$(this).removeClass("hide");
self.$target.detach();
self.dropped = false;
$("body").scroll(); // trigger a scroll to reset position for jquery api
},
});
},
@ -675,12 +656,8 @@
$("body").addClass('move-important');
var $dark = $(openerp.qweb.render('website.snippet_dark_for_dropzone'))
.css('height', $("body")[0].scrollHeight + 'px');
$("body").append($dark);
self._drag_and_drop_after_insert_dropzone();
self._drag_and_drop_active_drop_zone();
self._drag_and_drop_active_drop_zone($('.oe_drop_zone'));
},
_drag_and_drop_stop: function (){
var self = this;
@ -690,7 +667,6 @@
self.$overlay.removeClass("hidden");
$("body").removeClass('move-important');
$('.oe_drop_zone').droppable('destroy').remove();
$('#dark_for_dropzone').remove();
$(".oe_drop_clone, .oe_drop_to_remove").remove();
self.parent.editor_busy = false;
self.get_parent_block();
@ -988,25 +964,23 @@
_drag_and_drop_after_insert_dropzone: function(){
var self = this;
// commented for perf
// var $zones = $(".row:has(> .oe_drop_zone)").each(function () {
// var $row = $(this);
// var width = $row.innerWidth();
// var pos = 0;
// while (width > pos + self.size.width) {
// var $last = $row.find("> .oe_drop_zone:last");
// $last.each(function () {
// pos = $(this).position().left;
// });
// if (width > pos + self.size.width) {
// $row.append("<div class='col-md-1 oe_drop_to_remove'/>");
// var $add_drop = $last.clone();
// $row.append($add_drop);
// self._drag_and_drop_active_drop_zone($add_drop);
// }
// }
// });
var $zones = $(".row:has(> .oe_drop_zone)").each(function () {
var $row = $(this);
var width = $row.innerWidth();
var pos = 0;
while (width > pos + self.size.width) {
var $last = $row.find("> .oe_drop_zone:last");
$last.each(function () {
pos = $(this).position().left;
});
if (width > pos + self.size.width) {
$row.append("<div class='col-md-1 oe_drop_to_remove'/>");
var $add_drop = $last.clone();
$row.append($add_drop);
self._drag_and_drop_active_drop_zone($add_drop);
}
}
});
},
_drag_and_drop_start: function () {
this._super();
@ -1100,6 +1074,14 @@
self.$target.carousel();
});
this.$target.on('dblclick', '.item.active .carousel-image img', function (event) {
var $img = $(event.currentTarget);
var editor = new website.editor.ImageDialog();
editor.on('start', self, function (o) {o.url = $img.attr("src");});
editor.on('save', self, function (o) {$img.attr("src", o.url);});
editor.appendTo($('body'));
});
this.rebind_event();
},
// rebind event to active carousel on edit mode

View File

@ -31,8 +31,8 @@
this one:
</p>
<div class="row">
<div class="col-sm-6 col-sm-offset-3 oe_drop_zone_style text-center">
<div>drop in this zone</div>
<div class="col-sm-4 col-sm-offset-4 oe_drop_zone_style text-center">
<span class="mt16">drop in this zone</span>
</div>
</div>
<div class="text-muted">
@ -57,25 +57,6 @@
<div id='oe_snippets'></div>
</t>
<t t-name="website.snippet_drop_zone">
<div class='oe_drop_zone oe_insert'>
<div>Drag your block here (move your mouse in this zone)</div>
<div class="oe_active">Drop your block</div>
</div>
</t>
<t t-name="website.snippet_dark_for_dropzone">
<div id="dark_for_dropzone">
<table width="100%" height="100%">
<tbody>
<tr><td colspan="3"></td></tr>
<tr><td></td><td class="center"></td><td></td></tr>
<tr><td colspan="3"></td></tr>
</tbody>
</table>
</div>
</t>
<t t-name="website.snippet_overlay">
<div class="oe_overlay">
<div class="oe_overlay_options">

View File

@ -358,7 +358,12 @@
<div class="oe_structure">
<h1 class="container mt32">500: Internal Server Error!</h1>
</div>
<pre t-if="editable" t-esc="traceback"/>
<t t-if="editable">
<h3>An exception appear in the template: <span id="exception_template" t-esc="template"/></h3>
<b id="exception_message" t-esc="message"/>
<pre id="exception_node" t-esc="node"/>
<pre id="exception_traceback" t-esc="traceback"/>
</t>
</div>
</t>
</template>

View File

@ -150,6 +150,13 @@ class WebsiteBlog(http.Controller):
'unable_editor': post.get('unable_editor')
}
if blog_post:
values['main_object'] = blog_post
elif tag:
values['main_object'] = tag
elif category:
values['main_object'] = category
return request.website.render("website_blog.index", values)
@website.route(['/blog/nav'], type='http', auth="public")

View File

@ -31,7 +31,7 @@ import difflib
class BlogCategory(osv.Model):
_name = 'blog.category'
_description = 'Blog Category'
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'website.seo.metadata']
_order = 'name'
_columns = {
@ -47,6 +47,7 @@ class BlogCategory(osv.Model):
class BlogTag(osv.Model):
_name = 'blog.tag'
_description = 'Blog Tag'
_inherit = ['website.seo.metadata']
_order = 'name'
_columns = {
@ -60,7 +61,7 @@ class BlogTag(osv.Model):
class BlogPost(osv.Model):
_name = "blog.post"
_description = "Blog Post"
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'website.seo.metadata']
_order = 'write_date DESC'
# maximum number of characters to display in summary
_shorten_max_char = 250

View File

@ -154,9 +154,10 @@ class website_event(http.Controller):
@website.route(['/event/<int:event_id>'], type='http', auth="public", multilang=True)
def event(self, event_id=None, **post):
event_obj = request.registry['event.event']
event = event_obj.browse(request.cr, request.uid, event_id, dict(request.context, show_address_only=1))
values = {
'event_id': event_obj.browse(request.cr, request.uid, event_id,
dict(request.context, show_address_only=1)),
'event_id': event,
'main_object': event,
'range': range,
'float': float,
}

View File

@ -45,6 +45,9 @@ class event(osv.osv):
string='Website Messages',
help="Website communication history",
),
'website_meta_title': fields.char("Website meta title", size=70, translate=True),
'website_meta_description': fields.text("Website meta description", size=160, translate=True),
'website_meta_keywords': fields.char("Website meta keywords", translate=True),
}
_defaults = {
'website_published': False,

View File

@ -30,8 +30,9 @@ class product_pricelist(osv.Model):
class product_template(osv.Model):
_inherit = "product.template"
_inherit = ["product.template", "website.seo.metadata"]
_order = 'website_sequence desc, website_published, name'
_name = 'product.template'
def _website_url(self, cr, uid, ids, field_name, arg, context=None):
res = {}
@ -50,9 +51,6 @@ class product_template(osv.Model):
'website_style_ids' : fields.many2many('website.product.style','product_website_style_rel', 'product_id', 'style_id', 'Styles'),
'website_sequence': fields.integer('Sequence', help="Determine the display order in the Website E-commerce"),
'website_url': fields.function(_website_url, string="Website url"),
'website_meta_title': fields.char("Website meta title", size=70, translate=True),
'website_meta_description': fields.text("Website meta description", size=160, translate=True),
'website_meta_keywords': fields.char("Website meta keywords", translate=True),
}
_defaults = {
'website_size_x': 1,

View File

@ -29,14 +29,25 @@ class attributes(osv.Model):
result.update(dict(cr.fetchall()))
return result
def _get_min_max(self, cr, uid, ids, context=None):
result = {}
for value in self.pool.get('product.attribute.product').browse(cr, uid, ids, context=context):
if value.type == 'float':
result[value.attribute_id.id] = True
return result.keys()
_columns = {
'name': fields.char('Name', size=64, translate=True, required=True),
'type': fields.selection([('distinct', 'Distinct'), ('float', 'Float')], "Type", required=True),
'value_ids': fields.one2many('product.attribute.value', 'attribute_id', 'Values'),
'product_ids': fields.one2many('product.attribute.product', 'attribute_id', 'Products'),
'float_max': fields.function(_get_float_max, type='float', string="Max", relation='product.attribute.product',store=True),
'float_min': fields.function(_get_float_min, type='float', string="Min", relation='product.attribute.product',store=True),
'float_max': fields.function(_get_float_max, type='float', string="Max", store={
'product.attribute.product': (_get_min_max, ['value','attribute_id'], 20),
}),
'float_min': fields.function(_get_float_min, type='float', string="Min", store={
'product.attribute.product': (_get_min_max, ['value','attribute_id'], 20),
}),
}
_defaults = {
'type': 'distinct'