Files
odoo_source/addons/website/models/ir_qweb.py
T

212 lines
7.6 KiB
Python

# -*- coding: utf-8 -*-
"""
Website-context rendering needs to add some metadata to rendered fields,
as well as render a few fields differently.
Also, adds methods to convert values back to openerp models.
"""
import cStringIO
import itertools
import logging
import re
import urllib2
import werkzeug.utils
from lxml import etree, html
from PIL import Image as I
from openerp.osv import orm, fields
from openerp.tools import ustr
REMOTE_CONNECTION_TIMEOUT = 2.5
logger = logging.getLogger(__name__)
class QWeb(orm.AbstractModel):
""" QWeb object for rendering stuff in the website context
"""
_name = 'website.qweb'
_inherit = 'ir.qweb'
def get_converter_for(self, field_type):
return self.pool.get(
'website.qweb.field.' + field_type,
self.pool['website.qweb.field'])
class Field(orm.AbstractModel):
_name = 'website.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):
column = record._model._all_columns[field_name].column
return itertools.chain(
super(Field, self).attributes(cr, uid, field_name, record, options,
source_element, g_att, t_att,
qweb_context, context=context),
[('data-oe-translate', 1 if column.translate else 0)]
)
def value_from_string(self, value):
return value
def from_html(self, cr, uid, model, column, element, context=None):
return self.value_from_string(element.text_content().strip())
def qweb_object(self):
return self.pool['website.qweb']
class Integer(orm.AbstractModel):
_name = 'website.qweb.field.integer'
_inherit = ['website.qweb.field']
value_from_string = int
class Float(orm.AbstractModel):
_name = 'website.qweb.field.float'
_inherit = ['website.qweb.field', 'ir.qweb.field.float']
def from_html(self, cr, uid, model, column, element, context=None):
lang = self.user_lang(cr, uid, context=context)
value = element.text_content().strip()
return float(value.replace(lang.thousands_sep, '')
.replace(lang.decimal_point, '.'))
class Date(orm.AbstractModel):
_name = 'website.qweb.field.date'
_inherit = ['website.qweb.field', 'ir.qweb.field.date']
def from_html(self, cr, uid, model, column, element, context=None):
raise NotImplementedError("Can not parse and save localized dates")
class DateTime(orm.AbstractModel):
_name = 'website.qweb.field.datetime'
_inherit = ['website.qweb.field', 'ir.qweb.field.datetime']
def from_html(self, cr, uid, model, column, element, context=None):
raise NotImplementedError("Can not parse and save localized datetimes")
class Text(orm.AbstractModel):
_name = 'website.qweb.field.text'
_inherit = ['website.qweb.field', 'ir.qweb.field.text']
def from_html(self, cr, uid, model, column, element, context=None):
return element.text_content()
class Selection(orm.AbstractModel):
_name = 'website.qweb.field.selection'
_inherit = ['website.qweb.field', 'ir.qweb.field.selection']
def from_html(self, cr, uid, model, column, element, context=None):
value = element.text_content().strip()
selection = column.reify(cr, uid, model, column, context=context)
for k, v in selection:
if isinstance(v, str):
v = ustr(v)
if value == v:
return k
raise ValueError(u"No value found for label %s in selection %s" % (
value, selection))
class ManyToOne(orm.AbstractModel):
_name = 'website.qweb.field.many2one'
_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]
class HTML(orm.AbstractModel):
_name = 'website.qweb.field.html'
_inherit = ['website.qweb.field', 'ir.qweb.field.html']
def from_html(self, cr, uid, model, column, element, context=None):
content = []
if element.text: content.append(element.text)
content.extend(html.tostring(child)
for child in element.iterchildren(tag=etree.Element))
return '\n'.join(content)
class Image(orm.AbstractModel):
"""
Widget options:
``class``
set as attribute on the generated <img> tag
"""
_name = 'website.qweb.field.image'
_inherit = ['website.qweb.field', '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.nodeName != '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, column, options=None, context=None):
cls = ''
if 'class' in options:
cls = ' class="%s"' % werkzeug.utils.escape(options['class'])
return '<img%s src="/website/image?model=%s&field=%s&id=%s"/>' % (
cls, record._model._name, field_name, record.id)
def from_html(self, cr, uid, model, column, element, context=None):
url = element.find('img').get('src')
m = re.match(r'^/website/attachment/(\d+)$', url)
if m:
attachment = self.pool['ir.attachment'].browse(
cr, uid, int(m.group(1)), context=context)
return attachment.datas
# remote URL?
try:
# should probably remove remote URLs entirely:
# * in fields, downloading them without blowing up the server is a
# challenge
# * in views, may trigger mixed content warnings if HTTPS CMS
# linking to HTTP images
# implement drag & drop image upload to mitigate?
req = urllib2.urlopen(url, timeout=REMOTE_CONNECTION_TIMEOUT)
# PIL needs a seekable file-like image, urllib result is not seekable
image = I.open(cStringIO.StringIO(req.read()))
# force a complete load of the image data to validate it
image.load()
except Exception:
logger.exception("Failed to load remote image %r", url)
return False
# don't use original data in case weird stuff was smuggled in, with
# luck PIL will remove some of it?
out = cStringIO.StringIO()
image.save(out, image.format)
return out.getvalue().encode('base64')
class Monetary(orm.AbstractModel):
_name = 'website.qweb.field.monetary'
_inherit = ['website.qweb.field', 'ir.qweb.field.monetary']
def from_html(self, cr, uid, model, column, element, context=None):
lang = self.user_lang(cr, uid, context=context)
value = element.find('span').text.strip()
return float(value.replace(lang.thousands_sep, '')
.replace(lang.decimal_point, '.'))