[MERGE] with trunk
bzr revid: dizzy.zala@gmail.com-20130919085041-9hnv7w93bzlinrhi
This commit is contained in:
@@ -6,6 +6,8 @@ Changelog
|
||||
`trunk`
|
||||
-------
|
||||
|
||||
- Added support of custom group_by format and display format when using group_by
|
||||
on a datetime field, using datetime_format context key
|
||||
- Improved ``html_email_clean`` in tools: better quote and signature finding,
|
||||
added shortening.
|
||||
- Cleaned and slightly refactored ``ir.actions.server``. The ``loop``, ``sms``
|
||||
|
||||
@@ -110,7 +110,7 @@ class report_xml(osv.osv):
|
||||
kwargs = {}
|
||||
new_report = report_sxw('report.'+r['report_name'], r['model'],
|
||||
opj('addons',r['report_rml'] or '/'), header=r['header'], register=False, **kwargs)
|
||||
elif r['report_xsl']:
|
||||
elif r['report_xsl'] and r['report_xml']:
|
||||
new_report = report_rml('report.'+r['report_name'], r['model'],
|
||||
opj('addons',r['report_xml']),
|
||||
r['report_xsl'] and opj('addons',r['report_xsl']), register=False)
|
||||
|
||||
@@ -404,8 +404,10 @@ class ir_mail_server(osv.osv):
|
||||
|
||||
# The email's "Envelope From" (Return-Path), and all recipient addresses must only contain ASCII characters.
|
||||
from_rfc2822 = extract_rfc2822_addresses(smtp_from)
|
||||
assert len(from_rfc2822) == 1, "Malformed 'Return-Path' or 'From' address - it may only contain plain ASCII characters"
|
||||
smtp_from = from_rfc2822[0]
|
||||
assert from_rfc2822, ("Malformed 'Return-Path' or 'From' address: %r - "
|
||||
"It should contain one valid plain ASCII email") % smtp_from
|
||||
# use last extracted email, to support rarities like 'Support@MyComp <support@mycompany.com>'
|
||||
smtp_from = from_rfc2822[-1]
|
||||
email_to = message['To']
|
||||
email_cc = message['Cc']
|
||||
email_bcc = message['Bcc']
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
import logging
|
||||
from lxml import etree
|
||||
from operator import itemgetter
|
||||
import os
|
||||
|
||||
from openerp import tools
|
||||
@@ -127,10 +128,14 @@ class view(osv.osv):
|
||||
try:
|
||||
fvg = self.pool[view.model].fields_view_get(cr, uid, view_id=view.id, view_type=view.type, context=context)
|
||||
return fvg['arch']
|
||||
except:
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _check_xml(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
context['check_view_ids'] = ids
|
||||
|
||||
for view in self.browse(cr, uid, ids, context):
|
||||
# Sanity check: the view should not break anything upon rendering!
|
||||
view_arch_utf8 = self._check_render_view(cr, uid, view, context=context)
|
||||
@@ -183,13 +188,15 @@ class view(osv.osv):
|
||||
:rtype: list of tuples
|
||||
:return: [(view_arch,view_id), ...]
|
||||
"""
|
||||
|
||||
user_groups = frozenset(self.pool.get('res.users').browse(cr, 1, uid, context).groups_id)
|
||||
if self.pool._init:
|
||||
# Module init currently in progress, only consider views from modules whose code was already loaded
|
||||
check_view_ids = context and context.get('check_view_ids') or (0,)
|
||||
query = """SELECT v.id FROM ir_ui_view v LEFT JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id)
|
||||
WHERE v.inherit_id=%s AND v.model=%s AND (md.module IS NULL or md.module in %s)
|
||||
WHERE v.inherit_id=%s AND v.model=%s AND (md.module in %s OR v.id in %s)
|
||||
ORDER BY priority"""
|
||||
query_params = (view_id, model, tuple(self.pool._init_modules))
|
||||
query_params = (view_id, model, tuple(self.pool._init_modules), tuple(check_view_ids))
|
||||
else:
|
||||
# Modules fully loaded, consider all views
|
||||
query = """SELECT v.id FROM ir_ui_view v
|
||||
@@ -282,6 +289,22 @@ class view(osv.osv):
|
||||
'blank_nodes': blank_nodes,
|
||||
'node_parent_field': _Model_Field,}
|
||||
|
||||
def _validate_custom_views(self, cr, uid, model):
|
||||
"""Validate architecture of custom views (= without xml id) for a given model.
|
||||
This method is called at the end of registry update.
|
||||
"""
|
||||
cr.execute("""SELECT max(v.id)
|
||||
FROM ir_ui_view v
|
||||
LEFT JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id)
|
||||
WHERE md.module IS NULL
|
||||
AND v.model = %s
|
||||
GROUP BY coalesce(v.inherit_id, v.id)
|
||||
""", (model,))
|
||||
|
||||
ids = map(itemgetter(0), cr.fetchall())
|
||||
return self._check_xml(cr, uid, ids)
|
||||
|
||||
|
||||
class view_sc(osv.osv):
|
||||
_name = 'ir.ui.view_sc'
|
||||
_columns = {
|
||||
|
||||
@@ -7,6 +7,7 @@ import test_menu
|
||||
import test_res_config
|
||||
import test_res_lang
|
||||
import test_search
|
||||
import test_views
|
||||
|
||||
checks = [
|
||||
test_base,
|
||||
@@ -18,4 +19,5 @@ checks = [
|
||||
test_res_config,
|
||||
test_res_lang,
|
||||
test_search,
|
||||
test_views,
|
||||
]
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
from functools import partial
|
||||
import unittest2
|
||||
|
||||
import openerp.tests.common as common
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
class test_views(common.TransactionCase):
|
||||
|
||||
@mute_logger('openerp.osv.orm', 'openerp.addons.base.ir.ir_ui_view')
|
||||
def test_00_init_check_views(self):
|
||||
Views = self.registry('ir.ui.view')
|
||||
|
||||
self.assertTrue(Views.pool._init)
|
||||
|
||||
error_msg = "The model name does not exist or the view architecture cannot be rendered"
|
||||
# test arch check is call for views without xmlid during registry initialization
|
||||
with self.assertRaisesRegexp(except_orm, error_msg):
|
||||
Views.create(self.cr, self.uid, {
|
||||
'name': 'Test View #1',
|
||||
'model': 'ir.ui.view',
|
||||
'arch': """<?xml version="1.0"?>
|
||||
<tree>
|
||||
<field name="test_1"/>
|
||||
</tree>
|
||||
""",
|
||||
})
|
||||
|
||||
# same for inherited views
|
||||
with self.assertRaisesRegexp(except_orm, error_msg):
|
||||
# Views.pudb = True
|
||||
Views.create(self.cr, self.uid, {
|
||||
'name': 'Test View #2',
|
||||
'model': 'ir.ui.view',
|
||||
'inherit_id': self.browse_ref('base.view_view_tree').id,
|
||||
'arch': """<?xml version="1.0"?>
|
||||
<xpath expr="//field[@name='name']" position="after">
|
||||
<field name="test_2"/>
|
||||
</xpath>
|
||||
""",
|
||||
})
|
||||
|
||||
def _insert_view(self, **kw):
|
||||
"""Insert view into database via a query to passtrough validation"""
|
||||
kw.pop('id', None)
|
||||
|
||||
keys = sorted(kw.keys())
|
||||
fields = ','.join('"%s"' % (k.replace('"', r'\"'),) for k in keys)
|
||||
params = ','.join('%%(%s)s' % (k,) for k in keys)
|
||||
|
||||
query = 'INSERT INTO ir_ui_view(%s) VALUES(%s) RETURNING id' % (fields, params)
|
||||
self.cr.execute(query, kw)
|
||||
return self.cr.fetchone()[0]
|
||||
|
||||
def test_10_validate_custom_views(self):
|
||||
Views = self.registry('ir.ui.view')
|
||||
model = 'ir.actions.act_url'
|
||||
|
||||
validate = partial(Views._validate_custom_views, self.cr, self.uid, model)
|
||||
|
||||
# validation of a single view
|
||||
vid = self._insert_view(**{
|
||||
'name': 'base view',
|
||||
'model': model,
|
||||
'priority': 1,
|
||||
'arch': """<?xml version="1.0"?>
|
||||
<tree string="view">
|
||||
<field name="url"/>
|
||||
</tree>
|
||||
""",
|
||||
})
|
||||
self.assertTrue(validate()) # single view
|
||||
|
||||
# validation of a inherited view
|
||||
self._insert_view(**{
|
||||
'name': 'inherited view',
|
||||
'model': model,
|
||||
'priority': 1,
|
||||
'inherit_id': vid,
|
||||
'arch': """<?xml version="1.0"?>
|
||||
<xpath expr="//field[@name='url']" position="before">
|
||||
<field name="name"/>
|
||||
</xpath>
|
||||
""",
|
||||
})
|
||||
self.assertTrue(validate()) # inherited view
|
||||
|
||||
# validation of a bad inherited view
|
||||
self._insert_view(**{
|
||||
'name': 'bad inherited view',
|
||||
'model': model,
|
||||
'priority': 2,
|
||||
'inherit_id': vid,
|
||||
'arch': """<?xml version="1.0"?>
|
||||
<xpath expr="//field[@name='url']" position="after">
|
||||
<field name="bad"/>
|
||||
</xpath>
|
||||
""",
|
||||
})
|
||||
with mute_logger('openerp.osv.orm', 'openerp.addons.base.ir.ir_ui_view'):
|
||||
self.assertFalse(validate()) # bad inherited view
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest2.main()
|
||||
@@ -400,12 +400,22 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||
_logger.info('Reloading registry once more after uninstalling modules')
|
||||
return openerp.modules.registry.RegistryManager.new(cr.dbname, force_demo, status, update_module)
|
||||
|
||||
# STEP 7: verify custom views on every model
|
||||
if update_module:
|
||||
Views = registry['ir.ui.view']
|
||||
custom_view_test = True
|
||||
for model in registry.models.keys():
|
||||
if not Views._validate_custom_views(cr, SUPERUSER_ID, model):
|
||||
custom_view_test = False
|
||||
_logger.error('invalid custom view(s) for model %s', model)
|
||||
report.record_result(custom_view_test)
|
||||
|
||||
if report.failures:
|
||||
_logger.error('At least one test failed when loading the modules.')
|
||||
else:
|
||||
_logger.info('Modules loaded.')
|
||||
|
||||
# STEP 7: call _register_hook on every model
|
||||
# STEP 8: call _register_hook on every model
|
||||
for model in registry.models.values():
|
||||
model._register_hook(cr)
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ class MigrationManager(object):
|
||||
'post': '[%s>]',
|
||||
}
|
||||
|
||||
if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
|
||||
if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade') or pkg.installed_version is None:
|
||||
return
|
||||
|
||||
def convert_version(version):
|
||||
|
||||
+44
-10
@@ -2191,7 +2191,7 @@ class BaseModel(object):
|
||||
are applied
|
||||
|
||||
"""
|
||||
sql_inherit = self.pool.get('ir.ui.view').get_inheriting_views_arch(cr, user, inherit_id, self._name)
|
||||
sql_inherit = self.pool.get('ir.ui.view').get_inheriting_views_arch(cr, user, inherit_id, self._name, context=context)
|
||||
for (view_arch, view_id) in sql_inherit:
|
||||
source = apply_inheritance_specs(source, view_arch, view_id)
|
||||
source = apply_view_inheritance(cr, user, source, view_id)
|
||||
@@ -2601,7 +2601,18 @@ class BaseModel(object):
|
||||
:param list groupby: fields by which the records will be grouped
|
||||
:param int offset: optional number of records to skip
|
||||
:param int limit: optional max number of records to return
|
||||
:param dict context: context arguments, like lang, time zone
|
||||
:param dict context: context arguments, like lang, time zone. A special
|
||||
context key exist for datetime fields : ``datetime_format``.
|
||||
context[``datetime_format``] = {
|
||||
'field_name': {
|
||||
groupby_format: format for to_char (default: yyyy-mm)
|
||||
display_format: format for displaying the value
|
||||
in the result (default: MMM yyyy)
|
||||
interval: day, month or year; used for begin
|
||||
and end date of group_by intervals
|
||||
computation (default: month)
|
||||
}
|
||||
}
|
||||
:param list orderby: optional ``order by`` specification, for
|
||||
overriding the natural sort ordering of the
|
||||
groups, see also :py:meth:`~osv.osv.osv.search`
|
||||
@@ -2641,11 +2652,26 @@ class BaseModel(object):
|
||||
fget = self.fields_get(cr, uid, fields)
|
||||
flist = ''
|
||||
group_count = group_by = groupby
|
||||
group_by_params = {}
|
||||
if groupby:
|
||||
if fget.get(groupby):
|
||||
groupby_type = fget[groupby]['type']
|
||||
if groupby_type in ('date', 'datetime'):
|
||||
qualified_groupby_field = "to_char(%s,'yyyy-mm')" % qualified_groupby_field
|
||||
if context.get('datetime_format') and isinstance(context['datetime_format'], dict) \
|
||||
and context['datetime_format'].get(groupby) and isinstance(context['datetime_format'][groupby], dict):
|
||||
groupby_format = context['datetime_format'][groupby].get('groupby_format', 'yyyy-mm')
|
||||
display_format = context['datetime_format'][groupby].get('display_format', 'MMMM yyyy')
|
||||
interval = context['datetime_format'][groupby].get('interval', 'month')
|
||||
else:
|
||||
groupby_format = 'yyyy-mm'
|
||||
display_format = 'MMMM yyyy'
|
||||
interval = 'month'
|
||||
group_by_params = {
|
||||
'groupby_format': groupby_format,
|
||||
'display_format': display_format,
|
||||
'interval': interval,
|
||||
}
|
||||
qualified_groupby_field = "to_char(%s,%%s)" % qualified_groupby_field
|
||||
flist = "%s as %s " % (qualified_groupby_field, groupby)
|
||||
elif groupby_type == 'boolean':
|
||||
qualified_groupby_field = "coalesce(%s,false)" % qualified_groupby_field
|
||||
@@ -2672,6 +2698,8 @@ class BaseModel(object):
|
||||
gb = groupby and (' GROUP BY ' + qualified_groupby_field) or ''
|
||||
|
||||
from_clause, where_clause, where_clause_params = query.get_sql()
|
||||
if group_by_params and group_by_params.get('groupby_format'):
|
||||
where_clause_params = [group_by_params['groupby_format']] + where_clause_params + [group_by_params['groupby_format']]
|
||||
where_clause = where_clause and ' WHERE ' + where_clause
|
||||
limit_str = limit and ' limit %d' % limit or ''
|
||||
offset_str = offset and ' offset %d' % offset or ''
|
||||
@@ -2708,14 +2736,20 @@ class BaseModel(object):
|
||||
d['__context'] = {'group_by': groupby_list[1:]}
|
||||
if groupby and groupby in fget:
|
||||
if d[groupby] and fget[groupby]['type'] in ('date', 'datetime'):
|
||||
dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7], '%Y-%m')
|
||||
days = calendar.monthrange(dt.year, dt.month)[1]
|
||||
|
||||
date_value = datetime.datetime.strptime(d[groupby][:10], '%Y-%m-%d')
|
||||
groupby_datetime = datetime.datetime.strptime(alldata[d['id']][groupby], '%Y-%m-%d')
|
||||
d[groupby] = babel.dates.format_date(
|
||||
date_value, format='MMMM yyyy', locale=context.get('lang', 'en_US'))
|
||||
d['__domain'] = [(groupby, '>=', alldata[d['id']][groupby] and datetime.datetime.strptime(alldata[d['id']][groupby][:7] + '-01', '%Y-%m-%d').strftime('%Y-%m-%d') or False),\
|
||||
(groupby, '<=', alldata[d['id']][groupby] and datetime.datetime.strptime(alldata[d['id']][groupby][:7] + '-' + str(days), '%Y-%m-%d').strftime('%Y-%m-%d') or False)] + domain
|
||||
groupby_datetime, format=group_by_params.get('display_format', 'MMMM yyyy'), locale=context.get('lang', 'en_US'))
|
||||
if group_by_params.get('interval') == 'month':
|
||||
days = calendar.monthrange(groupby_datetime.year, groupby_datetime.month)[1]
|
||||
domain_dt_begin = groupby_datetime.replace(day=1)
|
||||
domain_dt_end = groupby_datetime.replace(day=days)
|
||||
elif group_by_params.get('interval') == 'day':
|
||||
domain_dt_begin = groupby_datetime.replace(hour=0, minute=0)
|
||||
domain_dt_end = groupby_datetime.replace(hour=23, minute=59, second=59)
|
||||
else:
|
||||
domain_dt_begin = groupby_datetime.replace(month=1, day=1)
|
||||
domain_dt_end = groupby_datetime.replace(month=12, day=31)
|
||||
d['__domain'] = [(groupby, '>=', domain_dt_begin.strftime('%Y-%m-%d')), (groupby, '<=', domain_dt_end.strftime('%Y-%m-%d'))] + domain
|
||||
del alldata[d['id']][groupby]
|
||||
d.update(alldata[d['id']])
|
||||
del d['id']
|
||||
|
||||
@@ -448,6 +448,11 @@ command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE)
|
||||
# group(1) = the record ID ; group(2) = the model (if any) ; group(3) = the domain
|
||||
reference_re = re.compile("<.*-open(?:object|erp)-(\\d+)(?:-([\w.]+))?.*@(.*)>", re.UNICODE)
|
||||
|
||||
# Bounce regex
|
||||
# Typical form of bounce is bounce-128-crm.lead-34@domain
|
||||
# group(1) = the mail ID; group(2) = the model (if any); group(3) = the record ID
|
||||
bounce_re = re.compile("[\w]+-(\d+)-?([\w.]+)?-?(\d+)?", re.UNICODE)
|
||||
|
||||
def generate_tracking_message_id(res_id):
|
||||
"""Returns a string that can be used in the Message-ID RFC822 header field
|
||||
|
||||
|
||||
@@ -832,8 +832,11 @@ def trans_generate(lang, modules, cr):
|
||||
if module:
|
||||
src_file = open(fabsolutepath, 'r')
|
||||
try:
|
||||
for lineno, message, comments in extract.extract(extract_method, src_file,
|
||||
keywords=extract_keywords):
|
||||
for extracted in extract.extract(extract_method, src_file,
|
||||
keywords=extract_keywords):
|
||||
# Babel 0.9.6 yields lineno, message, comments
|
||||
# Babel 1.3 yields lineno, message, comments, context
|
||||
lineno, message, comments = extracted[:3]
|
||||
push_translation(module, trans_type, display_path, lineno,
|
||||
encode(message), comments + extra_comments)
|
||||
except Exception:
|
||||
|
||||
Reference in New Issue
Block a user