1 # -*- coding: utf-8 -*-
3 Website-context rendering needs to add some metadata to rendered fields,
4 as well as render a few fields differently.
6 Also, adds methods to convert values back to openerp models.
16 from lxml import etree, html
17 from PIL import Image as I
19 from openerp.osv import orm, fields
20 from openerp.tools import ustr
22 REMOTE_CONNECTION_TIMEOUT = 2.5
24 logger = logging.getLogger(__name__)
26 class QWeb(orm.AbstractModel):
27 """ QWeb object for rendering stuff in the website context
29 _name = 'website.qweb'
32 def get_converter_for(self, field_type):
34 'website.qweb.field.' + field_type,
35 self.pool['website.qweb.field'])
37 class Field(orm.AbstractModel):
38 _name = 'website.qweb.field'
39 _inherit = 'ir.qweb.field'
41 def attributes(self, cr, uid, field_name, record, options,
42 source_element, g_att, t_att, qweb_context, context=None):
43 column = record._model._all_columns[field_name].column
44 return itertools.chain(
45 super(Field, self).attributes(cr, uid, field_name, record, options,
46 source_element, g_att, t_att,
47 qweb_context, context=context),
48 [('data-oe-translate', 1 if column.translate else 0)]
51 def value_from_string(self, value):
54 def from_html(self, cr, uid, model, column, element, context=None):
55 return self.value_from_string(element.text_content().strip())
57 def qweb_object(self):
58 return self.pool['website.qweb']
60 class Integer(orm.AbstractModel):
61 _name = 'website.qweb.field.integer'
62 _inherit = ['website.qweb.field']
64 value_from_string = int
66 class Float(orm.AbstractModel):
67 _name = 'website.qweb.field.float'
68 _inherit = ['website.qweb.field', 'ir.qweb.field.float']
70 def from_html(self, cr, uid, model, column, element, context=None):
71 lang = self.user_lang(cr, uid, context=context)
73 value = element.text_content().strip()
75 return float(value.replace(lang.thousands_sep, '')
76 .replace(lang.decimal_point, '.'))
78 class Date(orm.AbstractModel):
79 _name = 'website.qweb.field.date'
80 _inherit = ['website.qweb.field', 'ir.qweb.field.date']
82 def from_html(self, cr, uid, model, column, element, context=None):
83 raise NotImplementedError("Can not parse and save localized dates")
85 class DateTime(orm.AbstractModel):
86 _name = 'website.qweb.field.datetime'
87 _inherit = ['website.qweb.field', 'ir.qweb.field.datetime']
89 def from_html(self, cr, uid, model, column, element, context=None):
90 raise NotImplementedError("Can not parse and save localized datetimes")
92 class Text(orm.AbstractModel):
93 _name = 'website.qweb.field.text'
94 _inherit = ['website.qweb.field', 'ir.qweb.field.text']
96 def from_html(self, cr, uid, model, column, element, context=None):
97 return element.text_content()
99 class Selection(orm.AbstractModel):
100 _name = 'website.qweb.field.selection'
101 _inherit = ['website.qweb.field', 'ir.qweb.field.selection']
103 def from_html(self, cr, uid, model, column, element, context=None):
104 value = element.text_content().strip()
105 selection = column.reify(cr, uid, model, column, context=context)
106 for k, v in selection:
107 if isinstance(v, str):
112 raise ValueError(u"No value found for label %s in selection %s" % (
115 class ManyToOne(orm.AbstractModel):
116 _name = 'website.qweb.field.many2one'
117 _inherit = ['website.qweb.field', 'ir.qweb.field.many2one']
119 def from_html(self, cr, uid, model, column, element, context=None):
120 # 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?
121 matches = self.pool[column._obj].name_search(
122 cr, uid, name=element.text_content().strip(), context=context)
123 # FIXME: no match? More than 1 match?
124 assert len(matches) == 1
127 class HTML(orm.AbstractModel):
128 _name = 'website.qweb.field.html'
129 _inherit = ['website.qweb.field', 'ir.qweb.field.html']
131 def from_html(self, cr, uid, model, column, element, context=None):
133 if element.text: content.append(element.text)
134 content.extend(html.tostring(child)
135 for child in element.iterchildren(tag=etree.Element))
136 return '\n'.join(content)
139 class Image(orm.AbstractModel):
144 set as attribute on the generated <img> tag
146 _name = 'website.qweb.field.image'
147 _inherit = ['website.qweb.field', 'ir.qweb.field.image']
149 def to_html(self, cr, uid, field_name, record, options,
150 source_element, t_att, g_att, qweb_context, context=None):
151 assert source_element.nodeName != 'img',\
152 "Oddly enough, the root tag of an image field can not be img. " \
153 "That is because the image goes into the tag, or it gets the " \
156 return super(Image, self).to_html(
157 cr, uid, field_name, record, options,
158 source_element, t_att, g_att, qweb_context, context=context)
160 def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None):
162 if 'class' in options:
163 cls = ' class="%s"' % werkzeug.utils.escape(options['class'])
165 return '<img%s src="/website/image?model=%s&field=%s&id=%s"/>' % (
166 cls, record._model._name, field_name, record.id)
168 def from_html(self, cr, uid, model, column, element, context=None):
169 url = element.find('img').get('src')
171 m = re.match(r'^/website/attachment/(\d+)$', url)
173 attachment = self.pool['ir.attachment'].browse(
174 cr, uid, int(m.group(1)), context=context)
175 return attachment.datas
179 # should probably remove remote URLs entirely:
180 # * in fields, downloading them without blowing up the server is a
182 # * in views, may trigger mixed content warnings if HTTPS CMS
183 # linking to HTTP images
184 # implement drag & drop image upload to mitigate?
186 req = urllib2.urlopen(url, timeout=REMOTE_CONNECTION_TIMEOUT)
187 # PIL needs a seekable file-like image, urllib result is not seekable
188 image = I.open(cStringIO.StringIO(req.read()))
189 # force a complete load of the image data to validate it
192 logger.exception("Failed to load remote image %r", url)
195 # don't use original data in case weird stuff was smuggled in, with
196 # luck PIL will remove some of it?
197 out = cStringIO.StringIO()
198 image.save(out, image.format)
199 return out.getvalue().encode('base64')
201 class Monetary(orm.AbstractModel):
202 _name = 'website.qweb.field.monetary'
203 _inherit = ['website.qweb.field', 'ir.qweb.field.monetary']
205 def from_html(self, cr, uid, model, column, element, context=None):
206 lang = self.user_lang(cr, uid, context=context)
208 value = element.find('span').text.strip()
210 return float(value.replace(lang.thousands_sep, '')
211 .replace(lang.decimal_point, '.'))