diff options
| author | Christophe Matthieu <chm@odoo.com> | 2016-05-02 16:03:48 +0200 |
|---|---|---|
| committer | Christophe Matthieu <chm@odoo.com> | 2016-06-14 09:46:25 +0200 |
| commit | 7ede9bcb2de9d52994b3a6fcb84edc3f81d60284 (patch) | |
| tree | bf91070143134b5b5ef31334c913bf52089c6710 /addons/web_editor/models | |
| parent | 3ebf46fcc0ca048fcc1d433a073b94e64fb7049c (diff) | |
[IMP] base ir.qweb: compile template (and use ast) to improve rendering speed
* The compiled templates are cached per user, lang, inherit context values
* ir.ui.fields: attributes method return an dict, and record_to_html return only the content value of the field
* all rendered text use build_text and all attributes use build_attribute
* t-esc-options is removed and replace by format_value method
* AssetsBundle receive the list files and remains
Diffstat (limited to 'addons/web_editor/models')
| -rw-r--r-- | addons/web_editor/models/ir_qweb.py | 220 | ||||
| -rw-r--r-- | addons/web_editor/models/ir_ui_view.py | 13 | ||||
| -rw-r--r-- | addons/web_editor/models/test_models.py | 2 |
3 files changed, 107 insertions, 128 deletions
diff --git a/addons/web_editor/models/ir_qweb.py b/addons/web_editor/models/ir_qweb.py index f6758536780..b6082bc0597 100644 --- a/addons/web_editor/models/ir_qweb.py +++ b/addons/web_editor/models/ir_qweb.py @@ -6,6 +6,7 @@ as well as render a few fields differently. Also, adds methods to convert values back to openerp models. """ +import ast import cStringIO import datetime import itertools @@ -23,6 +24,8 @@ from lxml import etree, html from PIL import Image as I import openerp.modules +from odoo import api + import openerp from openerp.osv import orm, fields from openerp.tools import ustr, DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT @@ -43,20 +46,26 @@ class QWeb(orm.AbstractModel): re_remove_spaces = re.compile('\s+') - def render_tag_snippet(self, element, template_attributes, generated_attributes, qwebcontext): - cr = qwebcontext['request'].cr - uid = qwebcontext['request'].uid - view_reg = self.pool['ir.ui.view'] - page_xmlid = self.eval_format(template_attributes["snippet"], qwebcontext) - view = view_reg.browse(cr, uid, view_reg.search(cr, uid, [('key', '=', page_xmlid)])) - generated_attributes += " " + "".join([ - 'data-oe-name="%s"' % view.name, - 'data-oe-type="snippet"', - 'data-oe-thumbnail="%s"' % template_attributes.get('thumbnail', "oe-thumbnail")]) + def _compile_directive_snippet(self, el, ast_calls): + el.set('t-call', el.attrib.pop('t-snippet')) + qwebcontext = ir_qweb.QWebContext(self.env, {}) + name = self.env['ir.ui.view'].search([('key', '=', el.attrib.get('t-call'))]).display_name + thumbnail = el.attrib.pop('t-thumbnail', "oe-thumbnail") + div = u'<div name="%s" data-oe-type="snippet" data-oe-thumbnail="%s">' % (escape(ir_qweb.unicodifier(name)), escape(ir_qweb.unicodifier(thumbnail))) + return [self._append(ast.Str(div))] + self._compile_node(el, ast_calls) + [self._append(ast.Str(u'</div>'))] + + def _compile_directive_tag(self, el, ast_calls): + if el.get('t-placeholder'): + el.set('t-att-placeholder', el.attrib.pop('t-placeholder')) + return super(QWeb, self)._compile_directive_tag(el, ast_calls) - template_attributes['call'] = template_attributes['snippet'] - return self.render_tag_call(element, template_attributes, generated_attributes, qwebcontext) + def _directives_eval_order(self): + directives = super(QWeb, self)._directives_eval_order() + directives.insert(directives.index('call'), 'snippet') + return directives + def _nondirectives_ignore(self): + return {'t-thumbnail'} | super(QWeb, self)._nondirectives_ignore() #------------------------------------------------------ # QWeb fields @@ -67,36 +76,28 @@ class Field(orm.AbstractModel): _name = 'ir.qweb.field' _inherit = 'ir.qweb.field' - def attributes(self, cr, uid, field_name, record, options, - source_element, g_att, t_att, qweb_context, context=None): - if options is None: - options = {} - field = record._model._fields[field_name] - attrs = [] + @api.model + def attributes(self, record, field_name, options, qwebcontext): + attrs = super(Field, self).attributes(record, field_name, options, qwebcontext) + field = record._fields[field_name] - placeholder = options.get('placeholder') \ - or source_element.get('placeholder') \ - or getattr(field, 'placeholder', None) + placeholder = options.get('placeholder') or getattr(field, 'placeholder', None) if placeholder: - attrs.append(('placeholder', placeholder)) + attrs['placeholder'] = placeholder - if context and context.get('edit_translations') and context.get('translatable') and field.type in ('char', 'text') and field.translate: + if options['translate'] and field.type in ('char', 'text'): name = "%s,%s" % (record._model._name, field_name) - domain = [('name', '=', name), ('res_id', '=', record.id), ('type', '=', 'model'), ('lang', '=', context.get('lang'))] + domain = [('name', '=', name), ('res_id', '=', record.id), ('type', '=', 'model'), ('lang', '=', qwebcontext.env.context.get('lang'))] translation = record.env['ir.translation'].search(domain, limit=1) - attrs.append(('data-oe-translation-state', translation and translation.state or 'to_translate')) + attrs['data-oe-translation-state'] = translation and translation.state or 'to_translate' - return itertools.chain( - super(Field, self).attributes(cr, uid, field_name, record, options, - source_element, g_att, t_att, - qweb_context, context=context), - attrs - ) + return attrs def value_from_string(self, value): return value - def from_html(self, cr, uid, model, field, element, context=None): + @api.model + def from_html(self, model, field, element): return self.value_from_string(element.text_content().strip()) @@ -111,8 +112,9 @@ class Float(orm.AbstractModel): _name = 'ir.qweb.field.float' _inherit = 'ir.qweb.field.float' - def from_html(self, cr, uid, model, field, element, context=None): - lang = self.user_lang(cr, uid, context=context) + @api.model + def from_html(self, model, field, element): + lang = self.user_lang() value = element.text_content().strip() @@ -124,32 +126,26 @@ class ManyToOne(orm.AbstractModel): _name = 'ir.qweb.field.many2one' _inherit = 'ir.qweb.field.many2one' - def attributes(self, cr, uid, field_name, record, options, - source_element, g_att, t_att, qweb_context, - context=None): - attrs = super(ManyToOne, self).attributes( - cr, uid, field_name, record, options, source_element, g_att, t_att, - qweb_context, context=context) + @api.model + def attributes(self, record, field_name, options, qwebcontext): + attrs = super(ManyToOne, self).attributes(record, field_name, options, qwebcontext) many2one = getattr(record, field_name) if many2one: - data = [('data-oe-many2one-id', many2one.id), - ('data-oe-many2one-model', many2one._name)] - return itertools.chain(attrs, data) - else: - return attrs + attrs['data-oe-many2one-id'] = many2one.id + attrs['data-oe-many2one-model'] = many2one._name + return attrs - def from_html(self, cr, uid, model, field, element, context=None): - Model = self.pool[element.get('data-oe-model')] - M2O = self.pool[field.comodel_name] - field_name = element.get('data-oe-field') + @api.model + def from_html(self, model, field, element): + Model = self.env[element.get('data-oe-model')] id = int(element.get('data-oe-id')) + M2O = self.env[field.comodel_name] + field_name = element.get('data-oe-field') many2one_id = int(element.get('data-oe-many2one-id')) - - if many2one_id and M2O.exists(cr, uid, [many2one_id]): + record = many2one_id and M2O.browse(many2one_id) + if record and record.exists(): # save the new id of the many2one - Model.write(cr, uid, [id], { - field_name: many2one_id - }, context=context) + Model.browse(id).write({field_name: many2one_id}) # not necessary, but might as well be explicit about it return None @@ -159,22 +155,18 @@ class Contact(orm.AbstractModel): _name = 'ir.qweb.field.contact' _inherit = 'ir.qweb.field.contact' - def attributes(self, cr, uid, field_name, record, options, - source_element, g_att, t_att, qweb_context, - context=None): - attrs = super(Contact, self).attributes( - cr, uid, field_name, record, options, source_element, g_att, t_att, - qweb_context, context=context) - if getattr(record, field_name): - return itertools.chain(attrs, [('data-oe-contact-options', json.dumps(options))]) - else: - return attrs + @api.model + def attributes(self, record, field_name, options, qwebcontext): + attrs = super(Contact, self).attributes(record, field_name, options, qwebcontext) + attrs['data-oe-contact-options'] = json.dumps(options) + return attrs # helper to call the rendering of contact field - def get_record_to_html(self, cr, uid, ids, options=None, context=None): - node = self.record_to_html(cr, uid, 'record', { - 'record': self.pool['res.partner'].browse(cr, uid, ids[0], context=context)}, - options=options, context=context) + @api.model + def get_record_to_html(self, ids, options=None): + node = self.record_to_html('record', { + 'record': self.env['res.partner'].browse(ids[0])}, + options=options) return node and node.__html__() @@ -193,15 +185,14 @@ class Date(orm.AbstractModel): _name = 'ir.qweb.field.date' _inherit = 'ir.qweb.field.date' - def attributes(self, cr, uid, field_name, record, options, - source_element, g_att, t_att, qweb_context, - context=None): - attrs = super(Date, self).attributes( - cr, uid, field_name, record, options, source_element, g_att, t_att, - qweb_context, context=None) - return itertools.chain(attrs, [('data-oe-original', record[field_name])]) + @api.model + def attributes(self, record, field_name, options, qwebcontext): + attrs = super(Date, self).attributes(record, field_name, options, qwebcontext) + attrs['data-oe-original'] = record[field_name] + return attrs - def from_html(self, cr, uid, model, field, element, context=None): + @api.model + def from_html(self, model, field, element): value = element.text_content().strip() if not value: return False @@ -214,9 +205,9 @@ class DateTime(orm.AbstractModel): _name = 'ir.qweb.field.datetime' _inherit = 'ir.qweb.field.datetime' - def attributes(self, cr, uid, field_name, record, options, - source_element, g_att, t_att, qweb_context, - context=None): + @api.model + def attributes(self, record, field_name, options, qwebcontext): + attrs = super(DateTime, self).attributes(record, field_name, options, qwebcontext) value = record[field_name] if isinstance(value, basestring): value = datetime.datetime.strptime( @@ -224,19 +215,13 @@ class DateTime(orm.AbstractModel): if value: # convert from UTC (server timezone) to user timezone value = fields.datetime.context_timestamp( - cr, uid, timestamp=value, context=context) + self._cr, self._uid, timestamp=value, context=qwebcontext.context) value = value.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + attrs['data-oe-original'] = value + return attrs - attrs = super(DateTime, self).attributes( - cr, uid, field_name, record, options, source_element, g_att, t_att, - qweb_context, context=None) - return itertools.chain(attrs, [ - ('data-oe-original', value) - ]) - - def from_html(self, cr, uid, model, field, element, context=None): - if context is None: - context = {} + @api.model + def from_html(self, model, field, element): value = element.text_content().strip() if not value: return False @@ -245,8 +230,7 @@ class DateTime(orm.AbstractModel): dt = parser.parse(value) # convert back from user's timezone to UTC - tz_name = context.get('tz') \ - or self.pool['res.users'].read(cr, openerp.SUPERUSER_ID, uid, ['tz'], context=context)['tz'] + tz_name = self.env.context.get('tz') or self.env.user.tz if tz_name: try: user_tz = pytz.timezone(tz_name) @@ -268,7 +252,8 @@ class Text(orm.AbstractModel): _name = 'ir.qweb.field.text' _inherit = 'ir.qweb.field.text' - def from_html(self, cr, uid, model, field, element, context=None): + @api.model + def from_html(self, model, field, element): return html_to_text(element) @@ -276,10 +261,10 @@ class Selection(orm.AbstractModel): _name = 'ir.qweb.field.selection' _inherit = 'ir.qweb.field.selection' - def from_html(self, cr, uid, model, field, element, context=None): - record = self.browse(cr, uid, [], context=context) + @api.model + def from_html(self, model, field, element): value = element.text_content().strip() - selection = field.get_description(record.env)['selection'] + selection = field.get_description(self.env)['selection'] for k, v in selection: if isinstance(v, str): v = ustr(v) @@ -294,7 +279,8 @@ class HTML(orm.AbstractModel): _name = 'ir.qweb.field.html' _inherit = 'ir.qweb.field.html' - def from_html(self, cr, uid, model, field, element, context=None): + @api.model + def from_html(self, model, field, element): content = [] if element.text: content.append(element.text) @@ -313,20 +299,13 @@ class Image(orm.AbstractModel): _name = 'ir.qweb.field.image' _inherit = 'ir.qweb.field.image' - def to_html(self, cr, uid, field_name, record, options, - source_element, t_att, g_att, qweb_context, context=None): - assert source_element.tag != 'img',\ + @api.model + def record_to_html(self, record, field_name, options, qwebcontext=None): + assert options['tagName'] != 'img',\ "Oddly enough, the root tag of an image field can not be img. " \ "That is because the image goes into the tag, or it gets the " \ "hose again." - return super(Image, self).to_html( - cr, uid, field_name, record, options, - source_element, t_att, g_att, qweb_context, context=context) - - def record_to_html(self, cr, uid, field_name, record, options=None, context=None): - if options is None: - options = {} aclasses = ['img', 'img-responsive'] + options.get('class', '').split() classes = ' '.join(itertools.imap(escape, aclasses)) @@ -350,11 +329,12 @@ class Image(orm.AbstractModel): img = '<img class="%s" src="%s" style="%s"%s/>' % \ (classes, src, options.get('style', ''), ' alt="%s"' % alt if alt else '') - return ir_qweb.HTMLSafe(img) + return ir_qweb.unicodifier(img) local_url_re = re.compile(r'^/(?P<module>[^]]+)/static/(?P<rest>.+)$') - def from_html(self, cr, uid, model, field, element, context=None): + @api.model + def from_html(self, model, field, element): url = element.find('img').get('src') url_object = urlparse.urlsplit(url) @@ -370,7 +350,7 @@ class Image(orm.AbstractModel): model = query.get('model', fragments[3]) oid = query.get('id', fragments[4].split('_')[0]) field = query.get('field', fragments[5]) - item = self.pool[model].browse(cr, uid, int(oid), context=context) + item = self.env[model].browse(int(oid)) return item[field] if self.local_url_re.match(url_object.path): @@ -432,8 +412,9 @@ class Monetary(orm.AbstractModel): _name = 'ir.qweb.field.monetary' _inherit = 'ir.qweb.field.monetary' - def from_html(self, cr, uid, model, field, element, context=None): - lang = self.user_lang(cr, uid, context=context) + @api.model + def from_html(self, model, field, element): + lang = self.user_lang() value = element.find('span').text.strip() @@ -445,15 +426,14 @@ class Duration(orm.AbstractModel): _name = 'ir.qweb.field.duration' _inherit = 'ir.qweb.field.duration' - def attributes(self, cr, uid, field_name, record, options, - source_element, g_att, t_att, qweb_context, - context=None): - attrs = super(Duration, self).attributes( - cr, uid, field_name, record, options, source_element, g_att, t_att, - qweb_context, context=None) - return itertools.chain(attrs, [('data-oe-original', record[field_name])]) + @api.model + def attributes(self, record, field_name, options, qwebcontext): + attrs = super(Duration, self).attributes(record, field_name, options, qwebcontext) + attrs['data-oe-original'] = record[field_name] + return attrs - def from_html(self, cr, uid, model, field, element, context=None): + @api.model + def from_html(self, model, field, element): value = element.text_content().strip() # non-localized value diff --git a/addons/web_editor/models/ir_ui_view.py b/addons/web_editor/models/ir_ui_view.py index 01082efb37b..762312bc28f 100644 --- a/addons/web_editor/models/ir_ui_view.py +++ b/addons/web_editor/models/ir_ui_view.py @@ -37,18 +37,17 @@ class view(osv.osv): def extract_embedded_fields(self, cr, uid, arch, context=None): return arch.xpath('//*[@data-oe-model != "ir.ui.view"]') - def save_embedded_field(self, cr, uid, el, context=None): - Model = self.pool[el.get('data-oe-model')] + @api.model + def save_embedded_field(self, el): + Model = self.env[el.get('data-oe-model')] field = el.get('data-oe-field') - converter = self.pool['ir.qweb'].get_converter_for(el.get('data-oe-type')) - value = converter.from_html(cr, uid, Model, Model._fields[field], el) + converter = self.env['ir.qweb'].get_field_for(el.get('data-oe-type')) + value = converter.from_html(Model, Model._fields[field], el) if value is not None: # TODO: batch writes? - Model.write(cr, uid, [int(el.get('data-oe-id'))], { - field: value - }, context=context) + Model.browse(int(el.get('data-oe-id'))).write({field: value}) def _pretty_arch(self, arch): # remove_blank_string does not seem to work on HTMLParser, and diff --git a/addons/web_editor/models/test_models.py b/addons/web_editor/models/test_models.py index 100b49b6448..716e42a4dc3 100644 --- a/addons/web_editor/models/test_models.py +++ b/addons/web_editor/models/test_models.py @@ -21,7 +21,7 @@ class test_converter(orm.Model): (1, "réponse A"), (2, "réponse B"), (3, "réponse C"), - (4, "réponse D"), + (4, "réponse <D>"), ]), 'selection_str': fields.selection([ ('A', "Qu'il n'est pas arrivé à Toronto"), |
