[MERGE] forward port of branch saas-3 up to revid 5093 chs@openerp.com-20140318114540-l50kmo4azb57g31x
bzr revid: chs@openerp.com-20140318124112-cj65l3wysk2rhwlw
This commit is contained in:
commit
8f8ca3bd84
|
@ -33,7 +33,7 @@ from openerp.osv.orm import Model, browse_null
|
||||||
from openerp.tools.safe_eval import safe_eval as eval
|
from openerp.tools.safe_eval import safe_eval as eval
|
||||||
from openerp.tools import config
|
from openerp.tools import config
|
||||||
from openerp.tools.translate import _
|
from openerp.tools.translate import _
|
||||||
from openerp.osv.orm import except_orm, browse_record
|
from openerp.osv.orm import except_orm, browse_record, MAGIC_COLUMNS
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -302,6 +302,8 @@ class ir_model_fields(osv.osv):
|
||||||
|
|
||||||
def _drop_column(self, cr, uid, ids, context=None):
|
def _drop_column(self, cr, uid, ids, context=None):
|
||||||
for field in self.browse(cr, uid, ids, context):
|
for field in self.browse(cr, uid, ids, context):
|
||||||
|
if field.name in MAGIC_COLUMNS:
|
||||||
|
continue
|
||||||
model = self.pool[field.model]
|
model = self.pool[field.model]
|
||||||
cr.execute('select relkind from pg_class where relname=%s', (model._table,))
|
cr.execute('select relkind from pg_class where relname=%s', (model._table,))
|
||||||
result = cr.fetchone()
|
result = cr.fetchone()
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#
|
#
|
||||||
# OpenERP, Open Source Management Solution
|
# OpenERP, Open Source Management Solution
|
||||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||||
# Copyright (C) 2010-2013 OpenERP s.a. (<http://openerp.com>).
|
# Copyright (C) 2010-2014 OpenERP s.a. (<http://openerp.com>).
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -169,8 +169,9 @@ class res_users(osv.osv):
|
||||||
}
|
}
|
||||||
|
|
||||||
def on_change_login(self, cr, uid, ids, login, context=None):
|
def on_change_login(self, cr, uid, ids, login, context=None):
|
||||||
v = {'email': login} if tools.single_email_re.match(login) else {}
|
if login and tools.single_email_re.match(login):
|
||||||
return {'value': v}
|
return {'value': {'email': login}}
|
||||||
|
return {}
|
||||||
|
|
||||||
def onchange_state(self, cr, uid, ids, state_id, context=None):
|
def onchange_state(self, cr, uid, ids, state_id, context=None):
|
||||||
partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context=context)]
|
partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context=context)]
|
||||||
|
@ -899,19 +900,23 @@ class change_password_wizard(osv.TransientModel):
|
||||||
}))
|
}))
|
||||||
return {'user_ids': res}
|
return {'user_ids': res}
|
||||||
|
|
||||||
|
|
||||||
def change_password_button(self, cr, uid, id, context=None):
|
def change_password_button(self, cr, uid, id, context=None):
|
||||||
wizard = self.browse(cr, uid, id, context=context)[0]
|
wizard = self.browse(cr, uid, id, context=context)[0]
|
||||||
user_ids = []
|
need_reload = any(uid == user.user_id.id for user in wizard.user_ids)
|
||||||
for user in wizard.user_ids:
|
line_ids = [user.id for user in wizard.user_ids]
|
||||||
user_ids.append(user.id)
|
|
||||||
self.pool.get('change.password.user').change_password_button(cr, uid, user_ids, context=context)
|
self.pool.get('change.password.user').change_password_button(cr, uid, line_ids, context=context)
|
||||||
# don't keep temporary password copies in the database longer than necessary
|
# don't keep temporary password copies in the database longer than necessary
|
||||||
self.pool.get('change.password.user').unlink(cr, uid, user_ids)
|
self.pool.get('change.password.user').write(cr, uid, line_ids, {'new_passwd': False}, context=context)
|
||||||
|
|
||||||
|
if need_reload:
|
||||||
return {
|
return {
|
||||||
'type': 'ir.actions.act_window_close',
|
'type': 'ir.actions.client',
|
||||||
|
'tag': 'reload'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {'type': 'ir.actions.act_window_close'}
|
||||||
|
|
||||||
class change_password_user(osv.TransientModel):
|
class change_password_user(osv.TransientModel):
|
||||||
"""
|
"""
|
||||||
A model to configure users in the change password wizard
|
A model to configure users in the change password wizard
|
||||||
|
|
|
@ -9,6 +9,7 @@ class test_base(common.TransactionCase):
|
||||||
super(test_base,self).setUp()
|
super(test_base,self).setUp()
|
||||||
self.res_partner = self.registry('res.partner')
|
self.res_partner = self.registry('res.partner')
|
||||||
self.res_users = self.registry('res.users')
|
self.res_users = self.registry('res.users')
|
||||||
|
self.res_partner_title = self.registry('res.partner.title')
|
||||||
|
|
||||||
# samples use effective TLDs from the Mozilla public suffix
|
# samples use effective TLDs from the Mozilla public suffix
|
||||||
# list at http://publicsuffix.org
|
# list at http://publicsuffix.org
|
||||||
|
@ -285,27 +286,99 @@ class test_base(common.TransactionCase):
|
||||||
|
|
||||||
def test_60_read_group(self):
|
def test_60_read_group(self):
|
||||||
cr, uid = self.cr, self.uid
|
cr, uid = self.cr, self.uid
|
||||||
for user_data in [
|
title_sir = self.res_partner_title.create(cr, uid, {'name': 'Sir', 'domain': 'contact'})
|
||||||
{'name': 'Alice', 'login': 'alice', 'color': 1, 'function': 'Friend'},
|
title_lady = self.res_partner_title.create(cr, uid, {'name': 'Lady', 'domain': 'contact'})
|
||||||
{'name': 'Bob', 'login': 'bob', 'color': 2, 'function': 'Friend'},
|
test_users = [
|
||||||
{'name': 'Eve', 'login': 'eve', 'color': 3, 'function': 'Eavesdropper'},
|
{'name': 'Alice', 'login': 'alice', 'color': 1, 'function': 'Friend', 'date': '2015-03-28', 'title': title_lady},
|
||||||
{'name': 'Nab', 'login': 'nab', 'color': 2, 'function': '5$ Wrench'},
|
{'name': 'Alice', 'login': 'alice2', 'color': 0, 'function': 'Friend', 'date': '2015-01-28', 'title': title_lady},
|
||||||
]:
|
{'name': 'Bob', 'login': 'bob', 'color': 2, 'function': 'Friend', 'date': '2015-03-02', 'title': title_sir},
|
||||||
self.res_users.create(cr, uid, user_data)
|
{'name': 'Eve', 'login': 'eve', 'color': 3, 'function': 'Eavesdropper', 'date': '2015-03-20', 'title': title_lady},
|
||||||
|
{'name': 'Nab', 'login': 'nab', 'color': -3, 'function': '5$ Wrench', 'date': '2014-09-10', 'title': title_sir},
|
||||||
|
{'name': 'Nab', 'login': 'nab-she', 'color': 6, 'function': '5$ Wrench', 'date': '2014-01-02', 'title': title_lady},
|
||||||
|
]
|
||||||
|
ids = [self.res_users.create(cr, uid, u) for u in test_users]
|
||||||
|
domain = [('id', 'in', ids)]
|
||||||
|
|
||||||
groups_data = self.res_users.read_group(cr, uid, domain=[('login', 'in', ('alice', 'bob', 'eve'))], fields=['name', 'color', 'function'], groupby='function')
|
# group on local char field without domain and without active_test (-> empty WHERE clause)
|
||||||
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
|
groups_data = self.res_users.read_group(cr, uid, [], fields=['login'], groupby=['login'], orderby='login DESC', context={'active_test': False})
|
||||||
|
self.assertGreater(len(groups_data), 6, "Incorrect number of results when grouping on a field")
|
||||||
|
|
||||||
|
# group on local char field with limit
|
||||||
|
groups_data = self.res_users.read_group(cr, uid, domain, fields=['login'], groupby=['login'], orderby='login DESC', limit=3, offset=3)
|
||||||
|
self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field with limit")
|
||||||
|
self.assertEqual(['bob', 'alice2', 'alice'], [g['login'] for g in groups_data], 'Result mismatch')
|
||||||
|
|
||||||
|
# group on inherited char field, aggregate on int field (second groupby ignored on purpose)
|
||||||
|
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color', 'function'], groupby=['function', 'login'])
|
||||||
|
self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field")
|
||||||
|
self.assertEqual(['5$ Wrench', 'Eavesdropper', 'Friend'], [g['function'] for g in groups_data], 'incorrect read_group order')
|
||||||
for group_data in groups_data:
|
for group_data in groups_data:
|
||||||
self.assertIn('color', group_data, "Aggregated data for the column 'color' is not present in read_group return values")
|
self.assertIn('color', group_data, "Aggregated data for the column 'color' is not present in read_group return values")
|
||||||
self.assertEqual(group_data['color'], 3, "Incorrect sum for aggregated data for the column 'color'")
|
self.assertEqual(group_data['color'], 3, "Incorrect sum for aggregated data for the column 'color'")
|
||||||
|
|
||||||
groups_data = self.res_users.read_group(cr, uid, domain=[('login', 'in', ('alice', 'bob', 'eve'))], fields=['name', 'color'], groupby='name', orderby='name DESC, color asc')
|
# group on inherited char field, reverse order
|
||||||
self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field")
|
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color'], groupby='name', orderby='name DESC')
|
||||||
self.assertEqual([user['name'] for user in groups_data], ['Eve', 'Bob', 'Alice'], 'Incorrect ordering of the list')
|
self.assertEqual(['Nab', 'Eve', 'Bob', 'Alice'], [g['name'] for g in groups_data], 'Incorrect ordering of the list')
|
||||||
|
|
||||||
|
# group on int field, default ordering
|
||||||
|
groups_data = self.res_users.read_group(cr, uid, domain, fields=['color'], groupby='color')
|
||||||
|
self.assertEqual([-3, 0, 1, 2, 3, 6], [g['color'] for g in groups_data], 'Incorrect ordering of the list')
|
||||||
|
|
||||||
|
# multi group, second level is int field, should still be summed in first level grouping
|
||||||
|
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color'], groupby=['name', 'color'], orderby='name DESC')
|
||||||
|
self.assertEqual(['Nab', 'Eve', 'Bob', 'Alice'], [g['name'] for g in groups_data], 'Incorrect ordering of the list')
|
||||||
|
self.assertEqual([3, 3, 2, 1], [g['color'] for g in groups_data], 'Incorrect ordering of the list')
|
||||||
|
|
||||||
|
# group on inherited char field, multiple orders with directions
|
||||||
|
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color'], groupby='name', orderby='color DESC, name')
|
||||||
|
self.assertEqual(len(groups_data), 4, "Incorrect number of results when grouping on a field")
|
||||||
|
self.assertEqual(['Eve', 'Nab', 'Bob', 'Alice'], [g['name'] for g in groups_data], 'Incorrect ordering of the list')
|
||||||
|
self.assertEqual([1, 2, 1, 2], [g['name_count'] for g in groups_data], 'Incorrect number of results')
|
||||||
|
|
||||||
|
# group on inherited date column (res_partner.date) -> Year-Month, default ordering
|
||||||
|
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'date'], groupby=['date'])
|
||||||
|
self.assertEqual(len(groups_data), 4, "Incorrect number of results when grouping on a field")
|
||||||
|
self.assertEqual(['January 2014', 'September 2014', 'January 2015', 'March 2015'], [g['date'] for g in groups_data], 'Incorrect ordering of the list')
|
||||||
|
self.assertEqual([1, 1, 1, 3], [g['date_count'] for g in groups_data], 'Incorrect number of results')
|
||||||
|
|
||||||
|
# group on inherited date column (res_partner.date) -> Year-Month, custom order
|
||||||
|
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'date'], groupby=['date'], orderby='date DESC')
|
||||||
|
self.assertEqual(len(groups_data), 4, "Incorrect number of results when grouping on a field")
|
||||||
|
self.assertEqual(['March 2015', 'January 2015', 'September 2014', 'January 2014'], [g['date'] for g in groups_data], 'Incorrect ordering of the list')
|
||||||
|
self.assertEqual([3, 1, 1, 1], [g['date_count'] for g in groups_data], 'Incorrect number of results')
|
||||||
|
|
||||||
|
# group on inherited many2one (res_partner.title), default order
|
||||||
|
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'])
|
||||||
|
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
|
||||||
|
# m2o is returned as a (id, label) pair
|
||||||
|
self.assertEqual([(title_lady, 'Lady'), (title_sir, 'Sir')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
|
||||||
|
self.assertEqual([4, 2], [g['title_count'] for g in groups_data], 'Incorrect number of results')
|
||||||
|
self.assertEqual([10, -1], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
|
||||||
|
|
||||||
|
# group on inherited many2one (res_partner.title), reversed natural order
|
||||||
|
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'], orderby="title desc")
|
||||||
|
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
|
||||||
|
# m2o is returned as a (id, label) pair
|
||||||
|
self.assertEqual([(title_sir, 'Sir'), (title_lady, 'Lady')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
|
||||||
|
self.assertEqual([2, 4], [g['title_count'] for g in groups_data], 'Incorrect number of results')
|
||||||
|
self.assertEqual([-1, 10], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
|
||||||
|
|
||||||
|
# group on inherited many2one (res_partner.title), multiple orders with m2o in second position
|
||||||
|
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'], orderby="color desc, title desc")
|
||||||
|
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
|
||||||
|
# m2o is returned as a (id, label) pair
|
||||||
|
self.assertEqual([(title_lady, 'Lady'), (title_sir, 'Sir')], [g['title'] for g in groups_data], 'Incorrect ordering of the result')
|
||||||
|
self.assertEqual([4, 2], [g['title_count'] for g in groups_data], 'Incorrect number of results')
|
||||||
|
self.assertEqual([10, -1], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
|
||||||
|
|
||||||
|
# group on inherited many2one (res_partner.title), ordered by other inherited field (color)
|
||||||
|
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'], orderby='color')
|
||||||
|
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
|
||||||
|
# m2o is returned as a (id, label) pair
|
||||||
|
self.assertEqual([(title_sir, 'Sir'), (title_lady, 'Lady')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
|
||||||
|
self.assertEqual([2, 4], [g['title_count'] for g in groups_data], 'Incorrect number of results')
|
||||||
|
self.assertEqual([-1, 10], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
|
||||||
|
|
||||||
groups_data = self.res_users.read_group(cr, uid, domain=[('login', 'in', ('alice', 'bob', 'eve', 'nab'))], fields=['function', 'color'], groupby='function', orderby='color ASC')
|
|
||||||
self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field")
|
|
||||||
self.assertEqual(groups_data, sorted(groups_data, key=lambda x: x['color']), 'Incorrect ordering of the list')
|
|
||||||
|
|
||||||
class test_partner_recursion(common.TransactionCase):
|
class test_partner_recursion(common.TransactionCase):
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
# OPENERP_DATABASE=yy PYTHONPATH=../:. unit2 test_ir_sequence
|
# OPENERP_DATABASE=yy PYTHONPATH=../:. unit2 test_ir_sequence
|
||||||
# This assume an existing database.
|
# This assume an existing database.
|
||||||
import psycopg2
|
import psycopg2
|
||||||
|
import psycopg2.errorcodes
|
||||||
import unittest2
|
import unittest2
|
||||||
|
|
||||||
import openerp
|
import openerp
|
||||||
|
@ -111,11 +112,11 @@ class test_ir_sequence_no_gap(unittest2.TestCase):
|
||||||
cr0 = cursor()
|
cr0 = cursor()
|
||||||
cr1 = cursor()
|
cr1 = cursor()
|
||||||
cr1._default_log_exceptions = False # Prevent logging a traceback
|
cr1._default_log_exceptions = False # Prevent logging a traceback
|
||||||
msg_re = '^could not obtain lock on row in relation "ir_sequence"$'
|
with self.assertRaises(psycopg2.OperationalError) as e:
|
||||||
with self.assertRaisesRegexp(psycopg2.OperationalError, msg_re):
|
|
||||||
n0 = registry('ir.sequence').next_by_code(cr0, ADMIN_USER_ID, 'test_sequence_type_2', {})
|
n0 = registry('ir.sequence').next_by_code(cr0, ADMIN_USER_ID, 'test_sequence_type_2', {})
|
||||||
assert n0
|
assert n0
|
||||||
n1 = registry('ir.sequence').next_by_code(cr1, ADMIN_USER_ID, 'test_sequence_type_2', {})
|
n1 = registry('ir.sequence').next_by_code(cr1, ADMIN_USER_ID, 'test_sequence_type_2', {})
|
||||||
|
self.assertEqual(e.exception.pgcode, psycopg2.errorcodes.LOCK_NOT_AVAILABLE, msg="postgresql returned an incorrect errcode")
|
||||||
cr0.close()
|
cr0.close()
|
||||||
cr1.close()
|
cr1.close()
|
||||||
|
|
||||||
|
|
|
@ -309,8 +309,9 @@ class test_m2o(CreatorCase):
|
||||||
def test_external_id(self):
|
def test_external_id(self):
|
||||||
integer_id = self.registry('export.integer').create(
|
integer_id = self.registry('export.integer').create(
|
||||||
self.cr, openerp.SUPERUSER_ID, {'value': 42})
|
self.cr, openerp.SUPERUSER_ID, {'value': 42})
|
||||||
# __export__.$class.$id
|
# Expecting the m2o target model name in the external id,
|
||||||
external_id = u'__export__.export_many2one_%d' % integer_id
|
# not this model's name
|
||||||
|
external_id = u'__export__.export_integer_%d' % integer_id
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.export(integer_id, fields=['value/id']),
|
self.export(integer_id, fields=['value/id']),
|
||||||
[[external_id]])
|
[[external_id]])
|
||||||
|
|
|
@ -133,6 +133,10 @@ class WebRequest(object):
|
||||||
self.auth_method = None
|
self.auth_method = None
|
||||||
self._cr_cm = None
|
self._cr_cm = None
|
||||||
self._cr = None
|
self._cr = None
|
||||||
|
|
||||||
|
# prevents transaction commit, use when you catch an exception during handling
|
||||||
|
self._failed = None
|
||||||
|
|
||||||
# set db/uid trackers - they're cleaned up at the WSGI
|
# set db/uid trackers - they're cleaned up at the WSGI
|
||||||
# dispatching phase in openerp.service.wsgi_server.application
|
# dispatching phase in openerp.service.wsgi_server.application
|
||||||
if self.db:
|
if self.db:
|
||||||
|
@ -180,10 +184,13 @@ class WebRequest(object):
|
||||||
_request_stack.pop()
|
_request_stack.pop()
|
||||||
|
|
||||||
if self._cr:
|
if self._cr:
|
||||||
# Dont commit test cursors
|
# Dont close test cursors
|
||||||
if not openerp.tests.common.release_test_cursor(self._cr):
|
if not openerp.tests.common.release_test_cursor(self._cr):
|
||||||
if exc_type is None:
|
if exc_type is None and not self._failed:
|
||||||
self._cr.commit()
|
self._cr.commit()
|
||||||
|
else:
|
||||||
|
# just to be explicit - happens at close() anyway
|
||||||
|
self._cr.rollback()
|
||||||
self._cr.close()
|
self._cr.close()
|
||||||
# just to be sure no one tries to re-use the request
|
# just to be sure no one tries to re-use the request
|
||||||
self.disable_db = True
|
self.disable_db = True
|
||||||
|
@ -368,6 +375,7 @@ class JsonRequest(WebRequest):
|
||||||
'message': "OpenERP Session Invalid",
|
'message': "OpenERP Session Invalid",
|
||||||
'data': se
|
'data': se
|
||||||
}
|
}
|
||||||
|
self._failed = e # prevent tx commit
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
# Mute test cursor error for runbot
|
# Mute test cursor error for runbot
|
||||||
if not (openerp.tools.config['test_enable'] and isinstance(e, psycopg2.OperationalError)):
|
if not (openerp.tools.config['test_enable'] and isinstance(e, psycopg2.OperationalError)):
|
||||||
|
@ -378,6 +386,7 @@ class JsonRequest(WebRequest):
|
||||||
'message': "OpenERP Server Error",
|
'message': "OpenERP Server Error",
|
||||||
'data': se
|
'data': se
|
||||||
}
|
}
|
||||||
|
self._failed = e # prevent tx commit
|
||||||
if error:
|
if error:
|
||||||
response["error"] = error
|
response["error"] = error
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# OpenERP, Open Source Management Solution
|
# OpenERP, Open Source Management Solution
|
||||||
# Copyright (C) 2004-2012 OpenERP SA (<http://www.openerp.com>)
|
# Copyright (C) 2004-2014 OpenERP SA (<http://www.openerp.com>)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -89,7 +89,13 @@ class ColoredFormatter(DBFormatter):
|
||||||
record.levelname = COLOR_PATTERN % (30 + fg_color, 40 + bg_color, record.levelname)
|
record.levelname = COLOR_PATTERN % (30 + fg_color, 40 + bg_color, record.levelname)
|
||||||
return DBFormatter.format(self, record)
|
return DBFormatter.format(self, record)
|
||||||
|
|
||||||
|
_logger_init = False
|
||||||
def init_logger():
|
def init_logger():
|
||||||
|
global _logger_init
|
||||||
|
if _logger_init:
|
||||||
|
return
|
||||||
|
_logger_init = True
|
||||||
|
|
||||||
from tools.translate import resetlocale
|
from tools.translate import resetlocale
|
||||||
resetlocale()
|
resetlocale()
|
||||||
|
|
||||||
|
@ -137,6 +143,8 @@ def init_logger():
|
||||||
formatter = DBFormatter(format)
|
formatter = DBFormatter(format)
|
||||||
handler.setFormatter(formatter)
|
handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
logging.getLogger().addHandler(handler)
|
||||||
|
|
||||||
# Configure handlers
|
# Configure handlers
|
||||||
pseudo_config = PSEUDOCONFIG_MAPPER.get(tools.config['log_level'], [])
|
pseudo_config = PSEUDOCONFIG_MAPPER.get(tools.config['log_level'], [])
|
||||||
|
|
||||||
|
@ -147,11 +155,7 @@ def init_logger():
|
||||||
loggername, level = logconfig_item.split(':')
|
loggername, level = logconfig_item.split(':')
|
||||||
level = getattr(logging, level, logging.INFO)
|
level = getattr(logging, level, logging.INFO)
|
||||||
logger = logging.getLogger(loggername)
|
logger = logging.getLogger(loggername)
|
||||||
logger.handlers = []
|
|
||||||
logger.setLevel(level)
|
logger.setLevel(level)
|
||||||
logger.addHandler(handler)
|
|
||||||
if loggername != '':
|
|
||||||
logger.propagate = False
|
|
||||||
|
|
||||||
for logconfig_item in logging_configurations:
|
for logconfig_item in logging_configurations:
|
||||||
_logger.debug('logger level set: "%s"', logconfig_item)
|
_logger.debug('logger level set: "%s"', logconfig_item)
|
||||||
|
|
|
@ -75,7 +75,7 @@ _schema = logging.getLogger(__name__ + '.schema')
|
||||||
# List of etree._Element subclasses that we choose to ignore when parsing XML.
|
# List of etree._Element subclasses that we choose to ignore when parsing XML.
|
||||||
from openerp.tools import SKIPPED_ELEMENT_TYPES
|
from openerp.tools import SKIPPED_ELEMENT_TYPES
|
||||||
|
|
||||||
regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
|
regex_order = re.compile('^( *([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
|
||||||
regex_object_name = re.compile(r'^[a-z0-9_.]+$')
|
regex_object_name = re.compile(r'^[a-z0-9_.]+$')
|
||||||
|
|
||||||
AUTOINIT_RECALCULATE_STORED_FIELDS = 1000
|
AUTOINIT_RECALCULATE_STORED_FIELDS = 1000
|
||||||
|
@ -1124,7 +1124,7 @@ class BaseModel(object):
|
||||||
|
|
||||||
def _get_xml_id(self, cr, uid, r):
|
def _get_xml_id(self, cr, uid, r):
|
||||||
model_data = self.pool.get('ir.model.data')
|
model_data = self.pool.get('ir.model.data')
|
||||||
data_ids = model_data.search(cr, uid, [('model', '=', r._table_name), ('res_id', '=', r['id'])])
|
data_ids = model_data.search(cr, uid, [('model', '=', r._model._name), ('res_id', '=', r['id'])])
|
||||||
if len(data_ids):
|
if len(data_ids):
|
||||||
d = model_data.read(cr, uid, data_ids, ['name', 'module'])[0]
|
d = model_data.read(cr, uid, data_ids, ['name', 'module'])[0]
|
||||||
if d['module']:
|
if d['module']:
|
||||||
|
@ -1134,13 +1134,13 @@ class BaseModel(object):
|
||||||
else:
|
else:
|
||||||
postfix = 0
|
postfix = 0
|
||||||
while True:
|
while True:
|
||||||
n = self._table+'_'+str(r['id']) + (postfix and ('_'+str(postfix)) or '' )
|
n = r._model._table+'_'+str(r['id']) + (postfix and ('_'+str(postfix)) or '' )
|
||||||
if not model_data.search(cr, uid, [('name', '=', n)]):
|
if not model_data.search(cr, uid, [('name', '=', n)]):
|
||||||
break
|
break
|
||||||
postfix += 1
|
postfix += 1
|
||||||
model_data.create(cr, SUPERUSER_ID, {
|
model_data.create(cr, SUPERUSER_ID, {
|
||||||
'name': n,
|
'name': n,
|
||||||
'model': self._name,
|
'model': r._model._name,
|
||||||
'res_id': r['id'],
|
'res_id': r['id'],
|
||||||
'module': '__export__',
|
'module': '__export__',
|
||||||
})
|
})
|
||||||
|
@ -2185,36 +2185,42 @@ class BaseModel(object):
|
||||||
r['__fold'] = folded.get(r[groupby] and r[groupby][0], False)
|
r['__fold'] = folded.get(r[groupby] and r[groupby][0], False)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _read_group_generate_order_by(self, orderby, aggregated_fields, groupby, query):
|
def _read_group_prepare(self, orderby, aggregated_fields, groupby, qualified_groupby_field, query, groupby_type=None):
|
||||||
"""
|
"""
|
||||||
Generates the ORDER BY sql clause for the read group method. Adds the missing JOIN clause
|
Prepares the GROUP BY and ORDER BY terms for the read_group method. Adds the missing JOIN clause
|
||||||
to the query if order should be computed against m2o field.
|
to the query if order should be computed against m2o field.
|
||||||
:param orderby: the orderby definition in the form "%(field)s %(order)s"
|
:param orderby: the orderby definition in the form "%(field)s %(order)s"
|
||||||
:param aggregated_fields: list of aggregated fields in the query
|
:param aggregated_fields: list of aggregated fields in the query
|
||||||
:param groupby: the current groupby field name
|
:param groupby: the current groupby field name
|
||||||
:param query: the query object used to construct the query afterwards
|
:param qualified_groupby_field: the fully qualified SQL name for the grouped field
|
||||||
|
:param osv.Query query: the query under construction
|
||||||
|
:param groupby_type: the type of the grouped field
|
||||||
|
:return: (groupby_terms, orderby_terms)
|
||||||
"""
|
"""
|
||||||
orderby_list = []
|
orderby_terms = []
|
||||||
ob = []
|
groupby_terms = [qualified_groupby_field] if groupby else []
|
||||||
for order_splits in orderby.split(','):
|
if not orderby:
|
||||||
order_split = order_splits.split()
|
return groupby_terms, orderby_terms
|
||||||
orderby_field = order_split[0]
|
|
||||||
fields = openerp.osv.fields
|
|
||||||
if isinstance(self._all_columns[orderby_field].column, (fields.date, fields.datetime)):
|
|
||||||
continue
|
|
||||||
orderby_dir = len(order_split) == 2 and order_split[1].upper() == 'ASC' and 'ASC' or 'DESC'
|
|
||||||
if orderby_field == groupby:
|
|
||||||
orderby_item = self._generate_order_by(order_splits, query).replace('ORDER BY ', '')
|
|
||||||
if orderby_item:
|
|
||||||
orderby_list.append(orderby_item)
|
|
||||||
ob += [obi.split()[0] for obi in orderby_item.split(',')]
|
|
||||||
elif orderby_field in aggregated_fields:
|
|
||||||
orderby_list.append('%s %s' % (orderby_field,orderby_dir))
|
|
||||||
|
|
||||||
if orderby_list:
|
self._check_qorder(orderby)
|
||||||
return ' ORDER BY %s' % (','.join(orderby_list)), ob and ','.join(ob) or ''
|
for order_part in orderby.split(','):
|
||||||
|
order_split = order_part.split()
|
||||||
|
order_field = order_split[0]
|
||||||
|
if order_field == groupby:
|
||||||
|
if groupby_type == 'many2one':
|
||||||
|
order_clause = self._generate_order_by(order_part, query).replace('ORDER BY ', '')
|
||||||
|
if order_clause:
|
||||||
|
orderby_terms.append(order_clause)
|
||||||
|
groupby_terms += [order_term.split()[0] for order_term in order_clause.split(',')]
|
||||||
else:
|
else:
|
||||||
return '', ''
|
orderby_terms.append(order_part)
|
||||||
|
elif order_field in aggregated_fields:
|
||||||
|
orderby_terms.append(order_part)
|
||||||
|
else:
|
||||||
|
# Cannot order by a field that will not appear in the results (needs to be grouped or aggregated)
|
||||||
|
_logger.warn('%s: read_group order by `%s` ignored, cannot sort on empty columns (not grouped/aggregated)',
|
||||||
|
self._name, order_part)
|
||||||
|
return groupby_terms, orderby_terms
|
||||||
|
|
||||||
def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
|
def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
|
||||||
"""
|
"""
|
||||||
|
@ -2275,9 +2281,9 @@ class BaseModel(object):
|
||||||
|
|
||||||
# TODO it seems fields_get can be replaced by _all_columns (no need for translation)
|
# TODO it seems fields_get can be replaced by _all_columns (no need for translation)
|
||||||
fget = self.fields_get(cr, uid, fields)
|
fget = self.fields_get(cr, uid, fields)
|
||||||
flist = ''
|
|
||||||
group_count = group_by = groupby
|
|
||||||
group_by_params = {}
|
group_by_params = {}
|
||||||
|
select_terms = []
|
||||||
|
groupby_type = None
|
||||||
if groupby:
|
if groupby:
|
||||||
if fget.get(groupby):
|
if fget.get(groupby):
|
||||||
groupby_type = fget[groupby]['type']
|
groupby_type = fget[groupby]['type']
|
||||||
|
@ -2305,12 +2311,9 @@ class BaseModel(object):
|
||||||
timezone = context.get('tz', 'UTC')
|
timezone = context.get('tz', 'UTC')
|
||||||
qualified_groupby_field = "timezone('%s', timezone('UTC',%s))" % (timezone, qualified_groupby_field)
|
qualified_groupby_field = "timezone('%s', timezone('UTC',%s))" % (timezone, qualified_groupby_field)
|
||||||
qualified_groupby_field = "date_trunc('%s', %s)" % (interval, qualified_groupby_field)
|
qualified_groupby_field = "date_trunc('%s', %s)" % (interval, qualified_groupby_field)
|
||||||
flist = "%s as %s " % (qualified_groupby_field, groupby)
|
|
||||||
elif groupby_type == 'boolean':
|
elif groupby_type == 'boolean':
|
||||||
qualified_groupby_field = "coalesce(%s,false)" % qualified_groupby_field
|
qualified_groupby_field = "coalesce(%s,false)" % qualified_groupby_field
|
||||||
flist = "%s as %s " % (qualified_groupby_field, groupby)
|
select_terms.append("%s as %s " % (qualified_groupby_field, groupby))
|
||||||
else:
|
|
||||||
flist = qualified_groupby_field
|
|
||||||
else:
|
else:
|
||||||
# Don't allow arbitrary values, as this would be a SQL injection vector!
|
# Don't allow arbitrary values, as this would be a SQL injection vector!
|
||||||
raise except_orm(_('Invalid group_by'),
|
raise except_orm(_('Invalid group_by'),
|
||||||
|
@ -2318,34 +2321,48 @@ class BaseModel(object):
|
||||||
|
|
||||||
aggregated_fields = [
|
aggregated_fields = [
|
||||||
f for f in fields
|
f for f in fields
|
||||||
if f not in ('id', 'sequence')
|
if f not in ('id', 'sequence', groupby)
|
||||||
if fget[f]['type'] in ('integer', 'float')
|
if fget[f]['type'] in ('integer', 'float')
|
||||||
if (f in self._all_columns and getattr(self._all_columns[f].column, '_classic_write'))]
|
if (f in self._all_columns and getattr(self._all_columns[f].column, '_classic_write'))]
|
||||||
for f in aggregated_fields:
|
for f in aggregated_fields:
|
||||||
group_operator = fget[f].get('group_operator', 'sum')
|
group_operator = fget[f].get('group_operator', 'sum')
|
||||||
if flist:
|
|
||||||
flist += ', '
|
|
||||||
qualified_field = self._inherits_join_calc(f, query)
|
qualified_field = self._inherits_join_calc(f, query)
|
||||||
flist += "%s(%s) AS %s" % (group_operator, qualified_field, f)
|
select_terms.append("%s(%s) AS %s" % (group_operator, qualified_field, f))
|
||||||
|
|
||||||
order = orderby or groupby
|
order = orderby or groupby or ''
|
||||||
orderby_clause = ''
|
groupby_terms, orderby_terms = self._read_group_prepare(order, aggregated_fields, groupby, qualified_groupby_field, query, groupby_type)
|
||||||
ob = ''
|
|
||||||
if order:
|
|
||||||
orderby_clause, ob = self._read_group_generate_order_by(order, aggregated_fields, groupby, query)
|
|
||||||
|
|
||||||
gb = groupby and (' GROUP BY ' + qualified_groupby_field) or ''
|
|
||||||
|
|
||||||
from_clause, where_clause, where_clause_params = query.get_sql()
|
from_clause, where_clause, where_clause_params = query.get_sql()
|
||||||
where_clause = where_clause and ' WHERE ' + where_clause
|
|
||||||
limit_str = limit and ' limit %d' % limit or ''
|
|
||||||
offset_str = offset and ' offset %d' % offset or ''
|
|
||||||
if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
|
if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
|
||||||
group_count = '_'
|
count_field = '_'
|
||||||
cr.execute('SELECT min(%s.id) AS id, count(%s.id) AS %s_count' % (self._table, self._table, group_count) + (flist and ',') + flist + ' FROM ' + from_clause + where_clause + gb + (ob and ',') + ob + orderby_clause + limit_str + offset_str, where_clause_params)
|
else:
|
||||||
alldata = {}
|
count_field = groupby
|
||||||
groupby = group_by
|
|
||||||
|
|
||||||
|
prefix_terms = lambda prefix, terms: (prefix + " " + ",".join(terms)) if terms else ''
|
||||||
|
prefix_term = lambda prefix, term: ('%s %s' % (prefix, term)) if term else ''
|
||||||
|
|
||||||
|
query = """
|
||||||
|
SELECT min(%(table)s.id) AS id, count(%(table)s.id) AS %(count_field)s_count
|
||||||
|
%(extra_fields)s
|
||||||
|
FROM %(from)s
|
||||||
|
%(where)s
|
||||||
|
%(groupby)s
|
||||||
|
%(orderby)s
|
||||||
|
%(limit)s
|
||||||
|
%(offset)s
|
||||||
|
""" % {
|
||||||
|
'table': self._table,
|
||||||
|
'count_field': count_field,
|
||||||
|
'extra_fields': prefix_terms(',', select_terms),
|
||||||
|
'from': from_clause,
|
||||||
|
'where': prefix_term('WHERE', where_clause),
|
||||||
|
'groupby': prefix_terms('GROUP BY', groupby_terms),
|
||||||
|
'orderby': prefix_terms('ORDER BY', orderby_terms),
|
||||||
|
'limit': prefix_term('LIMIT', int(limit) if limit else None),
|
||||||
|
'offset': prefix_term('OFFSET', int(offset) if limit else None),
|
||||||
|
}
|
||||||
|
cr.execute(query, where_clause_params)
|
||||||
|
alldata = {}
|
||||||
fetched_data = cr.dictfetchall()
|
fetched_data = cr.dictfetchall()
|
||||||
|
|
||||||
data_ids = []
|
data_ids = []
|
||||||
|
@ -2356,8 +2373,6 @@ class BaseModel(object):
|
||||||
data_ids.append(r['id'])
|
data_ids.append(r['id'])
|
||||||
del r['id']
|
del r['id']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if groupby:
|
if groupby:
|
||||||
data = self.read(cr, uid, data_ids, [groupby], context=context)
|
data = self.read(cr, uid, data_ids, [groupby], context=context)
|
||||||
# restore order of the search as read() uses the default _order (this is only for groups, so the footprint of data should be small):
|
# restore order of the search as read() uses the default _order (this is only for groups, so the footprint of data should be small):
|
||||||
|
@ -2847,7 +2862,7 @@ class BaseModel(object):
|
||||||
msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
|
msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
|
||||||
_schema.debug(msg, self._table, k, f._type)
|
_schema.debug(msg, self._table, k, f._type)
|
||||||
|
|
||||||
if isinstance(f, fields.many2one):
|
if isinstance(f, fields.many2one) or (isinstance(f, fields.function) and f._type == 'many2one' and f.store):
|
||||||
dest_model = self.pool[f._obj]
|
dest_model = self.pool[f._obj]
|
||||||
if dest_model._table != 'ir_actions':
|
if dest_model._table != 'ir_actions':
|
||||||
self._m2o_fix_foreign_key(cr, self._table, k, dest_model, f.ondelete)
|
self._m2o_fix_foreign_key(cr, self._table, k, dest_model, f.ondelete)
|
||||||
|
@ -2882,7 +2897,7 @@ class BaseModel(object):
|
||||||
todo_end.append((order, self._update_store, (f, k)))
|
todo_end.append((order, self._update_store, (f, k)))
|
||||||
|
|
||||||
# and add constraints if needed
|
# and add constraints if needed
|
||||||
if isinstance(f, fields.many2one):
|
if isinstance(f, fields.many2one) or (isinstance(f, fields.function) and f._type == 'many2one' and f.store):
|
||||||
if f._obj not in self.pool:
|
if f._obj not in self.pool:
|
||||||
raise except_orm('Programming Error', 'There is no reference available for %s' % (f._obj,))
|
raise except_orm('Programming Error', 'There is no reference available for %s' % (f._obj,))
|
||||||
dest_model = self.pool[f._obj]
|
dest_model = self.pool[f._obj]
|
||||||
|
|
|
@ -32,7 +32,7 @@ from random import randint
|
||||||
# Image resizing
|
# Image resizing
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
|
|
||||||
def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', filetype='PNG', avoid_if_small=False):
|
def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', filetype=None, avoid_if_small=False):
|
||||||
""" Function to resize an image. The image will be resized to the given
|
""" Function to resize an image. The image will be resized to the given
|
||||||
size, while keeping the aspect ratios, and holes in the image will be
|
size, while keeping the aspect ratios, and holes in the image will be
|
||||||
filled with transparent background. The image will not be stretched if
|
filled with transparent background. The image will not be stretched if
|
||||||
|
@ -58,7 +58,8 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
|
||||||
height mean an automatically computed value based respectivelly
|
height mean an automatically computed value based respectivelly
|
||||||
on height or width of the source image.
|
on height or width of the source image.
|
||||||
:param encoding: the output encoding
|
:param encoding: the output encoding
|
||||||
:param filetype: the output filetype
|
:param filetype: the output filetype, by default the source image's
|
||||||
|
:type filetype: str, any PIL image format (supported for creation)
|
||||||
:param avoid_if_small: do not resize if image height and width
|
:param avoid_if_small: do not resize if image height and width
|
||||||
are smaller than the expected size.
|
are smaller than the expected size.
|
||||||
"""
|
"""
|
||||||
|
@ -68,6 +69,8 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
|
||||||
return base64_source
|
return base64_source
|
||||||
image_stream = StringIO.StringIO(base64_source.decode(encoding))
|
image_stream = StringIO.StringIO(base64_source.decode(encoding))
|
||||||
image = Image.open(image_stream)
|
image = Image.open(image_stream)
|
||||||
|
# store filetype here, as Image.new below will lose image.format
|
||||||
|
filetype = filetype or image.format
|
||||||
|
|
||||||
asked_width, asked_height = size
|
asked_width, asked_height = size
|
||||||
if asked_width is None:
|
if asked_width is None:
|
||||||
|
@ -95,21 +98,21 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
|
||||||
image.save(background_stream, filetype)
|
image.save(background_stream, filetype)
|
||||||
return background_stream.getvalue().encode(encoding)
|
return background_stream.getvalue().encode(encoding)
|
||||||
|
|
||||||
def image_resize_image_big(base64_source, size=(1204, 1204), encoding='base64', filetype='PNG', avoid_if_small=True):
|
def image_resize_image_big(base64_source, size=(1204, 1024), encoding='base64', filetype=None, avoid_if_small=True):
|
||||||
""" Wrapper on image_resize_image, to resize images larger than the standard
|
""" Wrapper on image_resize_image, to resize images larger than the standard
|
||||||
'big' image size: 1024x1024px.
|
'big' image size: 1024x1024px.
|
||||||
:param size, encoding, filetype, avoid_if_small: refer to image_resize_image
|
:param size, encoding, filetype, avoid_if_small: refer to image_resize_image
|
||||||
"""
|
"""
|
||||||
return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
|
return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
|
||||||
|
|
||||||
def image_resize_image_medium(base64_source, size=(128, 128), encoding='base64', filetype='PNG', avoid_if_small=False):
|
def image_resize_image_medium(base64_source, size=(128, 128), encoding='base64', filetype=None, avoid_if_small=False):
|
||||||
""" Wrapper on image_resize_image, to resize to the standard 'medium'
|
""" Wrapper on image_resize_image, to resize to the standard 'medium'
|
||||||
image size: 180x180.
|
image size: 180x180.
|
||||||
:param size, encoding, filetype, avoid_if_small: refer to image_resize_image
|
:param size, encoding, filetype, avoid_if_small: refer to image_resize_image
|
||||||
"""
|
"""
|
||||||
return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
|
return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
|
||||||
|
|
||||||
def image_resize_image_small(base64_source, size=(64, 64), encoding='base64', filetype='PNG', avoid_if_small=False):
|
def image_resize_image_small(base64_source, size=(64, 64), encoding='base64', filetype=None, avoid_if_small=False):
|
||||||
""" Wrapper on image_resize_image, to resize to the standard 'small' image
|
""" Wrapper on image_resize_image, to resize to the standard 'small' image
|
||||||
size: 50x50.
|
size: 50x50.
|
||||||
:param size, encoding, filetype, avoid_if_small: refer to image_resize_image
|
:param size, encoding, filetype, avoid_if_small: refer to image_resize_image
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# Copyright (C) 2004-2012 OpenERP s.a. (<http://www.openerp.com>).
|
# Copyright (C) 2004-2014 OpenERP s.a. (<http://www.openerp.com>).
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -225,9 +225,14 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
|
||||||
'False': False,
|
'False': False,
|
||||||
'None': None,
|
'None': None,
|
||||||
'str': str,
|
'str': str,
|
||||||
|
'unicode': unicode,
|
||||||
'globals': locals,
|
'globals': locals,
|
||||||
'locals': locals,
|
'locals': locals,
|
||||||
'bool': bool,
|
'bool': bool,
|
||||||
|
'int': int,
|
||||||
|
'float': float,
|
||||||
|
'long': long,
|
||||||
|
'enumerate': enumerate,
|
||||||
'dict': dict,
|
'dict': dict,
|
||||||
'list': list,
|
'list': list,
|
||||||
'tuple': tuple,
|
'tuple': tuple,
|
||||||
|
@ -235,15 +240,23 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
|
||||||
'abs': abs,
|
'abs': abs,
|
||||||
'min': min,
|
'min': min,
|
||||||
'max': max,
|
'max': max,
|
||||||
|
'sum': sum,
|
||||||
'reduce': reduce,
|
'reduce': reduce,
|
||||||
'filter': filter,
|
'filter': filter,
|
||||||
'round': round,
|
'round': round,
|
||||||
'len': len,
|
'len': len,
|
||||||
'set': set,
|
|
||||||
'repr': repr,
|
'repr': repr,
|
||||||
'int': int,
|
'set': set,
|
||||||
'float': float,
|
'all': all,
|
||||||
|
'any': any,
|
||||||
|
'ord': ord,
|
||||||
|
'chr': chr,
|
||||||
|
'cmp': cmp,
|
||||||
|
'divmod': divmod,
|
||||||
|
'isinstance': isinstance,
|
||||||
'range': range,
|
'range': range,
|
||||||
|
'xrange': xrange,
|
||||||
|
'zip': zip,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if locals_builtins:
|
if locals_builtins:
|
||||||
|
|
Loading…
Reference in New Issue