[MERGE] Sync with website-al

bzr revid: tde@openerp.com-20131219102240-43nvqj9hkzn5ymt2
This commit is contained in:
Thibault Delavallée
2013-12-19 11:22:40 +01:00
13 changed files with 281 additions and 91 deletions
+26 -9
View File
@@ -204,12 +204,29 @@ class ManyToOne(orm.AbstractModel):
_inherit = ['website.qweb.field', 'ir.qweb.field.many2one']
def from_html(self, cr, uid, model, column, element, context=None):
# FIXME: this behavior is really weird, what if the user wanted to edit the name of the related thingy? Should m2os really be editable without a widget?
matches = self.pool[column._obj].name_search(
cr, uid, name=element.text_content().strip(), context=context)
# FIXME: no match? More than 1 match?
assert len(matches) == 1
return matches[0][0]
# FIXME: layering violations all the things
Model = self.pool[element.get('data-oe-model')]
M2O = self.pool[column._obj]
field = element.get('data-oe-field')
id = int(element.get('data-oe-id'))
value = element.text_content().strip()
# if anything blows up, just ignore it and bail
try:
# get parent record
[obj] = Model.read(cr, uid, [id], [field])
# get m2o record id
(m2o_id, _) = obj[field]
# assume _rec_name and write directly to it
M2O.write(cr, uid, [m2o_id], {
M2O._rec_name: value
}, context=context)
except:
logger.exception("Could not save %r to m2o field %s of model %s",
value, field, Model._name)
# not necessary, but might as well be explicit about it
return None
class HTML(orm.AbstractModel):
_name = 'website.qweb.field.html'
@@ -281,7 +298,7 @@ class Image(orm.AbstractModel):
match.group('module'), 'static', *(rest.split('/')))
if not path:
return False
return None
try:
with open(path, 'rb') as f:
@@ -292,7 +309,7 @@ class Image(orm.AbstractModel):
return f.read().encode('base64')
except Exception:
logger.exception("Failed to load local image %r", url)
return False
return None
def load_remote_url(self, url):
try:
@@ -310,7 +327,7 @@ class Image(orm.AbstractModel):
image.load()
except Exception:
logger.exception("Failed to load remote image %r", url)
return False
return None
# don't use original data in case weird stuff was smuggled in, with
# luck PIL will remove some of it?
+5 -3
View File
@@ -70,9 +70,11 @@ class view(osv.osv):
el.get('data-oe-type'))
value = converter.from_html(cr, uid, Model, column, el)
Model.write(cr, uid, [int(el.get('data-oe-id'))], {
field: value
}, context=context)
if value is not None:
# TODO: batch writes?
Model.write(cr, uid, [int(el.get('data-oe-id'))], {
field: value
}, context=context)
def to_field_ref(self, cr, uid, el, context=None):
# filter out meta-information inserted in the document
+14 -14
View File
@@ -1,4 +1,4 @@
/* ---- CKEditor Minimal Reset ---- */
/* ---- CKEditor Minimal Reset ---- {{{ */
.navbar.navbar-inverse .cke_chrome {
border: none;
}
@@ -28,7 +28,7 @@
box-shadow: none;
}
/* ---- OpenERP Style ---- */
/* ---- OpenERP Style ---- {{{ */
.oe_website_editorbar {
position: fixed;
top: 0;
@@ -103,7 +103,7 @@
top: 2px;
}
/* ---- EDITOR BAR ---- */
/* ---- EDITOR BAR ---- {{{ */
table.editorbar-panel {
cursor: pointer;
width: 100%;
@@ -115,6 +115,7 @@ table.editorbar-panel td.selected {
background-color: #b1c9d9;
}
/* ---- TRANSLATIONS ---- {{{ */
.oe_translate_or {
color: white;
padding: 0 0 0 1em;
@@ -145,6 +146,7 @@ table.editorbar-panel td.selected {
background: #ffffb6;
}
/* ---- MENU ---- {{{ */
div.oe_menu_buttons {
top: -8px;
right: -8px;
@@ -171,11 +173,12 @@ ul.oe_menu_editor .disclose {
display: none;
}
/* ---- RTE ---- */
/* ---- RTE ---- {{{ */
.oe_editable .btn, .btn.oe_editable {
-webkit-user-select: auto;
-moz-user-select: auto;
user-select: auto;
cursor: text !important;
}
.modal-dialog.select-image {
@@ -233,12 +236,11 @@ ul.oe_menu_editor .disclose {
z-index: 1000;
}
/* ---- SNIPPET EDITOR ---- */
/* ---- SNIPPET EDITOR ---- {{{ */
#oe_snippets {
position: fixed;
left: 0px;
right: 0px;
top: 51px;
background: #282828;
-webkit-box-shadow: 0px 10px 10px -10px black inset;
-moz-box-shadow: 0px 10px 10px -10px black inset;
@@ -368,7 +370,7 @@ ul.oe_menu_editor .disclose {
background: #747474, radial-gradient(rgba(0, 0, 0, 0.25), rgba(0, 0, 0, 0.4));
}
/* ---- SNIPPETS DROP ZONES ---- */
/* ---- SNIPPETS DROP ZONES ---- {{{ */
.oe_drop_zone.oe_insert {
display: block;
height: 48px;
@@ -429,7 +431,7 @@ ul.oe_menu_editor .disclose {
padding-top: 12px;
}
/* ---- SNIPPET MANIPULATOR ---- */
/* ---- SNIPPET MANIPULATOR ---- {{{ */
.oe_overlay {
display: none;
position: absolute;
@@ -599,7 +601,7 @@ ul.oe_menu_editor .disclose {
cursor: move !important;
}
/* ---- MOBILE PREVIEW ---- */
/* ---- MOBILE PREVIEW ---- {{{ */
.oe_mobile_preview.modal .modal-content {
height: 660px;
background-color: black;
@@ -655,7 +657,7 @@ ul.oe_menu_editor .disclose {
background-color: black;
}
/* ---- SEO TOOLS ---- */
/* ---- SEO TOOLS ---- {{{ */
.oe_seo_configuration .modal-dialog {
width: 80%;
}
@@ -674,10 +676,9 @@ ul.oe_menu_editor .disclose {
border-radius: 0.4em;
}
/* ---- ACE EDITOR ---- */
/* ---- ACE EDITOR ---- {{{ */
.oe_ace_view_editor {
position: fixed;
top: 51px;
right: 0;
z-index: 1000;
height: 100%;
@@ -707,7 +708,6 @@ ul.oe_menu_editor .disclose {
position: absolute;
top: 30px;
right: 0;
bottom: 51px;
left: 0;
}
.oe_ace_view_editor .ace_editor .ace_gutter {
@@ -723,7 +723,7 @@ ul.oe_menu_editor .disclose {
opacity: 0;
}
/* ---- EDITOR TOUR ---- */
/* ---- EDITOR TOUR ---- {{{ */
div.tour-backdrop {
z-index: 2009;
}
+43 -19
View File
@@ -1,8 +1,6 @@
@charset "utf-8"
@import "common"
/* ---- CKEditor Minimal Reset ---- */
/* ---- CKEditor Minimal Reset ---- {{{ */
.navbar.navbar-inverse .cke_chrome
border: none
@@ -26,7 +24,9 @@
border: none
+box-shadow(none)
/* ---- OpenERP Style ---- */
// }}}
/* ---- OpenERP Style ---- {{{ */
.oe_website_editorbar
position: fixed
@@ -85,7 +85,9 @@
position: relative !important
top: +2px
/* ---- EDITOR BAR ---- */
// }}}
/* ---- EDITOR BAR ---- {{{ */
table.editorbar-panel
cursor: pointer
@@ -95,7 +97,10 @@ table.editorbar-panel
td.selected
background-color: #b1c9d9
// ---- TRANSLATIONS ---- {{{
// }}}
/* ---- TRANSLATIONS ---- {{{ */
.oe_translate_or
color: white
padding: 0 0 0 1em
@@ -112,9 +117,11 @@ table.editorbar-panel
padding: 0 10px
.oe_translatable_todo
background: rgb(255, 255, 182)
// }}}
// -------- MENU -------- {{{
/* ---- MENU ---- {{{ */
div.oe_menu_buttons
top: -8px
right: -8px
@@ -137,12 +144,13 @@ ul.oe_menu_editor
// }}}
/* ---- RTE ---- */
/* ---- RTE ---- {{{ */
// bootstrap makes .btn elements unselectable -> RTE double-click can't know
// about them either
.oe_editable .btn, .btn.oe_editable
+user-select(auto)
cursor: text !important
.modal-dialog.select-image
width: 80%
@@ -197,13 +205,15 @@ ul.oe_menu_editor
// This z-index is due to .navbar of bootstrap
z-index: 1000
/* ---- SNIPPET EDITOR ---- */
// }}}
/* ---- SNIPPET EDITOR ---- {{{ */
#oe_snippets
position: fixed
left: 0px
right: 0px
top: 51px
// top property is set programmatically
background: rgb(40,40,40)
+box-shadow(0px 10px 10px -10px black inset)
z-index: 1010
@@ -286,7 +296,9 @@ ul.oe_menu_editor
#oe_snippets .oe_snippet_thumbnail
@include background(#747474, radial-gradient(rgba(0,0,0,0.25),rgba(0,0,0,0.4)))
/* ---- SNIPPETS DROP ZONES ---- */
// }}}}
/* ---- SNIPPETS DROP ZONES ---- {{{ */
.oe_drop_zone.oe_insert
display: block
@@ -333,7 +345,9 @@ ul.oe_menu_editor
margin-bottom: 32px
padding-top: 12px
/* ---- SNIPPET MANIPULATOR ---- */
// }}}
/* ---- SNIPPET MANIPULATOR ---- {{{ */
.oe_overlay
display: none
@@ -454,8 +468,9 @@ ul.oe_menu_editor
.move-important, .move-important *
cursor: move !important
// }}}
/* ---- MOBILE PREVIEW ---- */
/* ---- MOBILE PREVIEW ---- {{{ */
$mobile_preview_background: #000000
$mobile_preview_border: #1C1F1F
@@ -498,7 +513,9 @@ $icon_close: #E00101
.modal-footer
background-color: $mobile_preview_background
/* ---- SEO TOOLS ---- */
// }}}
/* ---- SEO TOOLS ---- {{{ */
$remove_color: $icon_close
$in_title_color: #5cb85c
@@ -516,15 +533,16 @@ $highlighted_text_color: #ffffff
padding: .2em .4em .2em .5em
+border-radius(.4em)
/* ---- ACE EDITOR ---- */
// }}}
/* ---- ACE EDITOR ---- {{{ */
$editorbar_height: 30px
// TODO Fix => might break with themes
$navbar_height: 51px
.oe_ace_view_editor
position: fixed
top: $navbar_height
// top property is set programmatically
right: 0
z-index: 1000
height: 100%
@@ -546,7 +564,7 @@ $navbar_height: 51px
position: absolute
top: $editorbar_height
right: 0
bottom: $navbar_height
// bottom property is set programmatically
left: 0
.ace_gutter
cursor: ew-resize
@@ -556,9 +574,15 @@ $navbar_height: 51px
z-index: -1000
+opacity(0)
/* ---- EDITOR TOUR ---- */
// }}}
/* ---- EDITOR TOUR ---- {{{ */
div.tour-backdrop
z-index: 2009
.popover.tour
z-index: 2010
// }}}
// vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker:
+26 -19
View File
@@ -1,35 +1,34 @@
(function () {
'use strict';
var globalEditor;
var hash = "#advanced-view-editor";
var website = openerp.website;
website.add_template_file('/website/static/src/xml/website.ace.xml');
website.ready().then(function () {
if (window.location.hash.indexOf(hash) >= 0) {
launch();
}
});
function launch () {
if (globalEditor) {
globalEditor.open();
} else {
globalEditor = new website.ace.ViewEditor();
globalEditor.appendTo($(document.body));
}
}
website.EditorBar.include({
events: _.extend({}, website.EditorBar.prototype.events, {
'click a[data-action=ace]': 'launchAce',
}),
start: function () {
var self = this;
this.globalEditor = null;
return this._super.apply(this, arguments).then(function () {
if (window.location.hash.indexOf(hash) >= 0) {
self.launchAce();
}
});
},
launchAce: function (e) {
e.preventDefault();
launch();
if (e) {
e.preventDefault();
}
if (this.globalEditor) {
this.globalEditor.open();
} else {
this.globalEditor = new website.ace.ViewEditor(this);
this.globalEditor.appendTo($(document.body));
}
},
});
@@ -104,6 +103,10 @@
self.aceEditor.resize();
self.$el.width(width);
}
function resizeEditorHeight(height) {
self.$el.css('top', height);
self.$('.ace_editor').css('bottom', height);
}
function storeEditorWidth() {
window.localStorage.setItem('ace_editor_width', self.$el.width());
}
@@ -133,7 +136,11 @@
$('button[data-action=edit]').click(function () {
self.close();
});
this.getParent().on('change:height', this, function (editor) {
resizeEditorHeight(editor.get('height'));
});
resizeEditor(readEditorWidth());
resizeEditorHeight(this.getParent().get('height'));
},
loadViews: function (views) {
var self = this;
+15 -4
View File
@@ -374,7 +374,6 @@
editor.edit();
}
});
$body.css('padding-top', '50px'); // Not working properly: editor.$el.outerHeight());
};
/* ----- TOP EDITOR BAR FOR ADMIN ---- */
@@ -423,7 +422,6 @@
},
start: function() {
var self = this;
this.saving_mutex = new openerp.Mutex();
this.$('#website-top-edit').hide();
@@ -443,12 +441,25 @@
this.rte.on('rte:ready', this, function () {
self.setup_hover_buttons();
self.trigger('rte:ready');
self.check_height();
});
$(window).on('resize', _.debounce(this.check_height.bind(this), 50));
this.check_height();
return $.when(
this._super.apply(this, arguments),
this.rte.appendTo(this.$('#website-top-edit .nav.pull-right'))
);
).then(function () {
self.check_height();
});
},
check_height: function () {
var editor_height = this.$el.outerHeight();
if (this.get('height') != editor_height) {
$(document.body).css('padding-top', editor_height);
this.set('height', editor_height);
}
},
edit: function () {
this.$buttons.edit.prop('disabled', true);
@@ -456,7 +467,7 @@
this.$('#website-top-edit').show();
$('.css_non_editable_mode_hidden').removeClass("css_non_editable_mode_hidden");
this.rte.start_edition();
this.rte.start_edition().then(this.check_height.bind(this));
},
rte_changed: function () {
this.$buttons.save.prop('disabled', false);
@@ -139,6 +139,10 @@
$(document).on('click', '.dropdown-submenu a[tabindex]', function (e) {
e.preventDefault();
});
this.getParent().on('change:height', this, function (editor) {
self.$el.css('top', editor.get('height'));
});
},
fetch_snippet_templates: function () {
var self = this;
+1 -1
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
import test_views, test_converter, test_requests
import test_views, test_converter, test_requests, test_ui
checks = [ test_views, test_converter, ]
+44 -14
View File
@@ -63,20 +63,6 @@ class TestConvertBack(common.TransactionCase):
self.field_roundtrip('char', "ⒸⓄⓇⒼⒺ")
def test_m2o(self):
Sub = self.registry('website.converter.test.sub')
sub = partial(Sub.create, self.cr, self.uid)
ids = [
sub({'name': "Foo"}),
sub({'name': "Bar"}),
sub({'name': "Baz"}),
]
self.field_rountrip_result(
'many2one',
ids[2],
ids[2])
def test_selection(self):
self.field_roundtrip('selection', 3)
@@ -104,3 +90,47 @@ class TestConvertBack(common.TransactionCase):
You never know
You never know until you go
""")
def test_m2o(self):
""" the M2O field conversion (from html) is markedly different from
others as it directly writes into the m2o and returns nothing at all.
"""
model = 'website.converter.test'
field = 'many2one'
Sub = self.registry('website.converter.test.sub')
sub_id = Sub.create(self.cr, self.uid, {'name': "Foo"})
Model = self.registry(model)
id = Model.create(self.cr, self.uid, {field: sub_id})
[record] = Model.browse(self.cr, self.uid, [id])
e = document.createElement('span')
field_value = 'record.%s' % field
e.setAttribute('t-field', field_value)
rendered = self.registry('website.qweb').render_tag_field(
e, {'field': field_value}, '', ir_qweb.QWebContext(self.cr, self.uid, {
'record': record,
}))
element = html.fromstring(rendered, parser=html.HTMLParser(encoding='utf-8'))
# emulate edition
element.text = "New content"
column = Model._all_columns[field].column
converter = self.registry('website.qweb').get_converter_for(
element.get('data-oe-type'))
value_back = converter.from_html(
self.cr, self.uid, model, column, element)
self.assertIsNone(
value_back, "the m2o converter should return None to avoid spurious"
" or useless writes on the parent record")
self.assertEqual(
Sub.browse(self.cr, self.uid, sub_id).name,
"New content",
"element edition should have been written directly to the m2o record"
)
+98
View File
@@ -0,0 +1,98 @@
import json
import subprocess
import unittest
import os
import time
ROOT = os.path.join(os.path.dirname(__file__), 'ui_suite')
__all__ = ['load_tests', 'WebsiteUiSuite']
def _exc_info_to_string(err, test):
return err
class WebsiteUiTest(unittest.TestCase):
def __init__(self, name):
self.name = name
def shortDescription(self):
return None
def __str__(self):
return self.name
class WebsiteUiSuite(unittest.TestSuite):
def __init__(self, testfile, timeout=5000):
self.testfile = testfile
self.timeout = timeout
self._test = None
def __iter__(self):
return iter([self])
def run(self, result):
# Test if phantom is correctly installed
try:
subprocess.call(['phantomjs', '-v'],
stdout=open(os.devnull, 'w'),
stderr=subprocess.STDOUT)
except OSError:
test = WebsiteUiTest('UI Tests')
result.startTest(test)
result.addSkip(test, "phantomjs command not found")
result.stopTest(test)
return
result._exc_info_to_string = _exc_info_to_string
try:
self._run(result)
finally:
del result._exc_info_to_string
def _run(self, result):
phantom = subprocess.Popen([
'phantomjs',
os.path.join(ROOT, self.testfile),
json.dumps({ 'timeout': self.timeout })
], stdout=subprocess.PIPE)
self._test = WebsiteUiTest(self.testfile)
try:
while True:
line = phantom.stdout.readline()
if line:
# the runner expects only one result per test
self.process(line, result)
result.stopTest(self._test)
break
else:
time.sleep(0.1)
finally:
# kill phantomjs if phantom.exit() wasn't called in the test
if phantom.poll() is None:
phantom.terminate()
def process(self, line, result):
# Test protocol
# -------------
# use console.log in phantomjs to output test results using the following format:
# - for a success: { "event": "success" }
# - for a failure: { "event": "failure", "message": "Failure description" }
# any other message is treated as an error
result.startTest(self._test)
try:
args = json.loads(line)
event = args.get('event')
message = args.get('message', "")
if event == 'success':
result.addSuccess(self._test)
elif event == 'failure':
result.addFailure(self._test, message)
else:
result.addError(self._test, "Unexpected message: %s" % line)
except ValueError:
result.addError(self._test, "Unexpected message: %s" % line)
def load_tests(loader, base, _):
base.addTest(WebsiteUiSuite('sample_test.js'))
return base
@@ -0,0 +1,4 @@
console.log('{ "event": "success" }');
// For a failure:
// console.log('{ "event": "failure", "message": "The test failed" }');
phantom.exit();
+1 -1
View File
@@ -597,7 +597,7 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
<template id="company_description" name="Company Description">
<address>
<div t-field="res_company.partner_id">Name</div>
<div t-field="res_company.name">Name</div>
<br />
<div>&amp;#x2706; <span t-field="res_company.phone"></span></div>
<div class="fa fa-envelope" t-field="res_company.email"></div>
@@ -52,12 +52,5 @@
</record>
<!-- Mail group for the company's jobs -->
<record id="blog_category_2" model="blog.category">
<field name="name">Jobs</field>
<field name="description">Job Announces</field>
</record>
</data>
</openerp>