Here is the exported translation file:
This file was generated using the universal Unicode/UTF-8 file encoding, please be sure to view and edit + using the same encoding.
+The next step depends on the file format: +
For more details about translating OpenERP in your language, please refer to the + documentation.
+${company.rml_footer1}
- %endif -${company.rml_footer2}
+ % if company['rml_footer']: +${company.rml_footer}
%endif|
-
- |
-
-
- |
yop
xxx' + self.assertEqual(x, html_sanitize(x)) - def test_test_case(self): + def test_no_exception(self): html_sanitize(test_case) - def test_crm(self): + def test_unicode(self): html_sanitize("Merci à l'intérêt pour notre produit.nous vous contacterons bientôt. Merci") if __name__ == '__main__': diff --git a/openerp/tests/test_misc.py b/openerp/tests/test_misc.py new file mode 100644 index 00000000000..54ae69899a6 --- /dev/null +++ b/openerp/tests/test_misc.py @@ -0,0 +1,51 @@ +# This test can be run stand-alone with something like: +# > PYTHONPATH=. python2 openerp/tests/test_misc.py + +import unittest2 +from ..tools import misc + +class append_content_to_html(unittest2.TestCase): + """ Test some of our generic utility functions """ + + def test_append_to_html(self): + test_samples = [ + ('some content', '--\nYours truly', True, + 'some content\n--\nYours truly\n'), + ('some content', '\n\n
--
\nYours truly
\n\n', False, + 'some content\n\n\n--
\nYours truly
\n\n\n'), + ] + for html, content, flag, expected in test_samples: + self.assertEqual(misc.append_content_to_html(html,content,flag), expected, 'append_content_to_html is broken') + +class test_countingstream(unittest2.TestCase): + def test_empty_stream(self): + s = misc.CountingStream(iter([])) + self.assertEqual(s.index, -1) + self.assertIsNone(next(s, None)) + self.assertEqual(s.index, 0) + + def test_single(self): + s = misc.CountingStream(xrange(1)) + self.assertEqual(s.index, -1) + self.assertEqual(next(s, None), 0) + self.assertIsNone(next(s, None)) + self.assertEqual(s.index, 1) + + def test_full(self): + s = misc.CountingStream(xrange(42)) + for _ in s: + pass + self.assertEqual(s.index, 42) + + def test_repeated(self): + """ Once the CountingStream has stopped iterating, the index should not + increase anymore (the internal state should not be allowed to change) + """ + s = misc.CountingStream(iter([])) + self.assertIsNone(next(s, None)) + self.assertEqual(s.index, 0) + self.assertIsNone(next(s, None)) + self.assertEqual(s.index, 0) + +if __name__ == '__main__': + unittest2.main() diff --git a/openerp/tests/test_orm.py b/openerp/tests/test_orm.py index ea9b37e5f3b..ef8c0cf19aa 100644 --- a/openerp/tests/test_orm.py +++ b/openerp/tests/test_orm.py @@ -1,4 +1,3 @@ -import os import unittest2 import openerp @@ -7,6 +6,85 @@ import common UID = common.ADMIN_USER_ID DB = common.DB +class TestInherits(common.TransactionCase): + """ test the behavior of the orm for models that use _inherits; + specifically: res.users, that inherits from res.partner + """ + + def setUp(self): + super(TestInherits, self).setUp() + self.partner = self.registry('res.partner') + self.user = self.registry('res.users') + + def test_create(self): + """ creating a user should automatically create a new partner """ + partners_before = self.partner.search(self.cr, UID, []) + foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'}) + foo = self.user.browse(self.cr, UID, foo_id) + + self.assertNotIn(foo.partner_id.id, partners_before) + + def test_create_with_ancestor(self): + """ creating a user with a specific 'partner_id' should not create a new partner """ + par_id = self.partner.create(self.cr, UID, {'name': 'Foo'}) + partners_before = self.partner.search(self.cr, UID, []) + foo_id = self.user.create(self.cr, UID, {'partner_id': par_id, 'login': 'foo', 'password': 'foo'}) + partners_after = self.partner.search(self.cr, UID, []) + + self.assertEqual(set(partners_before), set(partners_after)) + + foo = self.user.browse(self.cr, UID, foo_id) + self.assertEqual(foo.name, 'Foo') + self.assertEqual(foo.partner_id.id, par_id) + + def test_read(self): + """ inherited fields should be read without any indirection """ + foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'}) + foo_values, = self.user.read(self.cr, UID, [foo_id]) + partner_id = foo_values['partner_id'][0] + partner_values, = self.partner.read(self.cr, UID, [partner_id]) + self.assertEqual(foo_values['name'], partner_values['name']) + + foo = self.user.browse(self.cr, UID, foo_id) + self.assertEqual(foo.name, foo.partner_id.name) + + def test_copy(self): + """ copying a user should automatically copy its partner, too """ + foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'}) + foo_before, = self.user.read(self.cr, UID, [foo_id]) + bar_id = self.user.copy(self.cr, UID, foo_id, {'login': 'bar', 'password': 'bar'}) + foo_after, = self.user.read(self.cr, UID, [foo_id]) + + self.assertEqual(foo_before, foo_after) + + foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id]) + self.assertEqual(bar.login, 'bar') + self.assertNotEqual(foo.id, bar.id) + self.assertNotEqual(foo.partner_id.id, bar.partner_id.id) + + def test_copy_with_ancestor(self): + """ copying a user with 'parent_id' in defaults should not duplicate the partner """ + foo_id = self.user.create(self.cr, UID, {'name': 'Foo', 'login': 'foo', 'password': 'foo'}) + par_id = self.partner.create(self.cr, UID, {'name': 'Bar'}) + + foo_before, = self.user.read(self.cr, UID, [foo_id]) + partners_before = self.partner.search(self.cr, UID, []) + bar_id = self.user.copy(self.cr, UID, foo_id, {'partner_id': par_id, 'login': 'bar'}) + foo_after, = self.user.read(self.cr, UID, [foo_id]) + partners_after = self.partner.search(self.cr, UID, []) + + self.assertEqual(foo_before, foo_after) + self.assertEqual(set(partners_before), set(partners_after)) + + foo, bar = self.user.browse(self.cr, UID, [foo_id, bar_id]) + self.assertNotEqual(foo.id, bar.id) + self.assertEqual(bar.partner_id.id, par_id) + self.assertEqual(bar.login, 'bar', "login is given from copy parameters") + self.assertEqual(bar.password, foo.password, "password is given from original record") + self.assertEqual(bar.name, 'Bar', "name is given from specific partner") + + + CREATE = lambda values: (0, False, values) UPDATE = lambda id, values: (1, id, values) DELETE = lambda id: (2, id, False) @@ -15,7 +93,12 @@ LINK_TO = lambda id: (4, id, False) DELETE_ALL = lambda: (5, False, False) REPLACE_WITH = lambda ids: (6, False, ids) +def sorted_by_id(list_of_dicts): + "sort dictionaries by their 'id' field; useful for comparisons" + return sorted(list_of_dicts, key=lambda d: d.get('id')) + class TestO2MSerialization(common.TransactionCase): + """ test the orm method 'write' on one2many fields """ def setUp(self): super(TestO2MSerialization, self).setUp() @@ -23,21 +106,18 @@ class TestO2MSerialization(common.TransactionCase): def test_no_command(self): " empty list of commands yields an empty list of records " - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', []) self.assertEqual(results, []) def test_CREATE_commands(self): " returns the VALUES dict as-is " - results = self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', - map(CREATE, [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}])) - self.assertEqual(results, [ - {'foo': 'bar'}, - {'foo': 'baz'}, - {'foo': 'baq'} - ]) + values = [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}] + results = self.partner.resolve_2many_commands( + self.cr, UID, 'address', map(CREATE, values)) + + self.assertEqual(results, values) def test_LINK_TO_command(self): " reads the records from the database, records are returned with their ids. " @@ -48,14 +128,14 @@ class TestO2MSerialization(common.TransactionCase): ] commands = map(LINK_TO, ids) - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', commands, ['name']) - self.assertEqual(results, [ + self.assertEqual(sorted_by_id(results), sorted_by_id([ {'id': ids[0], 'name': 'foo'}, {'id': ids[1], 'name': 'bar'}, {'id': ids[2], 'name': 'baz'} - ]) + ])) def test_bare_ids_command(self): " same as the equivalent LINK_TO commands " @@ -65,14 +145,14 @@ class TestO2MSerialization(common.TransactionCase): self.partner.create(self.cr, UID, {'name': 'baz'}) ] - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', ids, ['name']) - self.assertEqual(results, [ + self.assertEqual(sorted_by_id(results), sorted_by_id([ {'id': ids[0], 'name': 'foo'}, {'id': ids[1], 'name': 'bar'}, {'id': ids[2], 'name': 'baz'} - ]) + ])) def test_UPDATE_command(self): " take the in-db records and merge the provided information in " @@ -80,18 +160,32 @@ class TestO2MSerialization(common.TransactionCase): id_bar = self.partner.create(self.cr, UID, {'name': 'bar'}) id_baz = self.partner.create(self.cr, UID, {'name': 'baz', 'city': 'tag'}) - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', [ LINK_TO(id_foo), UPDATE(id_bar, {'name': 'qux', 'city': 'tagtag'}), UPDATE(id_baz, {'name': 'quux'}) ], ['name', 'city']) - self.assertEqual(results, [ + self.assertEqual(sorted_by_id(results), sorted_by_id([ {'id': id_foo, 'name': 'foo', 'city': False}, {'id': id_bar, 'name': 'qux', 'city': 'tagtag'}, {'id': id_baz, 'name': 'quux', 'city': 'tag'} - ]) + ])) + + def test_DELETE_command(self): + " deleted records are not returned at all. " + ids = [ + self.partner.create(self.cr, UID, {'name': 'foo'}), + self.partner.create(self.cr, UID, {'name': 'bar'}), + self.partner.create(self.cr, UID, {'name': 'baz'}) + ] + commands = [DELETE(ids[0]), DELETE(ids[1]), DELETE(ids[2])] + + results = self.partner.resolve_2many_commands( + self.cr, UID, 'address', commands, ['name']) + + self.assertEqual(results, []) def test_mixed_commands(self): ids = [ @@ -99,28 +193,27 @@ class TestO2MSerialization(common.TransactionCase): for name in ['NObar', 'baz', 'qux', 'NOquux', 'NOcorge', 'garply'] ] - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', [ CREATE({'name': 'foo'}), UPDATE(ids[0], {'name': 'bar'}), LINK_TO(ids[1]), - LINK_TO(ids[2]), + DELETE(ids[2]), UPDATE(ids[3], {'name': 'quux',}), UPDATE(ids[4], {'name': 'corge'}), CREATE({'name': 'grault'}), LINK_TO(ids[5]) ], ['name']) - self.assertEqual(results, [ + self.assertEqual(sorted_by_id(results), sorted_by_id([ {'name': 'foo'}, {'id': ids[0], 'name': 'bar'}, {'id': ids[1], 'name': 'baz'}, - {'id': ids[2], 'name': 'qux'}, {'id': ids[3], 'name': 'quux'}, {'id': ids[4], 'name': 'corge'}, {'name': 'grault'}, {'id': ids[5], 'name': 'garply'} - ]) + ])) def test_LINK_TO_pairs(self): "LINK_TO commands can be written as pairs, instead of triplets" @@ -131,43 +224,20 @@ class TestO2MSerialization(common.TransactionCase): ] commands = map(lambda id: (4, id), ids) - results = self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_2many_commands( self.cr, UID, 'address', commands, ['name']) - self.assertEqual(results, [ + self.assertEqual(sorted_by_id(results), sorted_by_id([ {'id': ids[0], 'name': 'foo'}, {'id': ids[1], 'name': 'bar'}, {'id': ids[2], 'name': 'baz'} - ]) + ])) def test_singleton_commands(self): "DELETE_ALL can appear as a singleton" + results = self.partner.resolve_2many_commands( + self.cr, UID, 'address', [DELETE_ALL()], ['name']) - try: - self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [(5,)], ['name']) - except AssertionError: - # 5 should fail with an assert error, but not e.g. a ValueError - pass - - def test_invalid_commands(self): - "Commands with uncertain semantics in this context should be forbidden" - - with self.assertRaises(AssertionError): - self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [DELETE(42)], ['name']) - - with self.assertRaises(AssertionError): - self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [FORGET(42)], ['name']) - - with self.assertRaises(AssertionError): - self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [DELETE_ALL()], ['name']) - - with self.assertRaises(AssertionError): - self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [REPLACE_WITH([42])], ['name']) - + self.assertEqual(results, []) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tools/cache.py b/openerp/tools/cache.py index 2c5c4a46e0d..6e18007c340 100644 --- a/openerp/tools/cache.py +++ b/openerp/tools/cache.py @@ -43,7 +43,7 @@ class ormcache(object): return r except KeyError: self.stat_miss += 1 - value = d[args] = self.method(self2, cr, *args) + value = d[key] = self.method(self2, cr, *args) return value except TypeError: self.stat_err += 1 diff --git a/openerp/tools/config.py b/openerp/tools/config.py index 3a4329433eb..56f1e5c7b83 100644 --- a/openerp/tools/config.py +++ b/openerp/tools/config.py @@ -182,7 +182,7 @@ class configmanager(object): group.add_option('--log-handler', action="append", default=[':INFO'], my_default=[':INFO'], metavar="PREFIX:LEVEL", help='setup a handler at LEVEL for a given PREFIX. An empty PREFIX indicates the root logger. This option can be repeated. Example: "openerp.orm:DEBUG" or "werkzeug:CRITICAL" (default: ":INFO")') group.add_option('--log-request', action="append_const", dest="log_handler", const="openerp.netsvc.rpc.request:DEBUG", help='shortcut for --log-handler=openerp.netsvc.rpc.request:DEBUG') group.add_option('--log-response', action="append_const", dest="log_handler", const="openerp.netsvc.rpc.response:DEBUG", help='shortcut for --log-handler=openerp.netsvc.rpc.response:DEBUG') - group.add_option('--log-web', action="append_const", dest="log_handler", const="openerp.addons.web.common.http:DEBUG", help='shortcut for --log-handler=openerp.addons.web.common.http:DEBUG') + group.add_option('--log-web', action="append_const", dest="log_handler", const="openerp.addons.web.http:DEBUG", help='shortcut for --log-handler=openerp.addons.web.http:DEBUG') group.add_option('--log-sql', action="append_const", dest="log_handler", const="openerp.sql_db:DEBUG", help='shortcut for --log-handler=openerp.sql_db:DEBUG') # For backward-compatibility, map the old log levels to something # quite close. @@ -223,7 +223,7 @@ class configmanager(object): help="specify the database port", type="int") group.add_option("--db_maxconn", dest="db_maxconn", type='int', my_default=64, help="specify the the maximum number of physical connections to posgresql") - group.add_option("--db-template", dest="db_template", my_default="template0", + group.add_option("--db-template", dest="db_template", my_default="template1", help="specify a custom database template to create a new database") parser.add_option_group(group) @@ -253,8 +253,6 @@ class configmanager(object): # Advanced options group = optparse.OptionGroup(parser, "Advanced options") - group.add_option("--cache-timeout", dest="cache_timeout", my_default=100000, - help="set the timeout for the cache system", type="int") group.add_option('--debug', dest='debug_mode', action='store_true', my_default=False, help='enable debug mode') group.add_option("--stop-after-init", action="store_true", dest="stop_after_init", my_default=False, help="stop the server after its initialization") @@ -272,23 +270,30 @@ class configmanager(object): group.add_option("--max-cron-threads", dest="max_cron_threads", my_default=4, help="Maximum number of threads processing concurrently cron jobs.", type="int") - # TODO sensible default for the three following limits. - group.add_option("--virtual-memory-limit", dest="virtual_memory_limit", my_default=768 * 1024 * 1024, - help="Maximum allowed virtual memory per Gunicorn process. " - "When the limit is reached, any memory allocation will fail.", - type="int") - group.add_option("--virtual-memory-reset", dest="virtual_memory_reset", my_default=640 * 1024 * 1024, - help="Maximum allowed virtual memory per Gunicorn process. " - "When the limit is reached, the worker will be reset after " - "the current request.", - type="int") - group.add_option("--cpu-time-limit", dest="cpu_time_limit", my_default=60, - help="Maximum allowed CPU time per Gunicorn process. " - "When the limit is reached, an exception is raised.", - type="int") group.add_option("--unaccent", dest="unaccent", my_default=False, action="store_true", help="Use the unaccent function provided by the database when available.") + parser.add_option_group(group) + group = optparse.OptionGroup(parser, "Multiprocessing options") + # TODO sensible default for the three following limits. + group.add_option("--workers", dest="workers", my_default=0, + help="Specify the number of workers, 0 disable prefork mode.", + type="int") + group.add_option("--limit-memory-soft", dest="limit_memory_soft", my_default=640 * 1024 * 1024, + help="Maximum allowed virtual memory per worker, when reached the worker be reset after the current request.", + type="int") + group.add_option("--limit-memory-hard", dest="limit_memory_hard", my_default=768 * 1024 * 1024, + help="Maximum allowed virtual memory per worker, when reached, any memory allocation will fail.", + type="int") + group.add_option("--limit-time-cpu", dest="limit_time_cpu", my_default=60, + help="Maximum allowed CPU time per request.", + type="int") + group.add_option("--limit-time-real", dest="limit_time_real", my_default=60, + help="Maximum allowed Real time per request. ", + type="int") + group.add_option("--limit-request", dest="limit_request", my_default=8192, + help="Maximum number of request to be processed per worker.", + type="int") parser.add_option_group(group) # Copy all optparse options (i.e. MyOption) into self.options. @@ -369,7 +374,7 @@ class configmanager(object): # if defined dont take the configfile value even if the defined value is None keys = ['xmlrpc_interface', 'xmlrpc_port', 'db_name', 'db_user', 'db_password', 'db_host', - 'db_port', 'db_template', 'logfile', 'pidfile', 'smtp_port', 'cache_timeout', + 'db_port', 'db_template', 'logfile', 'pidfile', 'smtp_port', 'email_from', 'smtp_server', 'smtp_user', 'smtp_password', 'netrpc_interface', 'netrpc_port', 'db_maxconn', 'import_partial', 'addons_path', 'netrpc', 'xmlrpc', 'syslog', 'without_demo', 'timezone', @@ -393,8 +398,8 @@ class configmanager(object): 'stop_after_init', 'logrotate', 'without_demo', 'netrpc', 'xmlrpc', 'syslog', 'list_db', 'xmlrpcs', 'proxy_mode', 'test_file', 'test_enable', 'test_commit', 'test_report_directory', - 'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', - 'virtual_memory_limit', 'virtual_memory_reset', 'cpu_time_limit', 'unaccent', + 'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent', + 'workers', 'limit_memory_hard', 'limit_memory_soft', 'limit_time_cpu', 'limit_time_real', 'limit_request' ] for arg in keys: diff --git a/openerp/tools/convert.py b/openerp/tools/convert.py index be8465055ee..12ed269db8b 100644 --- a/openerp/tools/convert.py +++ b/openerp/tools/convert.py @@ -134,14 +134,14 @@ def _eval_xml(self, node, pool, cr, uid, idref, context=None): f_val = f_val[0] return f_val a_eval = node.get('eval','') - idref2 = {} if a_eval: idref2 = _get_idref(self, cr, uid, f_model, context, idref) try: return unsafe_eval(a_eval, idref2) except Exception: - _logger.warning('could not eval(%s) for %s in %s' % (a_eval, node.get('name'), context), exc_info=True) - return "" + logging.getLogger('openerp.tools.convert.init').error( + 'Could not eval(%s) for %s in %s', a_eval, node.get('name'), context) + raise def _process(s, idref): m = re.findall('[^%]%\((.*?)\)[ds]', s) for id in m: @@ -386,18 +386,8 @@ form: module.record_id""" % (xml_id,) res = {'name': name, 'url': url, 'target':target} - id = self.pool.get('ir.model.data')._update(cr, self.uid, "ir.actions.url", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode) + id = self.pool.get('ir.model.data')._update(cr, self.uid, "ir.actions.act_url", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode) self.idref[xml_id] = int(id) - # ir_set - if (not rec.get('menu') or eval(rec.get('menu','False'))) and id: - keyword = str(rec.get('keyword','') or 'client_action_multi') - value = 'ir.actions.url,'+str(id) - replace = rec.get("replace",'') or True - self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, url, ["ir.actions.url"], value, replace=replace, isobject=True, xml_id=xml_id) - elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False): - # Special check for URL having attribute menu=False on update - value = 'ir.actions.url,'+str(id) - self._remove_ir_values(cr, url, value, "ir.actions.url") def _tag_act_window(self, cr, rec, data_node=None): name = rec.get('name','').encode('utf-8') diff --git a/openerp/tools/html_sanitize.py b/openerp/tools/html_sanitize.py index 6763b464ab1..6ea7b90e2ba 100644 --- a/openerp/tools/html_sanitize.py +++ b/openerp/tools/html_sanitize.py @@ -1,62 +1,92 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (C) 2012 OpenERP S.A. (