2013-10-08 09:22:33 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2013-10-08 09:26:12 +00:00
|
|
|
"""
|
|
|
|
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.
|
|
|
|
"""
|
|
|
|
|
2013-10-10 10:30:05 +00:00
|
|
|
import cStringIO
|
2013-10-23 14:57:29 +00:00
|
|
|
import datetime
|
2013-10-08 09:22:33 +00:00
|
|
|
import itertools
|
2013-10-10 10:30:05 +00:00
|
|
|
import logging
|
2013-11-27 10:46:38 +00:00
|
|
|
import os
|
2013-10-10 10:30:05 +00:00
|
|
|
import urllib2
|
2013-10-24 10:34:01 +00:00
|
|
|
import urlparse
|
2013-11-27 10:46:38 +00:00
|
|
|
import re
|
2013-10-08 09:22:33 +00:00
|
|
|
|
2014-06-18 14:09:02 +00:00
|
|
|
import pytz
|
2014-05-23 13:03:23 +00:00
|
|
|
import werkzeug.urls
|
2013-10-08 12:04:56 +00:00
|
|
|
import werkzeug.utils
|
2013-10-23 14:57:29 +00:00
|
|
|
from dateutil import parser
|
2013-10-09 13:31:12 +00:00
|
|
|
from lxml import etree, html
|
2013-10-10 10:30:05 +00:00
|
|
|
from PIL import Image as I
|
2013-11-27 10:46:38 +00:00
|
|
|
import openerp.modules
|
2013-10-08 12:04:56 +00:00
|
|
|
|
2013-12-06 13:30:03 +00:00
|
|
|
import openerp
|
2013-10-08 09:22:33 +00:00
|
|
|
from openerp.osv import orm, fields
|
2013-10-23 14:57:29 +00:00
|
|
|
from openerp.tools import ustr, DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
2014-06-10 16:46:40 +00:00
|
|
|
from openerp.tools import html_escape as escape
|
2013-11-12 14:44:42 +00:00
|
|
|
from openerp.addons.web.http import request
|
2013-12-16 09:23:43 +00:00
|
|
|
from openerp.addons.base.ir import ir_qweb
|
2013-10-08 09:22:33 +00:00
|
|
|
|
2013-10-10 12:26:52 +00:00
|
|
|
REMOTE_CONNECTION_TIMEOUT = 2.5
|
|
|
|
|
2013-10-10 10:30:05 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2013-10-08 09:22:33 +00:00
|
|
|
class QWeb(orm.AbstractModel):
|
|
|
|
""" QWeb object for rendering stuff in the website context
|
|
|
|
"""
|
|
|
|
_name = 'website.qweb'
|
|
|
|
_inherit = 'ir.qweb'
|
|
|
|
|
2013-11-12 14:44:42 +00:00
|
|
|
URL_ATTRS = {
|
|
|
|
'form': 'action',
|
|
|
|
'a': 'href',
|
|
|
|
}
|
|
|
|
|
2014-01-28 20:14:17 +00:00
|
|
|
def add_template(self, qcontext, name, node):
|
2013-11-12 14:44:42 +00:00
|
|
|
# preprocessing for multilang static urls
|
2014-01-28 20:14:17 +00:00
|
|
|
if request.website:
|
2014-07-02 14:01:36 +00:00
|
|
|
for tag, attr in self.URL_ATTRS.iteritems():
|
|
|
|
for e in node.iterdescendants(tag=tag):
|
|
|
|
url = e.get(attr)
|
2013-11-12 14:44:42 +00:00
|
|
|
if url:
|
2014-07-02 14:01:36 +00:00
|
|
|
e.set(attr, qcontext.get('url_for')(url))
|
2014-01-28 20:14:17 +00:00
|
|
|
super(QWeb, self).add_template(qcontext, name, node)
|
|
|
|
|
|
|
|
def render_att_att(self, element, attribute_name, attribute_value, qwebcontext):
|
2014-09-11 06:38:08 +00:00
|
|
|
URL_ATTRS = self.URL_ATTRS.get(element.tag)
|
|
|
|
is_website = request.website
|
|
|
|
for att, val in super(QWeb, self).render_att_att(element, attribute_name, attribute_value, qwebcontext):
|
|
|
|
if is_website and att == URL_ATTRS and isinstance(val, basestring):
|
|
|
|
val = qwebcontext.get('url_for')(val)
|
|
|
|
yield (att, val)
|
2014-01-28 20:14:17 +00:00
|
|
|
|
2013-11-12 14:44:42 +00:00
|
|
|
|
2013-10-08 09:22:33 +00:00
|
|
|
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,
|
2013-10-21 08:28:40 +00:00
|
|
|
source_element, g_att, t_att, qweb_context, context=None):
|
2013-12-18 11:49:01 +00:00
|
|
|
if options is None: options = {}
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
field = record._model._fields[field_name]
|
|
|
|
attrs = [('data-oe-translate', 1 if getattr(field, 'translate', False) else 0)]
|
2013-12-18 11:49:01 +00:00
|
|
|
|
|
|
|
placeholder = options.get('placeholder') \
|
2014-07-02 14:01:36 +00:00
|
|
|
or source_element.get('placeholder') \
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
or getattr(field, 'placeholder', None)
|
2013-12-18 11:49:01 +00:00
|
|
|
if placeholder:
|
|
|
|
attrs.append(('placeholder', placeholder))
|
|
|
|
|
2013-10-08 09:22:33 +00:00
|
|
|
return itertools.chain(
|
|
|
|
super(Field, self).attributes(cr, uid, field_name, record, options,
|
|
|
|
source_element, g_att, t_att,
|
2013-10-21 08:28:40 +00:00
|
|
|
qweb_context, context=context),
|
2013-12-18 11:49:01 +00:00
|
|
|
attrs
|
2013-10-08 09:22:33 +00:00
|
|
|
)
|
|
|
|
|
2013-10-09 13:31:12 +00:00
|
|
|
def value_from_string(self, value):
|
|
|
|
return value
|
|
|
|
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
def from_html(self, cr, uid, model, field, element, context=None):
|
2013-10-09 13:31:12 +00:00
|
|
|
return self.value_from_string(element.text_content().strip())
|
|
|
|
|
2013-10-11 13:11:47 +00:00
|
|
|
def qweb_object(self):
|
|
|
|
return self.pool['website.qweb']
|
|
|
|
|
2013-10-09 13:31:12 +00:00
|
|
|
class Integer(orm.AbstractModel):
|
|
|
|
_name = 'website.qweb.field.integer'
|
|
|
|
_inherit = ['website.qweb.field']
|
|
|
|
|
|
|
|
value_from_string = int
|
|
|
|
|
2013-10-08 09:22:33 +00:00
|
|
|
class Float(orm.AbstractModel):
|
|
|
|
_name = 'website.qweb.field.float'
|
|
|
|
_inherit = ['website.qweb.field', 'ir.qweb.field.float']
|
|
|
|
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
def from_html(self, cr, uid, model, field, element, context=None):
|
2013-10-22 08:54:50 +00:00
|
|
|
lang = self.user_lang(cr, uid, context=context)
|
|
|
|
|
|
|
|
value = element.text_content().strip()
|
|
|
|
|
|
|
|
return float(value.replace(lang.thousands_sep, '')
|
|
|
|
.replace(lang.decimal_point, '.'))
|
2013-10-09 13:31:12 +00:00
|
|
|
|
2013-10-23 14:57:29 +00:00
|
|
|
|
|
|
|
def parse_fuzzy(in_format, value):
|
|
|
|
day_first = in_format.find('%d') < in_format.find('%m')
|
|
|
|
|
|
|
|
if '%y' in in_format:
|
|
|
|
year_first = in_format.find('%y') < in_format.find('%d')
|
|
|
|
else:
|
|
|
|
year_first = in_format.find('%Y') < in_format.find('%d')
|
|
|
|
|
|
|
|
return parser.parse(value, dayfirst=day_first, yearfirst=year_first)
|
|
|
|
|
2013-10-21 15:26:51 +00:00
|
|
|
class Date(orm.AbstractModel):
|
|
|
|
_name = 'website.qweb.field.date'
|
|
|
|
_inherit = ['website.qweb.field', 'ir.qweb.field.date']
|
|
|
|
|
2013-12-05 11:39:08 +00:00
|
|
|
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])])
|
2013-10-23 14:57:29 +00:00
|
|
|
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
def from_html(self, cr, uid, model, field, element, context=None):
|
2013-10-23 14:57:29 +00:00
|
|
|
value = element.text_content().strip()
|
2013-12-05 11:39:08 +00:00
|
|
|
if not value: return False
|
2013-10-23 14:57:29 +00:00
|
|
|
|
2013-12-05 11:39:08 +00:00
|
|
|
datetime.datetime.strptime(value, DEFAULT_SERVER_DATE_FORMAT)
|
|
|
|
return value
|
2013-10-22 07:33:26 +00:00
|
|
|
|
2013-10-21 15:26:51 +00:00
|
|
|
class DateTime(orm.AbstractModel):
|
|
|
|
_name = 'website.qweb.field.datetime'
|
|
|
|
_inherit = ['website.qweb.field', 'ir.qweb.field.datetime']
|
|
|
|
|
2013-12-05 11:39:08 +00:00
|
|
|
def attributes(self, cr, uid, field_name, record, options,
|
|
|
|
source_element, g_att, t_att, qweb_context,
|
|
|
|
context=None):
|
|
|
|
value = record[field_name]
|
|
|
|
if isinstance(value, basestring):
|
|
|
|
value = datetime.datetime.strptime(
|
|
|
|
value, DEFAULT_SERVER_DATETIME_FORMAT)
|
2013-12-22 23:00:21 +00:00
|
|
|
if value:
|
2014-06-18 14:09:02 +00:00
|
|
|
# convert from UTC (server timezone) to user timezone
|
2014-04-01 16:06:23 +00:00
|
|
|
value = fields.datetime.context_timestamp(
|
2013-12-22 23:00:21 +00:00
|
|
|
cr, uid, timestamp=value, context=context)
|
2014-06-18 14:09:02 +00:00
|
|
|
value = value.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
2013-12-05 11:39:08 +00:00
|
|
|
|
|
|
|
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, [
|
2013-12-22 23:00:21 +00:00
|
|
|
('data-oe-original', value)
|
2013-12-05 11:39:08 +00:00
|
|
|
])
|
2013-10-23 14:57:29 +00:00
|
|
|
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
def from_html(self, cr, uid, model, field, element, context=None):
|
2014-06-18 14:09:02 +00:00
|
|
|
if context is None: context = {}
|
2013-10-23 14:57:29 +00:00
|
|
|
value = element.text_content().strip()
|
2013-12-05 11:39:08 +00:00
|
|
|
if not value: return False
|
2013-10-23 14:57:29 +00:00
|
|
|
|
2014-06-18 14:09:02 +00:00
|
|
|
# parse from string to datetime
|
|
|
|
dt = datetime.datetime.strptime(value, DEFAULT_SERVER_DATETIME_FORMAT)
|
|
|
|
|
|
|
|
# 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']
|
|
|
|
if tz_name:
|
|
|
|
try:
|
|
|
|
user_tz = pytz.timezone(tz_name)
|
|
|
|
utc = pytz.utc
|
|
|
|
|
|
|
|
dt = user_tz.localize(dt).astimezone(utc)
|
|
|
|
except Exception:
|
|
|
|
logger.warn(
|
|
|
|
"Failed to convert the value for a field of the model"
|
|
|
|
" %s back from the user's timezone (%s) to UTC",
|
|
|
|
model, tz_name,
|
|
|
|
exc_info=True)
|
|
|
|
|
|
|
|
# format back to string
|
|
|
|
return dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
2013-10-22 07:33:26 +00:00
|
|
|
|
2013-10-08 09:22:33 +00:00
|
|
|
class Text(orm.AbstractModel):
|
|
|
|
_name = 'website.qweb.field.text'
|
|
|
|
_inherit = ['website.qweb.field', 'ir.qweb.field.text']
|
|
|
|
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
def from_html(self, cr, uid, model, field, element, context=None):
|
2014-01-10 15:20:24 +00:00
|
|
|
return html_to_text(element)
|
2013-10-09 13:31:12 +00:00
|
|
|
|
2013-10-08 09:22:33 +00:00
|
|
|
class Selection(orm.AbstractModel):
|
|
|
|
_name = 'website.qweb.field.selection'
|
|
|
|
_inherit = ['website.qweb.field', 'ir.qweb.field.selection']
|
|
|
|
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
def from_html(self, cr, uid, model, field, element, context=None):
|
|
|
|
record = self.browse(cr, uid, [], context=context)
|
2013-10-09 13:31:12 +00:00
|
|
|
value = element.text_content().strip()
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
selection = field.get_description(record.env)['selection']
|
2013-10-09 13:31:12 +00:00
|
|
|
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))
|
|
|
|
|
2013-10-08 09:22:33 +00:00
|
|
|
class ManyToOne(orm.AbstractModel):
|
|
|
|
_name = 'website.qweb.field.many2one'
|
|
|
|
_inherit = ['website.qweb.field', 'ir.qweb.field.many2one']
|
|
|
|
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
def from_html(self, cr, uid, model, field, element, context=None):
|
2013-12-18 14:09:17 +00:00
|
|
|
# FIXME: layering violations all the things
|
|
|
|
Model = self.pool[element.get('data-oe-model')]
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
M2O = self.pool[field.comodel_name]
|
|
|
|
field_name = element.get('data-oe-field')
|
2013-12-18 14:09:17 +00:00
|
|
|
id = int(element.get('data-oe-id'))
|
2014-01-10 15:20:24 +00:00
|
|
|
# FIXME: weird things are going to happen for char-type _rec_name
|
|
|
|
value = html_to_text(element)
|
2013-12-18 14:09:17 +00:00
|
|
|
|
|
|
|
# if anything blows up, just ignore it and bail
|
|
|
|
try:
|
|
|
|
# get parent record
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
[obj] = Model.read(cr, uid, [id], [field_name])
|
2013-12-18 14:09:17 +00:00
|
|
|
# get m2o record id
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
(m2o_id, _) = obj[field_name]
|
2013-12-18 14:09:17 +00:00
|
|
|
# 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",
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
value, field_name, Model._name)
|
2013-12-18 14:09:17 +00:00
|
|
|
|
|
|
|
# not necessary, but might as well be explicit about it
|
|
|
|
return None
|
2013-10-09 13:31:12 +00:00
|
|
|
|
2013-10-08 09:22:33 +00:00
|
|
|
class HTML(orm.AbstractModel):
|
|
|
|
_name = 'website.qweb.field.html'
|
|
|
|
_inherit = ['website.qweb.field', 'ir.qweb.field.html']
|
|
|
|
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
def from_html(self, cr, uid, model, field, element, context=None):
|
2013-10-09 13:31:12 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2013-10-08 09:22:33 +00:00
|
|
|
class Image(orm.AbstractModel):
|
2013-10-08 12:04:56 +00:00
|
|
|
"""
|
|
|
|
Widget options:
|
|
|
|
|
|
|
|
``class``
|
|
|
|
set as attribute on the generated <img> tag
|
|
|
|
"""
|
2013-10-08 09:22:33 +00:00
|
|
|
_name = 'website.qweb.field.image'
|
|
|
|
_inherit = ['website.qweb.field', 'ir.qweb.field.image']
|
|
|
|
|
2013-10-08 12:04:56 +00:00
|
|
|
def to_html(self, cr, uid, field_name, record, options,
|
2013-10-21 08:28:40 +00:00
|
|
|
source_element, t_att, g_att, qweb_context, context=None):
|
2014-07-02 14:01:36 +00:00
|
|
|
assert source_element.tag != 'img',\
|
2013-10-08 12:04:56 +00:00
|
|
|
"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,
|
2013-10-21 08:28:40 +00:00
|
|
|
source_element, t_att, g_att, qweb_context, context=context)
|
2013-10-08 12:04:56 +00:00
|
|
|
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
def record_to_html(self, cr, uid, field_name, record, options=None, context=None):
|
2013-11-22 10:41:48 +00:00
|
|
|
if options is None: options = {}
|
2014-09-05 16:20:55 +00:00
|
|
|
aclasses = ['img', 'img-responsive'] + options.get('class', '').split()
|
|
|
|
classes = ' '.join(itertools.imap(escape, aclasses))
|
|
|
|
|
|
|
|
max_size = None
|
2014-08-28 14:05:58 +00:00
|
|
|
max_width, max_height = options.get('max_width', 0), options.get('max_height', 0)
|
|
|
|
if max_width or max_height:
|
2014-09-05 16:20:55 +00:00
|
|
|
max_size = '%sx%s' % (max_width, max_height)
|
2014-08-28 14:05:58 +00:00
|
|
|
|
2014-09-05 16:20:55 +00:00
|
|
|
src = self.pool['website'].image_url(cr, uid, record, field_name, max_size)
|
2014-11-05 10:11:26 +00:00
|
|
|
img = '<img class="%s" src="%s" style="%s"/>' % (classes, src, options.get('style', ''))
|
2014-09-05 16:20:55 +00:00
|
|
|
return ir_qweb.HTMLSafe(img)
|
2013-10-08 12:04:56 +00:00
|
|
|
|
2013-11-27 10:46:38 +00:00
|
|
|
local_url_re = re.compile(r'^/(?P<module>[^]]+)/static/(?P<rest>.+)$')
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
|
|
|
|
def from_html(self, cr, uid, model, field, element, context=None):
|
2013-10-10 10:30:05 +00:00
|
|
|
url = element.find('img').get('src')
|
|
|
|
|
2013-10-24 10:34:01 +00:00
|
|
|
url_object = urlparse.urlsplit(url)
|
2014-09-17 14:54:08 +00:00
|
|
|
if url_object.path.startswith('/website/image'):
|
|
|
|
# url might be /website/image/<model>/<id>[_<checksum>]/<field>[/<width>x<height>]
|
|
|
|
fragments = url_object.path.split('/')
|
|
|
|
query = dict(urlparse.parse_qsl(url_object.query))
|
|
|
|
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)
|
|
|
|
return item[field]
|
2013-10-10 10:30:05 +00:00
|
|
|
|
2013-11-27 10:46:38 +00:00
|
|
|
if self.local_url_re.match(url_object.path):
|
|
|
|
return self.load_local_url(url)
|
|
|
|
|
|
|
|
return self.load_remote_url(url)
|
|
|
|
|
|
|
|
def load_local_url(self, url):
|
|
|
|
match = self.local_url_re.match(urlparse.urlsplit(url).path)
|
|
|
|
|
|
|
|
rest = match.group('rest')
|
|
|
|
for sep in os.sep, os.altsep:
|
|
|
|
if sep and sep != '/':
|
|
|
|
rest.replace(sep, '/')
|
|
|
|
|
|
|
|
path = openerp.modules.get_module_resource(
|
|
|
|
match.group('module'), 'static', *(rest.split('/')))
|
|
|
|
|
|
|
|
if not path:
|
2013-12-18 14:09:17 +00:00
|
|
|
return None
|
2013-11-27 10:46:38 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
with open(path, 'rb') as f:
|
|
|
|
# force complete image load to ensure it's valid image data
|
|
|
|
image = I.open(f)
|
|
|
|
image.load()
|
|
|
|
f.seek(0)
|
|
|
|
return f.read().encode('base64')
|
|
|
|
except Exception:
|
|
|
|
logger.exception("Failed to load local image %r", url)
|
2013-12-18 14:09:17 +00:00
|
|
|
return None
|
2013-11-27 10:46:38 +00:00
|
|
|
|
|
|
|
def load_remote_url(self, url):
|
2013-10-10 10:30:05 +00:00
|
|
|
try:
|
2013-10-10 12:26:52 +00:00
|
|
|
# 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
|
2013-10-10 10:30:05 +00:00
|
|
|
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)
|
2013-12-18 14:09:17 +00:00
|
|
|
return None
|
2013-10-10 10:30:05 +00:00
|
|
|
|
|
|
|
# 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')
|
|
|
|
|
2013-10-08 12:20:39 +00:00
|
|
|
class Monetary(orm.AbstractModel):
|
|
|
|
_name = 'website.qweb.field.monetary'
|
|
|
|
_inherit = ['website.qweb.field', 'ir.qweb.field.monetary']
|
2013-10-11 13:11:47 +00:00
|
|
|
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
def from_html(self, cr, uid, model, field, element, context=None):
|
2013-10-22 09:08:43 +00:00
|
|
|
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, '.'))
|
2013-12-05 11:39:08 +00:00
|
|
|
|
|
|
|
class Duration(orm.AbstractModel):
|
|
|
|
_name = 'website.qweb.field.duration'
|
|
|
|
_inherit = [
|
|
|
|
'ir.qweb.field.duration',
|
|
|
|
'website.qweb.field.float',
|
|
|
|
]
|
|
|
|
|
|
|
|
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])])
|
|
|
|
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
def from_html(self, cr, uid, model, field, element, context=None):
|
2013-12-05 11:39:08 +00:00
|
|
|
value = element.text_content().strip()
|
|
|
|
|
|
|
|
# non-localized value
|
|
|
|
return float(value)
|
|
|
|
|
|
|
|
|
|
|
|
class RelativeDatetime(orm.AbstractModel):
|
|
|
|
_name = 'website.qweb.field.relative'
|
|
|
|
_inherit = [
|
|
|
|
'ir.qweb.field.relative',
|
|
|
|
'website.qweb.field.datetime',
|
|
|
|
]
|
|
|
|
|
|
|
|
# get formatting from ir.qweb.field.relative but edition/save from datetime
|
2013-12-06 13:30:03 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Contact(orm.AbstractModel):
|
|
|
|
_name = 'website.qweb.field.contact'
|
2014-03-24 13:59:33 +00:00
|
|
|
_inherit = ['ir.qweb.field.contact', 'website.qweb.field.many2one']
|
2013-12-06 13:30:03 +00:00
|
|
|
|
[IMP] use model._fields instead of model._all_columns to cover all fields
The old-api model._all_columns contains information about model._columns and
inherited columns. This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...
This commit contains the following changes:
- adapt several methods of BaseModel to use fields instead of columns and
_all_columns
- copy all semantic-free attributes of related fields from their source
- add attribute 'group_operator' on integer and float fields
- base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
payment_acquirer, share, website, website_crm, website_mail: simply use
_fields instead of _all_columns
- base, decimal_precision, website: adapt qweb rendering methods to use fields
instead of columns
2014-11-03 15:00:50 +00:00
|
|
|
def from_html(self, cr, uid, model, field, element, context=None):
|
2014-10-16 16:38:20 +00:00
|
|
|
return None
|
|
|
|
|
2014-04-09 10:44:27 +00:00
|
|
|
class QwebView(orm.AbstractModel):
|
|
|
|
_name = 'website.qweb.field.qweb'
|
|
|
|
_inherit = ['ir.qweb.field.qweb']
|
|
|
|
|
2014-01-10 15:20:24 +00:00
|
|
|
|
|
|
|
def html_to_text(element):
|
|
|
|
""" Converts HTML content with HTML-specified line breaks (br, p, div, ...)
|
|
|
|
in roughly equivalent textual content.
|
|
|
|
|
|
|
|
Used to replace and fixup the roundtripping of text and m2o: when using
|
|
|
|
libxml 2.8.0 (but not 2.9.1) and parsing HTML with lxml.html.fromstring
|
|
|
|
whitespace text nodes (text nodes composed *solely* of whitespace) are
|
|
|
|
stripped out with no recourse, and fundamentally relying on newlines
|
|
|
|
being in the text (e.g. inserted during user edition) is probably poor form
|
|
|
|
anyway.
|
|
|
|
|
|
|
|
-> this utility function collapses whitespace sequences and replaces
|
|
|
|
nodes by roughly corresponding linebreaks
|
|
|
|
* p are pre-and post-fixed by 2 newlines
|
|
|
|
* br are replaced by a single newline
|
|
|
|
* block-level elements not already mentioned are pre- and post-fixed by
|
|
|
|
a single newline
|
|
|
|
|
|
|
|
ought be somewhat similar (but much less high-tech) to aaronsw's html2text.
|
|
|
|
the latter produces full-blown markdown, our text -> html converter only
|
|
|
|
replaces newlines by <br> elements at this point so we're reverting that,
|
|
|
|
and a few more newline-ish elements in case the user tried to add
|
|
|
|
newlines/paragraphs into the text field
|
|
|
|
|
|
|
|
:param element: lxml.html content
|
|
|
|
:returns: corresponding pure-text output
|
|
|
|
"""
|
|
|
|
|
|
|
|
# output is a list of str | int. Integers are padding requests (in minimum
|
|
|
|
# number of newlines). When multiple padding requests, fold them into the
|
|
|
|
# biggest one
|
|
|
|
output = []
|
|
|
|
_wrap(element, output)
|
|
|
|
|
|
|
|
# remove any leading or tailing whitespace, replace sequences of
|
|
|
|
# (whitespace)\n(whitespace) by a single newline, where (whitespace) is a
|
|
|
|
# non-newline whitespace in this case
|
|
|
|
return re.sub(
|
|
|
|
r'[ \t\r\f]*\n[ \t\r\f]*',
|
|
|
|
'\n',
|
|
|
|
''.join(_realize_padding(output)).strip())
|
|
|
|
|
|
|
|
_PADDED_BLOCK = set('p h1 h2 h3 h4 h5 h6'.split())
|
|
|
|
# https://developer.mozilla.org/en-US/docs/HTML/Block-level_elements minus p
|
|
|
|
_MISC_BLOCK = set((
|
|
|
|
'address article aside audio blockquote canvas dd dl div figcaption figure'
|
|
|
|
' footer form header hgroup hr ol output pre section tfoot ul video'
|
|
|
|
).split())
|
|
|
|
|
|
|
|
def _collapse_whitespace(text):
|
|
|
|
""" Collapses sequences of whitespace characters in ``text`` to a single
|
|
|
|
space
|
|
|
|
"""
|
|
|
|
return re.sub('\s+', ' ', text)
|
|
|
|
def _realize_padding(it):
|
|
|
|
""" Fold and convert padding requests: integers in the output sequence are
|
|
|
|
requests for at least n newlines of padding. Runs thereof can be collapsed
|
|
|
|
into the largest requests and converted to newlines.
|
|
|
|
"""
|
|
|
|
padding = None
|
|
|
|
for item in it:
|
|
|
|
if isinstance(item, int):
|
|
|
|
padding = max(padding, item)
|
|
|
|
continue
|
|
|
|
|
|
|
|
if padding:
|
|
|
|
yield '\n' * padding
|
|
|
|
padding = None
|
|
|
|
|
|
|
|
yield item
|
|
|
|
# leftover padding irrelevant as the output will be stripped
|
|
|
|
|
|
|
|
def _wrap(element, output, wrapper=u''):
|
|
|
|
""" Recursively extracts text from ``element`` (via _element_to_text), and
|
|
|
|
wraps it all in ``wrapper``. Extracted text is added to ``output``
|
|
|
|
|
|
|
|
:type wrapper: basestring | int
|
|
|
|
"""
|
|
|
|
output.append(wrapper)
|
|
|
|
if element.text:
|
|
|
|
output.append(_collapse_whitespace(element.text))
|
|
|
|
for child in element:
|
|
|
|
_element_to_text(child, output)
|
|
|
|
output.append(wrapper)
|
|
|
|
|
|
|
|
def _element_to_text(e, output):
|
|
|
|
if e.tag == 'br':
|
|
|
|
output.append(u'\n')
|
|
|
|
elif e.tag in _PADDED_BLOCK:
|
|
|
|
_wrap(e, output, 2)
|
|
|
|
elif e.tag in _MISC_BLOCK:
|
|
|
|
_wrap(e, output, 1)
|
|
|
|
else:
|
|
|
|
# inline
|
|
|
|
_wrap(e, output)
|
|
|
|
|
|
|
|
if e.tail:
|
|
|
|
output.append(_collapse_whitespace(e.tail))
|