[MERGE]sync with trunk
bzr revid: sgo@tinyerp.com-20130220045801-lu50cl1263s83252 bzr revid: sgo@tinyerp.com-20130221052617-nvqt5o7rtcsugzvg bzr revid: sgo@tinyerp.com-20130225050233-4bn9qv2mvp93b9tn bzr revid: sgo@tinyerp.com-20130225112150-e7pekthp8avygckp bzr revid: sgo@tinyerp.com-20130226051344-oufdzabzd9hgpe3m bzr revid: sgo@tinyerp.com-20130228045107-1bxgg16ufzihvftn bzr revid: sgo@tinyerp.com-20130305082735-dlrijuwctoti7zb7 bzr revid: sgo@tinyerp.com-20130306060028-zo3jhob82kwv5sus bzr revid: sgo@tinyerp.com-20130312084136-4595ofhftjyenw7s bzr revid: sgo@tinyerp.com-20130318054509-rl37g2kr37a9gdey bzr revid: sgo@tinyerp.com-20130319051009-xuy3az0gbywunuxc bzr revid: sgo@tinyerp.com-20130326101240-p6wpvi27rirdfjhc
This commit is contained in:
+1
-1
@@ -7,7 +7,7 @@ graft win32
|
||||
include README
|
||||
include LICENSE
|
||||
include MANIFEST.in
|
||||
include gunicorn.conf.py
|
||||
include openerp-wsgi.py
|
||||
include openerp-server
|
||||
include setup*
|
||||
global-exclude *pyc *~ # Exclude possible garbage from previous graft.
|
||||
|
||||
Vendored
+2
-2
@@ -52,6 +52,6 @@ Description: OpenERP Enterprise Resource Management
|
||||
features are accounting (analytic and financial), stock management, sales and
|
||||
purchases management, tasks automation, marketing campaigns, help desk, POS,
|
||||
etc. Technical features include a distributed server, flexible workflows, an
|
||||
object database, a dynamic GUI, customizable reports, and NET-RPC and XML-RPC
|
||||
interfaces.
|
||||
object database, a dynamic GUI, customizable reports, and an XML-RPC
|
||||
interface.
|
||||
|
||||
|
||||
Vendored
+2
-1
@@ -6,10 +6,11 @@ case "${1}" in
|
||||
remove)
|
||||
deluser --quiet --system "openerp" || true
|
||||
delgroup --quiet --system --only-if-empty "openerp" || true
|
||||
find /var/lib/openerp -path '/var/lib/openerp/*' ! -path '/var/lib/openerp/filestore*' -delete
|
||||
;;
|
||||
|
||||
purge)
|
||||
rm -rf /var/lib/openerp-server
|
||||
rm -rf /var/lib/openerp
|
||||
;;
|
||||
|
||||
upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
|
||||
|
||||
@@ -103,9 +103,6 @@ General Options
|
||||
--logfile=LOGFILE file where the server log will be stored
|
||||
-n INTERFACE, --interface=INTERFACE specify the TCP IP address
|
||||
-p PORT, --port=PORT specify the TCP port
|
||||
--net_interface=NETINTERFACE specify the TCP IP address for netrpc
|
||||
--net_port=NETPORT specify the TCP port for netrpc
|
||||
--no-netrpc disable netrpc
|
||||
--no-xmlrpc disable xmlrpc
|
||||
-i INIT, --init=INIT init a module (use "all" for all modules)
|
||||
--without-demo=WITHOUT_DEMO load demo data for a module (use "all" for all modules)
|
||||
|
||||
+45
-2
@@ -1,6 +1,6 @@
|
||||
========================================
|
||||
============
|
||||
Architecture
|
||||
========================================
|
||||
============
|
||||
|
||||
OpenERP as a multitenant three-tiers architecture
|
||||
=================================================
|
||||
@@ -180,6 +180,7 @@ OpenERP follows the MVC semantic with
|
||||
|
||||
Network communications and WSGI
|
||||
===============================
|
||||
|
||||
OpenERP is an HTTP web server and may also be deployed as an WSGI-compliant
|
||||
application.
|
||||
|
||||
@@ -207,3 +208,45 @@ As such, it can be run as a stand-alone HTTP server or embedded inside OpenERP.
|
||||
The HTTP namespaces /openerp/ /object/ /common/ are reserved for the XML-RPC
|
||||
layer, every module restrict it's HTTP namespace to /<name_of_the_module>/
|
||||
|
||||
Process model
|
||||
=============
|
||||
|
||||
In the past, the OpenERP server was using threads to handle HTTP requests
|
||||
concurrently or to process cron jobs. Using threads is still the default
|
||||
behavior when running the ``openerp-server`` script but not the recommended
|
||||
one: it is in fact recommended to use the ``--workers`` option.
|
||||
|
||||
By using the ``--workers`` option, the OpenERP server will spawn a fixed number
|
||||
of processes instead of spawning a new thread for each incoming request.
|
||||
|
||||
This has a number of advantages:
|
||||
|
||||
- Processes do not suffer from CPython's Global Interpreter Lock.
|
||||
- Processes can be gracefully recycled while requests are still handled by the
|
||||
server.
|
||||
- Resources such as CPU time and memory made available to a process can be
|
||||
monitored on a per-process basis.
|
||||
|
||||
When using the ``--workers`` options, two types of processes may be spawned:
|
||||
web process, and cron process.
|
||||
|
||||
.. versionadded:: 7.1
|
||||
|
||||
.. _longpolling-worker:
|
||||
|
||||
When using the ``--workers`` options, three types of processes may be spawned:
|
||||
web process, and cron process, just as previsouly, but also an evented (using
|
||||
gevent) web process is started. It is used for long-polling as needed by the
|
||||
upcoming Instant Messaging feature. As for now, that process is listening on a
|
||||
different port than the main web processes. A reverse proxy (e.g. Nginx) to
|
||||
listen on a unique port, mapping all requests to the normal port, but mapping
|
||||
the ``/longpolling`` route to the evented process is necessary (the web
|
||||
interface cannot issue requests to different ports).
|
||||
|
||||
(It is possible to make the threaded server evented by passing the ``--gevent``
|
||||
flag.)
|
||||
|
||||
The goal is to drop support for the threaded model, and also make all web
|
||||
processes evented; there would be no more distinction between "normal" and
|
||||
"longpolling" processes. For this to happen, further testing is needed.
|
||||
|
||||
|
||||
+9
-7
@@ -6,13 +6,15 @@ Changelog
|
||||
`trunk`
|
||||
-------
|
||||
|
||||
- Removed support for the ``NET-RPC`` protocol.
|
||||
- Added the :ref:`Long polling <longpolling-worker>` worker type.
|
||||
- Added :ref:`orm-workflows` to the ORM.
|
||||
- Added :ref:`routing-decorators` to the RPC and WSGI stack.
|
||||
- Removed support for `__terp__.py` descriptor files.
|
||||
- Removed support for `<terp>` root element in XML files.
|
||||
- Removed support for the non-openerp namespace (e.g. importing `tools` instead
|
||||
of `openerp.tools` in an addons).
|
||||
- Removed support for ``__terp__.py`` descriptor files.
|
||||
- Removed support for ``<terp>`` root element in XML files.
|
||||
- Removed support for the non-openerp namespace (e.g. importing ``tools``
|
||||
instead of ``openerp.tools`` in an addons).
|
||||
- Add a new type of exception that allows redirections:
|
||||
openerp.exceptions.RedirectWarning.
|
||||
- Give a pair of new methods to res.config.settings and a helper to make them
|
||||
easier to use: get_config_warning()
|
||||
``openerp.exceptions.RedirectWarning``.
|
||||
- Give a pair of new methods to ``res.config.settings`` and a helper to make
|
||||
them easier to use: ``get_config_warning()``.
|
||||
|
||||
@@ -80,17 +80,6 @@ specify the certificate file for the SSL connection
|
||||
\fB\-\-pkey\-file\fR=\fISECURE_PKEY_FILE\fR
|
||||
specify the private key file for the SSL connection
|
||||
.IP
|
||||
NET\-RPC Configuration:
|
||||
.TP
|
||||
\fB\-\-netrpc\-interface\fR=\fINETRPC_INTERFACE\fR
|
||||
specify the TCP IP address for the NETRPC protocol
|
||||
.TP
|
||||
\fB\-\-netrpc\-port\fR=\fINETRPC_PORT\fR
|
||||
specify the TCP port for the NETRPC protocol
|
||||
.TP
|
||||
\fB\-\-no\-netrpc\fR
|
||||
disable the NETRPC protocol
|
||||
.IP
|
||||
Static HTTP service:
|
||||
.TP
|
||||
\fB\-\-static\-http\-enable\fR
|
||||
|
||||
+1
-1
@@ -13,6 +13,6 @@ Description: OpenERP is a complete ERP and CRM. The main features are accounting
|
||||
and financial), stock management, sales and purchases management, tasks
|
||||
automation, marketing campaigns, help desk, POS, etc. Technical features include
|
||||
a distributed server, flexible workflows, an object database, a dynamic GUI,
|
||||
customizable reports, and NET-RPC and XML-RPC interfaces.
|
||||
customizable reports, and an XML-RPC interface.
|
||||
Keywords: ERP, Accounting, Stock, CRM, Enterprise, Logistics, Management, Sales, Purchases
|
||||
Platform: Linux, Win32
|
||||
|
||||
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+212
-151
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+271
-185
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+2148
-165
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+205
-138
File diff suppressed because it is too large
Load Diff
+218
-139
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+170
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+166
-141
File diff suppressed because it is too large
Load Diff
+342
-164
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+305
-281
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+165
-140
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+4052
-2101
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+197
-167
File diff suppressed because it is too large
Load Diff
+1963
-1460
File diff suppressed because it is too large
Load Diff
+474
-218
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+255
-156
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+166
-141
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+168
-140
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
+163
-138
File diff suppressed because it is too large
Load Diff
@@ -207,7 +207,7 @@ class act_window(osv.osv):
|
||||
def _search_view(self, cr, uid, ids, name, arg, context=None):
|
||||
res = {}
|
||||
for act in self.browse(cr, uid, ids, context=context):
|
||||
field_get = self.pool.get(act.res_model).fields_view_get(cr, uid,
|
||||
field_get = self.pool[act.res_model].fields_view_get(cr, uid,
|
||||
act.search_view_id and act.search_view_id.id or False,
|
||||
'search', context=context)
|
||||
res[act.id] = str(field_get)
|
||||
@@ -792,7 +792,7 @@ Launch Manually Once: after having been launched manually, it sets automatically
|
||||
act_type = self.pool.get('ir.actions.actions').read(cr, uid, wizard.action_id.id, ['type'], context=context)
|
||||
|
||||
res = self.pool.get(act_type['type']).read(cr, uid, wizard.action_id.id, [], context=context)
|
||||
if act_type<>'ir.actions.act_window':
|
||||
if act_type['type'] != 'ir.actions.act_window':
|
||||
return res
|
||||
res.setdefault('context','{}')
|
||||
res['nodestroy'] = True
|
||||
|
||||
@@ -120,11 +120,13 @@ class ir_cron(osv.osv):
|
||||
:param args: arguments of the method (without the usual self, cr, uid).
|
||||
:param job_id: job id.
|
||||
"""
|
||||
args = str2tuple(args)
|
||||
model = self.pool.get(model_name)
|
||||
if model and hasattr(model, method_name):
|
||||
method = getattr(model, method_name)
|
||||
try:
|
||||
try:
|
||||
args = str2tuple(args)
|
||||
openerp.modules.registry.RegistryManager.check_registry_signaling(cr.dbname)
|
||||
registry = openerp.pooler.get_pool(cr.dbname)
|
||||
model = registry.get(model_name)
|
||||
if model and hasattr(model, method_name):
|
||||
method = getattr(model, method_name)
|
||||
log_depth = (None if _logger.isEnabledFor(logging.DEBUG) else 1)
|
||||
netsvc.log(_logger, logging.DEBUG, 'cron.object.execute', (cr.dbname,uid,'*',model_name,method_name)+tuple(args), depth=log_depth)
|
||||
if _logger.isEnabledFor(logging.DEBUG):
|
||||
@@ -133,12 +135,13 @@ class ir_cron(osv.osv):
|
||||
if _logger.isEnabledFor(logging.DEBUG):
|
||||
end_time = time.time()
|
||||
_logger.debug('%.3fs (%s, %s)' % (end_time - start_time, model_name, method_name))
|
||||
except Exception, e:
|
||||
self._handle_callback_exception(cr, uid, model_name, method_name, args, job_id, e)
|
||||
else:
|
||||
msg = "Method `%s.%s` do not exist." % (model._name, method_name) \
|
||||
if model else "Model `%s` do not exist." % model._name
|
||||
_logger.warning(msg)
|
||||
openerp.modules.registry.RegistryManager.signal_caches_change(cr.dbname)
|
||||
else:
|
||||
msg = "Method `%s.%s` does not exist." % (model_name, method_name) \
|
||||
if model else "Model `%s` does not exist." % model_name
|
||||
_logger.warning(msg)
|
||||
except Exception, e:
|
||||
self._handle_callback_exception(cr, uid, model_name, method_name, args, job_id, e)
|
||||
|
||||
def _process_job(self, job_cr, job, cron_cr):
|
||||
""" Run a given job taking care of the repetition.
|
||||
@@ -220,10 +223,8 @@ class ir_cron(osv.osv):
|
||||
_logger.debug('Starting job `%s`.', job['name'])
|
||||
job_cr = db.cursor()
|
||||
try:
|
||||
openerp.modules.registry.RegistryManager.check_registry_signaling(db_name)
|
||||
registry = openerp.pooler.get_pool(db_name)
|
||||
registry[cls._name]._process_job(job_cr, job, lock_cr)
|
||||
openerp.modules.registry.RegistryManager.signal_caches_change(db_name)
|
||||
except Exception:
|
||||
_logger.exception('Unexpected exception while processing cron job %r', job)
|
||||
finally:
|
||||
|
||||
@@ -933,13 +933,6 @@ class ir_model_data(osv.osv):
|
||||
elif res_id:
|
||||
model_obj.write(cr, uid, [res_id], values, context=context)
|
||||
if xml_id:
|
||||
self.create(cr, uid, {
|
||||
'name': xml_id,
|
||||
'model': model,
|
||||
'module':module,
|
||||
'res_id':res_id,
|
||||
'noupdate': noupdate,
|
||||
},context=context)
|
||||
if model_obj._inherits:
|
||||
for table in model_obj._inherits:
|
||||
inherit_id = model_obj.browse(cr, uid,
|
||||
@@ -951,17 +944,17 @@ class ir_model_data(osv.osv):
|
||||
'res_id': inherit_id.id,
|
||||
'noupdate': noupdate,
|
||||
},context=context)
|
||||
self.create(cr, uid, {
|
||||
'name': xml_id,
|
||||
'model': model,
|
||||
'module':module,
|
||||
'res_id':res_id,
|
||||
'noupdate': noupdate,
|
||||
},context=context)
|
||||
else:
|
||||
if mode=='init' or (mode=='update' and xml_id):
|
||||
res_id = model_obj.create(cr, uid, values, context=context)
|
||||
if xml_id:
|
||||
self.create(cr, uid, {
|
||||
'name': xml_id,
|
||||
'model': model,
|
||||
'module': module,
|
||||
'res_id': res_id,
|
||||
'noupdate': noupdate
|
||||
},context=context)
|
||||
if model_obj._inherits:
|
||||
for table in model_obj._inherits:
|
||||
inherit_id = model_obj.browse(cr, uid,
|
||||
@@ -973,6 +966,13 @@ class ir_model_data(osv.osv):
|
||||
'res_id': inherit_id.id,
|
||||
'noupdate': noupdate,
|
||||
},context=context)
|
||||
self.create(cr, uid, {
|
||||
'name': xml_id,
|
||||
'model': model,
|
||||
'module': module,
|
||||
'res_id': res_id,
|
||||
'noupdate': noupdate
|
||||
},context=context)
|
||||
if xml_id and res_id:
|
||||
self.loads[(module, xml_id)] = (model, res_id)
|
||||
for table, inherit_field in model_obj._inherits.iteritems():
|
||||
@@ -1060,6 +1060,14 @@ class ir_model_data(osv.osv):
|
||||
if set(external_ids)-ids_set:
|
||||
# if other modules have defined this record, we must not delete it
|
||||
continue
|
||||
if model == 'ir.model.fields':
|
||||
# Don't remove the LOG_ACCESS_COLUMNS unless _log_access
|
||||
# has been turned off on the model.
|
||||
field = self.pool.get(model).browse(cr, uid, [res_id], context=context)[0]
|
||||
if field.name in openerp.osv.orm.LOG_ACCESS_COLUMNS and self.pool[field.model]._log_access:
|
||||
continue
|
||||
if field.name == 'id':
|
||||
continue
|
||||
_logger.info('Deleting %s@%s', res_id, model)
|
||||
try:
|
||||
cr.execute('SAVEPOINT record_unlink_save')
|
||||
|
||||
@@ -557,7 +557,10 @@ class res_config_settings(osv.osv_memory):
|
||||
'params': {'modules': to_install_missing_names},
|
||||
}
|
||||
|
||||
config = self.pool.get('res.config').next(cr, uid, [], context=context) or {}
|
||||
# After the uninstall/install calls, the self.pool is no longer valid.
|
||||
# So we reach into the RegistryManager directly.
|
||||
res_config = openerp.modules.registry.RegistryManager.get(cr.dbname)['res.config']
|
||||
config = res_config.next(cr, uid, [], context=context) or {}
|
||||
if config.get('type') not in ('ir.actions.act_window_close',):
|
||||
return config
|
||||
|
||||
@@ -612,7 +615,7 @@ class res_config_settings(osv.osv_memory):
|
||||
"""
|
||||
Fetch the human readable name of a specified configuration option.
|
||||
|
||||
:param string full_field_name: the full name of the field, structured as follows:
|
||||
:param string full_field_name: the full name of the field, structured as follows:
|
||||
model_name.field_name (e.g.: "sale.config.settings.fetchmail_lead")
|
||||
:return string: human readable name of the field (e.g.: "Create leads from incoming mails")
|
||||
"""
|
||||
@@ -622,7 +625,7 @@ class res_config_settings(osv.osv_memory):
|
||||
|
||||
def get_config_warning(self, cr, msg, context=None):
|
||||
"""
|
||||
Helper: return a Warning exception with the given message where the %(field:xxx)s
|
||||
Helper: return a Warning exception with the given message where the %(field:xxx)s
|
||||
and/or %(menu:yyy)s are replaced by the human readable field's name and/or menuitem's
|
||||
full path.
|
||||
|
||||
@@ -639,6 +642,14 @@ class res_config_settings(osv.osv_memory):
|
||||
|
||||
This will return an exception containing the following message:
|
||||
Error: this action is prohibited. You should check the field Create leads from incoming mails in Settings/Configuration/Sales.
|
||||
|
||||
What if there is another substitution in the message already?
|
||||
-------------------------------------------------------------
|
||||
You could have a situation where the error message you want to upgrade already contains a substitution. Example:
|
||||
Cannot find any account journal of %s type for this company.\n\nYou can create one in the menu: \nConfiguration\Journals\Journals.
|
||||
What you want to do here is simply to replace the path by %menu:account.menu_account_config)s, and leave the rest alone.
|
||||
In order to do that, you can use the double percent (%%) to escape your new substitution, like so:
|
||||
Cannot find any account journal of %s type for this company.\n\nYou can create one in the %%(menu:account.menu_account_config)s.
|
||||
"""
|
||||
|
||||
res_config_obj = pooler.get_pool(cr.dbname).get('res.config.settings')
|
||||
|
||||
@@ -77,7 +77,7 @@ addresses belonging to this country.\n\nYou can use the python-style string pate
|
||||
context=context)
|
||||
|
||||
def write(self, cursor, user, ids, vals, context=None):
|
||||
if 'code' in vals:
|
||||
if vals.get('code', False):
|
||||
vals['code'] = vals['code'].upper()
|
||||
return super(Country, self).write(cursor, user, ids, vals,
|
||||
context=context)
|
||||
|
||||
@@ -362,20 +362,14 @@ class res_partner(osv.osv, format_address):
|
||||
# Update parent and siblings or children records
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
if vals.get('is_company')==False:
|
||||
vals.update({'child_ids' : [(5,)]})
|
||||
for partner in self.browse(cr, uid, ids, context=context):
|
||||
update_ids = []
|
||||
if partner.is_company:
|
||||
domain_children = [('parent_id', '=', partner.id), ('use_parent_address', '=', True)]
|
||||
domain_children = [('parent_id', 'child_of', partner.id), ('use_parent_address', '=', True)]
|
||||
update_ids = self.search(cr, uid, domain_children, context=context)
|
||||
elif partner.parent_id:
|
||||
if vals.get('use_parent_address')==True:
|
||||
domain_siblings = [('parent_id', '=', partner.parent_id.id), ('use_parent_address', '=', True)]
|
||||
update_ids = [partner.parent_id.id] + self.search(cr, uid, domain_siblings, context=context)
|
||||
if 'use_parent_address' not in vals and partner.use_parent_address:
|
||||
domain_siblings = [('parent_id', '=', partner.parent_id.id), ('use_parent_address', '=', True)]
|
||||
update_ids = [partner.parent_id.id] + self.search(cr, uid, domain_siblings, context=context)
|
||||
elif partner.parent_id and vals.get('use_parent_address', partner.use_parent_address):
|
||||
domain_siblings = [('parent_id', '=', partner.parent_id.id), ('use_parent_address', '=', True)]
|
||||
update_ids = [partner.parent_id.id] + self.search(cr, uid, domain_siblings, context=context)
|
||||
self.update_address(cr, uid, update_ids, vals, context)
|
||||
return super(res_partner,self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
<field name="city">Wavre</field>
|
||||
<field name="zip">1300</field>
|
||||
<field name="country_id" ref="base.be"/>
|
||||
<field name="street">69 rue de Chimay</field>
|
||||
<field name="street">69 rue de Namur</field>
|
||||
<field name="email">info@agrolait.com</field>
|
||||
<field name="phone">+32 10 588 558</field>
|
||||
<field name="website">www.agrolait.com</field>
|
||||
|
||||
@@ -269,7 +269,8 @@
|
||||
<page string="Sales & Purchases">
|
||||
<group>
|
||||
<group>
|
||||
<field name="user_id"/>
|
||||
<field name="user_id"
|
||||
context="{'default_groups_ref': ['base.group_partner_manager']}"/>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
</group>
|
||||
<group>
|
||||
|
||||
@@ -744,6 +744,7 @@ class users_view(osv.osv):
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
self._set_reified_groups(values)
|
||||
|
||||
return super(users_view, self).create(cr, uid, values, context)
|
||||
|
||||
def write(self, cr, uid, ids, values, context=None):
|
||||
@@ -782,6 +783,21 @@ class users_view(osv.osv):
|
||||
fields1 = (fields + ['groups_id']) if group_fields else fields
|
||||
values = super(users_view, self).default_get(cr, uid, fields1, context)
|
||||
self._get_reified_groups(group_fields, values)
|
||||
|
||||
# add "default_groups_ref" inside the context to set default value for group_id with xml values
|
||||
if 'groups_id' in fields and isinstance(context.get("default_groups_ref"), list):
|
||||
groups = []
|
||||
ir_model_data = self.pool.get('ir.model.data')
|
||||
for group_xml_id in context["default_groups_ref"]:
|
||||
group_split = group_xml_id.split('.')
|
||||
if len(group_split) != 2:
|
||||
raise osv.except_osv(_('Invalid context value'), _('Invalid context default_groups_ref value (model.name_id) : "%s"') % group_xml_id)
|
||||
try:
|
||||
temp, group_id = ir_model_data.get_object_reference(cr, uid, group_split[0], group_split[1])
|
||||
except ValueError:
|
||||
group_id = False
|
||||
groups += [group_id]
|
||||
values['groups_id'] = groups
|
||||
return values
|
||||
|
||||
def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
|
||||
|
||||
@@ -84,48 +84,39 @@
|
||||
</record>
|
||||
<menuitem action="action_res_groups" id="menu_action_res_groups" parent="base.menu_users" groups="base.group_no_one"/>
|
||||
|
||||
|
||||
<!-- res.users -->
|
||||
<record id="view_users_simple_form" model="ir.ui.view">
|
||||
<field name="name">res.users.simplified.form</field>
|
||||
<field name="model">res.users</field>
|
||||
<field name="priority">1</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Contact" version="7.0">
|
||||
<form string="Users" version="7.0">
|
||||
<sheet>
|
||||
<field name="image" widget='image' class="oe_left oe_avatar" options='{"preview_image": "image_medium", "size": [90, 90]}'/>
|
||||
<div class="oe_title">
|
||||
<div class="oe_edit_only">
|
||||
<label for="name"/>
|
||||
<field name="id" invisible="1"/>
|
||||
<div class="oe_form_box_info oe_text_center" style="margin-bottom: 10px" attrs="{'invisible': [('id', '>', 0)]}">
|
||||
You are creating a new user. After saving, the user will receive an invite email containing a link to set its password.
|
||||
</div>
|
||||
<field name="image" widget='image' class="oe_avatar oe_left" options='{"preview_image": "image_medium"}'/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
<label for="login" class="oe_edit_only"/>
|
||||
<h2><field name="login"/></h2>
|
||||
<label for="company_id" class="oe_edit_only" groups="base.group_multi_company"/>
|
||||
<field name="company_id" context="{'user_preference': 0}" groups="base.group_multi_company"/>
|
||||
</div>
|
||||
<h1>
|
||||
<field name="name" default_focus="1" placeholder="Name" />
|
||||
</h1>
|
||||
<field name="category_id" widget="many2many_tags" placeholder="Tags..."/>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<label for="street" string="Address"/>
|
||||
<div>
|
||||
<field name="street" placeholder="Street..."/>
|
||||
<field name="street2"/>
|
||||
<div class="address_format">
|
||||
<field name="city" placeholder="City" style="width: 40%%"/>
|
||||
<field name="state_id" class="oe_no_button" placeholder="State" style="width: 37%%" options='{"no_open": True}' on_change="onchange_state(state_id)"/>
|
||||
<field name="zip" placeholder="ZIP" style="width: 20%%"/>
|
||||
</div>
|
||||
<field name="country_id" placeholder="Country" class="oe_no_button" options='{"no_open": True}'/>
|
||||
<label for="groups_id" string="Access Rights"
|
||||
attrs="{'invisible': [('id', '>', 0)]}"/>
|
||||
<div attrs="{'invisible': [('id', '>', 0)]}">
|
||||
<field name="groups_id" readonly="1" widget="many2many_tags" style="display: inline;"/> You will be able to define additional access rights by editing the newly created user under the Settings / Users menu.
|
||||
</div>
|
||||
<field name="website" widget="url" placeholder="e.g. www.openerp.com"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="phone" placeholder="e.g. +32.81.81.37.00"/>
|
||||
<field name="mobile"/>
|
||||
<field name="fax"/>
|
||||
<field name="email" widget="email"/>
|
||||
<field name="email" widget="email" required="True"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
@@ -134,8 +125,8 @@
|
||||
<field name="model">res.users</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Users" version="7.0">
|
||||
<field name="id" invisible="1"/>
|
||||
<sheet>
|
||||
<field name="id" invisible="1"/>
|
||||
<div class="oe_right oe_button_box">
|
||||
<button string="Change Password" type="action" name="%(change_password_wizard_action)d"
|
||||
help="Change the user password."/>
|
||||
@@ -146,17 +137,29 @@
|
||||
<h1><field name="name"/></h1>
|
||||
<label for="login" class="oe_edit_only"/>
|
||||
<h2><field name="login"/></h2>
|
||||
<label for="company_id" class="oe_edit_only" groups="base.group_multi_company"/>
|
||||
<field name="company_id" context="{'user_preference': 0}" groups="base.group_multi_company"/>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="company_id" context="{'user_preference': 0}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<notebook colspan="4">
|
||||
<page string="Access Rights">
|
||||
<group>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<group string="Allowed Companies" groups="base.group_multi_company">
|
||||
<field name="company_ids" nolabel="1" widget="many2many_tags"/>
|
||||
</group>
|
||||
<group col="4">
|
||||
<field name="groups_id"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Contact">
|
||||
<group>
|
||||
<field name="phone" placeholder="e.g. +32.81.81.37.00"/>
|
||||
<field name="mobile"/>
|
||||
<field name="fax"/>
|
||||
<field name="email" widget="email"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Preferences">
|
||||
<group>
|
||||
<group name="preferences">
|
||||
@@ -168,24 +171,16 @@
|
||||
<field domain="[('usage','=','menu')]" name="menu_id" required="True"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="email" widget="email"/>
|
||||
<group string="Email preferences">
|
||||
<field name="signature"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Access Rights">
|
||||
<group string="Allowed Companies" groups="base.group_multi_company">
|
||||
<field name="company_ids" nolabel="1" widget="many2many_tags"/>
|
||||
</group>
|
||||
<group col="4">
|
||||
<field name="groups_id"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_users_tree" model="ir.ui.view">
|
||||
<field name="name">res.users.tree</field>
|
||||
<field name="model">res.users</field>
|
||||
|
||||
@@ -393,6 +393,7 @@
|
||||
<rng:optional><rng:attribute name="day_length"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="date_start"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="date_stop"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="progress"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="date_string"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="string"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="default_group_by"/></rng:optional>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
with mute_logger('openerp.tools.safe_eval'):
|
||||
safe_eval('open("/etc/passwd","r")')
|
||||
assert False, "safe_eval should not allow calling open() builtin"
|
||||
except NameError:
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
-
|
||||
|
||||
@@ -39,42 +39,42 @@ class test_res_config(common.TransactionCase):
|
||||
res = self.res_config.get_option_path(self.cr, self.uid, self.menu_xml_id, context=None)
|
||||
|
||||
# Check types
|
||||
self.assertTrue(isinstance(res, tuple)), "The result of get_option_path() should be a tuple (got %s)" % type(res)
|
||||
self.assertTrue(len(res) == 2), "The tuple should contain 2 elements (got %s)" % len(res)
|
||||
self.assertTrue(isinstance(res[0], basestring)), "The first element of the tuple should be a string (got %s)" % type(res[0])
|
||||
self.assertTrue(isinstance(res[1], long)), "The second element of the tuple should be an long (got %s)" % type(res[1])
|
||||
self.assertIsInstance(res, tuple)
|
||||
self.assertEqual(len(res), 2, "The result should contain 2 elements")
|
||||
self.assertIsInstance(res[0], basestring)
|
||||
self.assertIsInstance(res[1], long)
|
||||
|
||||
# Check returned values
|
||||
self.assertTrue(res[0] == self.expected_path), "Result mismatch: expected %s, got %s" % (self.expected_path, res[0])
|
||||
self.assertTrue(res[1] == self.expected_action_id), "Result mismatch: expected %s, got %s" % (self.expected_action_id, res[1])
|
||||
self.assertEqual(res[0], self.expected_path)
|
||||
self.assertEqual(res[1], self.expected_action_id)
|
||||
|
||||
def test_10_get_option_name(self):
|
||||
""" The get_option_name() method should return a string """
|
||||
res = self.res_config.get_option_name(self.cr, self.uid, self.full_field_name, context=None)
|
||||
|
||||
# Check type
|
||||
self.assertTrue(isinstance(res, basestring)), "Result type mismatch: expected basestring, got %s" % type(res)
|
||||
self.assertIsInstance(res, basestring)
|
||||
|
||||
# Check returned value
|
||||
self.assertTrue(res == self.expected_name), "Result mismatch: expected %s, got %s" % (self.expected_name, res)
|
||||
self.assertEqual(res, self.expected_name)
|
||||
|
||||
def test_20_get_config_warning(self):
|
||||
""" The get_config_warning() method should return a RedirectWarning """
|
||||
res = self.res_config.get_config_warning(self.cr, self.error_msg, context=None)
|
||||
|
||||
# Check type
|
||||
self.assertTrue(isinstance(res, openerp.exceptions.RedirectWarning)), "Result type mismatch: expected openerp.exceptions.RedirectWarning, got %s" % type(res)
|
||||
self.assertIsInstance(res, openerp.exceptions.RedirectWarning)
|
||||
|
||||
# Check returned value
|
||||
self.assertTrue(res.args[0], self.expected_final_error_msg), "Result mismatch: expected %s, got %s" % (self.expected_final_error_msg, res.args[0])
|
||||
self.assertTrue(res.args[1], self.expected_action_id), "Result mismatch: expected %s, got %s" % (self.expected_action_id, res.args[1])
|
||||
self.assertEqual(res.args[0], self.expected_final_error_msg)
|
||||
self.assertEqual(res.args[1], self.expected_action_id)
|
||||
|
||||
def test_30_get_config_warning_wo_menu(self):
|
||||
""" The get_config_warning() method should return a Warning exception """
|
||||
res = self.res_config.get_config_warning(self.cr, self.error_msg_wo_menu, context=None)
|
||||
|
||||
# Check type
|
||||
self.assertTrue(isinstance(res, openerp.exceptions.Warning)), "Result type mismatch: expected openerp.exceptions.Warning, got %s" % type(res)
|
||||
self.assertIsInstance(res, openerp.exceptions.Warning)
|
||||
|
||||
# Check returned value
|
||||
self.assertTrue(res.args[0], self.expected_final_error_msg), "Result mismatch: expected %s, got %s" % (self.expected_final_error_msg_wo_menu, res.args[0])
|
||||
self.assertEqual(res.args[0], self.expected_final_error_msg_wo_menu)
|
||||
|
||||
@@ -230,6 +230,14 @@ def main(args):
|
||||
check_root_user()
|
||||
openerp.tools.config.parse_config(args)
|
||||
|
||||
if openerp.tools.config.options["gevent"]:
|
||||
openerp.evented = True
|
||||
_logger.info('Using gevent mode')
|
||||
import gevent.monkey
|
||||
gevent.monkey.patch_all()
|
||||
import gevent_psycopg2
|
||||
gevent_psycopg2.monkey_patch()
|
||||
|
||||
check_postgres_user()
|
||||
openerp.netsvc.init_logger()
|
||||
report_configuration()
|
||||
|
||||
@@ -62,23 +62,6 @@ class AddonsImportHook(object):
|
||||
backward compatibility, `import <module>` is still supported. Now they
|
||||
are living in `openerp.addons`. The good way to import such modules is
|
||||
thus `import openerp.addons.module`.
|
||||
|
||||
For backward compatibility, loading an addons puts it in `sys.modules`
|
||||
under both the legacy (short) name, and the new (longer) name. This
|
||||
ensures that
|
||||
import hr
|
||||
import openerp.addons.hr
|
||||
loads the hr addons only once.
|
||||
|
||||
When an OpenERP addons name clashes with some other installed Python
|
||||
module (for instance this is the case of the `resource` addons),
|
||||
obtaining the OpenERP addons is only possible with the long name. The
|
||||
short name will give the expected Python module.
|
||||
|
||||
Instead of relying on some addons path, an alternative approach would be
|
||||
to use pkg_resources entry points from already installed Python libraries
|
||||
(and install our addons as such). Even when implemented, we would still
|
||||
have to support the addons path approach for backward compatibility.
|
||||
"""
|
||||
|
||||
def find_module(self, module_name, package_path):
|
||||
@@ -86,25 +69,6 @@ class AddonsImportHook(object):
|
||||
if len(module_parts) == 3 and module_name.startswith('openerp.addons.'):
|
||||
return self # We act as a loader too.
|
||||
|
||||
# TODO list of loadable modules can be cached instead of always
|
||||
# calling get_module_path().
|
||||
if len(module_parts) == 1 and \
|
||||
get_module_path(module_parts[0],
|
||||
display_warning=False):
|
||||
try:
|
||||
# Check if the bare module name clashes with another module.
|
||||
f, path, descr = imp.find_module(module_parts[0])
|
||||
_logger.warning("""
|
||||
Ambiguous import: the OpenERP module `%s` is shadowed by another
|
||||
module (available at %s).
|
||||
To import it, use `import openerp.addons.<module>.`.""" % (module_name, path))
|
||||
return
|
||||
except ImportError, e:
|
||||
# Using `import <module_name>` instead of
|
||||
# `import openerp.addons.<module_name>` is ugly but not harmful
|
||||
# and kept for backward compatibility.
|
||||
return self # We act as a loader too.
|
||||
|
||||
def load_module(self, module_name):
|
||||
|
||||
module_parts = module_name.split('.')
|
||||
@@ -113,29 +77,9 @@ To import it, use `import openerp.addons.<module>.`.""" % (module_name, path))
|
||||
if module_name in sys.modules:
|
||||
return sys.modules[module_name]
|
||||
|
||||
if len(module_parts) == 1:
|
||||
module_part = module_parts[0]
|
||||
if module_part in sys.modules:
|
||||
return sys.modules[module_part]
|
||||
|
||||
try:
|
||||
# Check if the bare module name shadows another module.
|
||||
f, path, descr = imp.find_module(module_part)
|
||||
is_shadowing = True
|
||||
except ImportError, e:
|
||||
# Using `import <module_name>` instead of
|
||||
# `import openerp.addons.<module_name>` is ugly but not harmful
|
||||
# and kept for backward compatibility.
|
||||
is_shadowing = False
|
||||
|
||||
# Note: we don't support circular import.
|
||||
f, path, descr = imp.find_module(module_part, ad_paths)
|
||||
mod = imp.load_module('openerp.addons.' + module_part, f, path, descr)
|
||||
if not is_shadowing:
|
||||
sys.modules[module_part] = mod
|
||||
for k in sys.modules.keys():
|
||||
if k.startswith('openerp.addons.' + module_part):
|
||||
sys.modules[k[len('openerp.addons.'):]] = sys.modules[k]
|
||||
sys.modules['openerp.addons.' + module_part] = mod
|
||||
return mod
|
||||
|
||||
|
||||
+6
-2
@@ -205,8 +205,8 @@ def log(logger, level, prefix, msg, depth=None):
|
||||
def dispatch_rpc(service_name, method, params):
|
||||
""" Handle a RPC call.
|
||||
|
||||
This is pure Python code, the actual marshalling (from/to XML-RPC or
|
||||
NET-RPC) is done in a upper layer.
|
||||
This is pure Python code, the actual marshalling (from/to XML-RPC) is done
|
||||
in a upper layer.
|
||||
"""
|
||||
try:
|
||||
rpc_request = logging.getLogger(__name__ + '.rpc.request')
|
||||
@@ -247,12 +247,16 @@ def dispatch_rpc(service_name, method, params):
|
||||
log(rpc_request,logging.DEBUG, logline, replace_request_password(params), depth=1)
|
||||
|
||||
return result
|
||||
except openerp.osv.orm.except_orm:
|
||||
raise
|
||||
except openerp.exceptions.AccessError:
|
||||
raise
|
||||
except openerp.exceptions.AccessDenied:
|
||||
raise
|
||||
except openerp.exceptions.Warning:
|
||||
raise
|
||||
except openerp.exceptions.RedirectWarning:
|
||||
raise
|
||||
except openerp.exceptions.DeferredException, e:
|
||||
_logger.exception(tools.exception_to_unicode(e))
|
||||
post_mortem(e.traceback)
|
||||
|
||||
+47
-44
@@ -59,7 +59,6 @@ from lxml import etree
|
||||
|
||||
import fields
|
||||
import openerp
|
||||
import openerp.netsvc as netsvc
|
||||
import openerp.tools as tools
|
||||
from openerp.tools.config import config
|
||||
from openerp.tools.misc import CountingStream
|
||||
@@ -481,7 +480,9 @@ class browse_record(object):
|
||||
try:
|
||||
return self[name]
|
||||
except KeyError, e:
|
||||
raise AttributeError(e)
|
||||
import sys
|
||||
exc_info = sys.exc_info()
|
||||
raise AttributeError, "Got %r while trying to get attribute %s on a %s record." % (e, name, self._table._name), exc_info[2]
|
||||
|
||||
def __contains__(self, name):
|
||||
return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name)
|
||||
@@ -3590,8 +3591,6 @@ class BaseModel(object):
|
||||
|
||||
"""
|
||||
|
||||
if not context:
|
||||
context = {}
|
||||
self.check_access_rights(cr, user, 'read')
|
||||
fields = self.check_field_access_rights(cr, user, 'read', fields)
|
||||
if isinstance(ids, (int, long)):
|
||||
@@ -3601,12 +3600,7 @@ class BaseModel(object):
|
||||
select = map(lambda x: isinstance(x, dict) and x['id'] or x, select)
|
||||
result = self._read_flat(cr, user, select, fields, context, load)
|
||||
|
||||
for r in result:
|
||||
for key, v in r.items():
|
||||
if v is None:
|
||||
r[key] = False
|
||||
|
||||
if isinstance(ids, (int, long, dict)):
|
||||
if isinstance(ids, (int, long)):
|
||||
return result and result[0] or False
|
||||
return result
|
||||
|
||||
@@ -3740,34 +3734,37 @@ class BaseModel(object):
|
||||
if field in self._columns:
|
||||
fobj = self._columns[field]
|
||||
|
||||
if not fobj:
|
||||
continue
|
||||
groups = fobj.read
|
||||
if groups:
|
||||
edit = False
|
||||
for group in groups:
|
||||
module = group.split(".")[0]
|
||||
grp = group.split(".")[1]
|
||||
cr.execute("select count(*) from res_groups_users_rel where gid IN (select res_id from ir_model_data where name=%s and module=%s and model=%s) and uid=%s", \
|
||||
(grp, module, 'res.groups', user))
|
||||
readonly = cr.fetchall()
|
||||
if readonly[0][0] >= 1:
|
||||
edit = True
|
||||
break
|
||||
elif readonly[0][0] == 0:
|
||||
edit = False
|
||||
else:
|
||||
edit = False
|
||||
if fobj:
|
||||
groups = fobj.read
|
||||
if groups:
|
||||
edit = False
|
||||
for group in groups:
|
||||
module = group.split(".")[0]
|
||||
grp = group.split(".")[1]
|
||||
cr.execute("select count(*) from res_groups_users_rel where gid IN (select res_id from ir_model_data where name=%s and module=%s and model=%s) and uid=%s", \
|
||||
(grp, module, 'res.groups', user))
|
||||
readonly = cr.fetchall()
|
||||
if readonly[0][0] >= 1:
|
||||
edit = True
|
||||
break
|
||||
elif readonly[0][0] == 0:
|
||||
edit = False
|
||||
else:
|
||||
edit = False
|
||||
|
||||
if not edit:
|
||||
if type(vals[field]) == type([]):
|
||||
vals[field] = []
|
||||
elif type(vals[field]) == type(0.0):
|
||||
vals[field] = 0
|
||||
elif type(vals[field]) == type(''):
|
||||
vals[field] = '=No Permission='
|
||||
else:
|
||||
vals[field] = False
|
||||
|
||||
if vals[field] is None:
|
||||
vals[field] = False
|
||||
|
||||
if not edit:
|
||||
if type(vals[field]) == type([]):
|
||||
vals[field] = []
|
||||
elif type(vals[field]) == type(0.0):
|
||||
vals[field] = 0
|
||||
elif type(vals[field]) == type(''):
|
||||
vals[field] = '=No Permission='
|
||||
else:
|
||||
vals[field] = False
|
||||
return res
|
||||
|
||||
# TODO check READ access
|
||||
@@ -4870,12 +4867,16 @@ class BaseModel(object):
|
||||
limit_str = limit and ' limit %d' % limit or ''
|
||||
offset_str = offset and ' offset %d' % offset or ''
|
||||
where_str = where_clause and (" WHERE %s" % where_clause) or ''
|
||||
query_str = 'SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str
|
||||
|
||||
if count:
|
||||
cr.execute('SELECT count("%s".id) FROM ' % self._table + from_clause + where_str + limit_str + offset_str, where_clause_params)
|
||||
res = cr.fetchall()
|
||||
return res[0][0]
|
||||
cr.execute('SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str, where_clause_params)
|
||||
# /!\ the main query must be executed as a subquery, otherwise
|
||||
# offset and limit apply to the result of count()!
|
||||
cr.execute('SELECT count(*) FROM (%s) AS count' % query_str, where_clause_params)
|
||||
res = cr.fetchone()
|
||||
return res[0]
|
||||
|
||||
cr.execute(query_str, where_clause_params)
|
||||
res = cr.fetchall()
|
||||
|
||||
# TDE note: with auto_join, we could have several lines about the same result
|
||||
@@ -5277,7 +5278,10 @@ class BaseModel(object):
|
||||
assert signal_name
|
||||
return (lambda *args, **kwargs:
|
||||
self.signal_workflow(*args, signal=signal_name, **kwargs))
|
||||
return super(BaseModel, self).__getattr__(name)
|
||||
get = getattr(super(BaseModel, self), '__getattr__', None)
|
||||
if get is not None: return get(name)
|
||||
raise AttributeError(
|
||||
"'%s' object has no attribute '%s'" % (type(self).__name__, name))
|
||||
|
||||
# keep this import here, at top it will cause dependency cycle errors
|
||||
import expression
|
||||
@@ -5349,8 +5353,7 @@ def convert_pgerror_23502(model, fields, info, e):
|
||||
message = _(u"Missing required value for the field '%s'.") % field_name
|
||||
field = fields.get(field_name)
|
||||
if field:
|
||||
message = _(u"%s This might be '%s' in the current model, or a field "
|
||||
u"of the same name in an o2m.") % (message, field['string'])
|
||||
message = _(u"Missing required value for the field '%s' (%s)") % (field['string'], field_name)
|
||||
return {
|
||||
'message': message,
|
||||
'field': field_name,
|
||||
|
||||
@@ -31,6 +31,7 @@ import time
|
||||
import cron
|
||||
import wsgi_server
|
||||
|
||||
import openerp
|
||||
import openerp.modules
|
||||
import openerp.netsvc
|
||||
import openerp.osv
|
||||
@@ -85,12 +86,14 @@ def start_services():
|
||||
# Start the WSGI server.
|
||||
wsgi_server.start_service()
|
||||
# Start the main cron thread.
|
||||
cron.start_service()
|
||||
if not openerp.evented:
|
||||
cron.start_service()
|
||||
|
||||
def stop_services():
|
||||
""" Stop all services. """
|
||||
# stop services
|
||||
cron.stop_service()
|
||||
if not openerp.evented:
|
||||
cron.stop_service()
|
||||
wsgi_server.stop_service()
|
||||
|
||||
_logger.info("Initiating shutdown")
|
||||
|
||||
@@ -44,11 +44,7 @@ def cron_runner(number):
|
||||
_logger.debug('cron%d polling for jobs', number)
|
||||
for db_name, registry in registries.items():
|
||||
while True and registry.ready:
|
||||
# acquired = openerp.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
|
||||
# TODO why isnt openerp.addons.base defined ?
|
||||
import sys
|
||||
base = sys.modules['addons.base']
|
||||
acquired = base.ir.ir_cron.ir_cron._acquire_job(db_name)
|
||||
acquired = openerp.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
|
||||
if not acquired:
|
||||
break
|
||||
|
||||
|
||||
+30
-22
@@ -1,12 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import base64
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
import openerp.pooler
|
||||
import openerp.release
|
||||
import openerp.sql_db
|
||||
import openerp.tools
|
||||
|
||||
@@ -20,39 +23,44 @@ self_id_protect = threading.Semaphore()
|
||||
|
||||
# This should be moved to openerp.modules.db, along side initialize().
|
||||
def _initialize_db(id, db_name, demo, lang, user_password):
|
||||
cr = None
|
||||
try:
|
||||
self_actions[id]['progress'] = 0
|
||||
cr = openerp.sql_db.db_connect(db_name).cursor()
|
||||
openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by pooler.restart_pool.
|
||||
openerp.tools.config['lang'] = lang
|
||||
cr.commit()
|
||||
cr.close()
|
||||
cr = None
|
||||
try:
|
||||
self_actions[id]['progress'] = 0
|
||||
cr = openerp.sql_db.db_connect(db_name).cursor()
|
||||
openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by pooler.restart_pool.
|
||||
openerp.tools.config['lang'] = lang
|
||||
cr.commit()
|
||||
finally:
|
||||
if cr:
|
||||
cr.close()
|
||||
cr = None
|
||||
|
||||
pool = openerp.pooler.restart_pool(db_name, demo, self_actions[id],
|
||||
update_module=True)[1]
|
||||
|
||||
cr = openerp.sql_db.db_connect(db_name).cursor()
|
||||
try:
|
||||
cr = openerp.sql_db.db_connect(db_name).cursor()
|
||||
|
||||
if lang:
|
||||
modobj = pool.get('ir.module.module')
|
||||
mids = modobj.search(cr, SUPERUSER_ID, [('state', '=', 'installed')])
|
||||
modobj.update_translations(cr, SUPERUSER_ID, mids, lang)
|
||||
if lang:
|
||||
modobj = pool.get('ir.module.module')
|
||||
mids = modobj.search(cr, SUPERUSER_ID, [('state', '=', 'installed')])
|
||||
modobj.update_translations(cr, SUPERUSER_ID, mids, lang)
|
||||
|
||||
# update admin's password and lang
|
||||
values = {'password': user_password, 'lang': lang}
|
||||
pool.get('res.users').write(cr, SUPERUSER_ID, [SUPERUSER_ID], values)
|
||||
# update admin's password and lang
|
||||
values = {'password': user_password, 'lang': lang}
|
||||
pool.get('res.users').write(cr, SUPERUSER_ID, [SUPERUSER_ID], values)
|
||||
|
||||
cr.execute('SELECT login, password FROM res_users ORDER BY login')
|
||||
self_actions[id].update(users=cr.dictfetchall(), clean=True)
|
||||
cr.commit()
|
||||
cr.close()
|
||||
cr.execute('SELECT login, password FROM res_users ORDER BY login')
|
||||
self_actions[id].update(users=cr.dictfetchall(), clean=True)
|
||||
cr.commit()
|
||||
finally:
|
||||
if cr:
|
||||
cr.close()
|
||||
except Exception, e:
|
||||
self_actions[id].update(clean=False, exception=e)
|
||||
_logger.exception('CREATE DATABASE failed:')
|
||||
self_actions[id]['traceback'] = traceback.format_exc()
|
||||
if cr:
|
||||
cr.close()
|
||||
|
||||
def dispatch(method, params):
|
||||
if method in [ 'create', 'get_progress', 'drop', 'dump',
|
||||
@@ -337,7 +345,7 @@ def exp_server_version():
|
||||
""" Return the version of the server
|
||||
Used by the client to verify the compatibility with its own version
|
||||
"""
|
||||
return release.version
|
||||
return openerp.release.version
|
||||
|
||||
def exp_migrate_databases(databases):
|
||||
for db in databases:
|
||||
|
||||
@@ -98,8 +98,6 @@ def check(f):
|
||||
if openerp.pooler.get_pool(dbname)._init:
|
||||
raise openerp.exceptions.Warning('Currently, this database is not fully loaded and can not be used.')
|
||||
return f(dbname, *args, **kwargs)
|
||||
except except_orm:
|
||||
raise
|
||||
except IntegrityError, inst:
|
||||
osv_pool = openerp.pooler.get_pool(dbname)
|
||||
for key in osv_pool._sql_error.keys():
|
||||
@@ -127,9 +125,6 @@ def check(f):
|
||||
raise openerp.osv.orm.except_orm(_('Integrity Error'), msg)
|
||||
else:
|
||||
raise openerp.osv.orm.except_orm(_('Integrity Error'), inst[0])
|
||||
except Exception:
|
||||
_logger.exception("Uncaught exception")
|
||||
raise
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ class Multicorn(object):
|
||||
def __init__(self, app):
|
||||
# config
|
||||
self.address = (config['xmlrpc_interface'] or '0.0.0.0', config['xmlrpc_port'])
|
||||
self.long_polling_address = (config['xmlrpc_interface'] or '0.0.0.0', config['longpolling_port'])
|
||||
self.population = config['workers']
|
||||
self.timeout = config['limit_time_real']
|
||||
self.limit_request = config['limit_request']
|
||||
@@ -45,6 +46,7 @@ class Multicorn(object):
|
||||
self.socket = None
|
||||
self.workers_http = {}
|
||||
self.workers_cron = {}
|
||||
self.workers_longpolling = {}
|
||||
self.workers = {}
|
||||
self.generation = 0
|
||||
self.queue = []
|
||||
@@ -131,7 +133,8 @@ class Multicorn(object):
|
||||
def process_timeout(self):
|
||||
now = time.time()
|
||||
for (pid, worker) in self.workers.items():
|
||||
if now - worker.watchdog_time >= worker.watchdog_timeout:
|
||||
if (worker.watchdog_timeout is not None) and \
|
||||
(now - worker.watchdog_time >= worker.watchdog_timeout):
|
||||
_logger.error("Worker (%s) timeout", pid)
|
||||
self.worker_kill(pid, signal.SIGKILL)
|
||||
|
||||
@@ -140,6 +143,8 @@ class Multicorn(object):
|
||||
self.worker_spawn(WorkerHTTP, self.workers_http)
|
||||
while len(self.workers_cron) < config['max_cron_threads']:
|
||||
self.worker_spawn(WorkerCron, self.workers_cron)
|
||||
while len(self.workers_longpolling) < 1:
|
||||
self.worker_spawn(WorkerLongPolling, self.workers_longpolling)
|
||||
|
||||
def sleep(self):
|
||||
try:
|
||||
@@ -178,6 +183,12 @@ class Multicorn(object):
|
||||
self.socket.setblocking(0)
|
||||
self.socket.bind(self.address)
|
||||
self.socket.listen(8)
|
||||
# long polling socket
|
||||
self.long_polling_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.long_polling_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.long_polling_socket.setblocking(0)
|
||||
self.long_polling_socket.bind(self.long_polling_address)
|
||||
self.long_polling_socket.listen(8)
|
||||
|
||||
def stop(self, graceful=True):
|
||||
if graceful:
|
||||
@@ -221,6 +232,7 @@ class Worker(object):
|
||||
self.multi = multi
|
||||
self.watchdog_time = time.time()
|
||||
self.watchdog_pipe = multi.pipe_new()
|
||||
# Can be set to None if no watchdog is desired.
|
||||
self.watchdog_timeout = multi.timeout
|
||||
self.ppid = os.getpid()
|
||||
self.pid = None
|
||||
@@ -340,6 +352,26 @@ class WorkerHTTP(Worker):
|
||||
Worker.start(self)
|
||||
self.server = WorkerBaseWSGIServer(self.multi.app)
|
||||
|
||||
class WorkerLongPolling(Worker):
|
||||
""" Long polling workers """
|
||||
def __init__(self, multi):
|
||||
super(WorkerLongPolling, self).__init__(multi)
|
||||
# Disable the watchdog feature for this kind of worker.
|
||||
self.watchdog_timeout = None
|
||||
|
||||
def start(self):
|
||||
openerp.evented = True
|
||||
_logger.info('Using gevent mode')
|
||||
import gevent.monkey
|
||||
gevent.monkey.patch_all()
|
||||
import gevent_psycopg2
|
||||
gevent_psycopg2.monkey_patch()
|
||||
|
||||
Worker.start(self)
|
||||
from gevent.wsgi import WSGIServer
|
||||
self.server = WSGIServer(self.multi.long_polling_socket, self.multi.app)
|
||||
self.server.serve_forever()
|
||||
|
||||
class WorkerBaseWSGIServer(werkzeug.serving.BaseWSGIServer):
|
||||
""" werkzeug WSGI Server patched to allow using an external listen socket
|
||||
"""
|
||||
@@ -375,7 +407,7 @@ class WorkerCron(Worker):
|
||||
while True:
|
||||
# acquired = openerp.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
|
||||
# TODO why isnt openerp.addons.base defined ?
|
||||
import base
|
||||
import openerp.addons.base as base
|
||||
acquired = base.ir.ir_cron.ir_cron._acquire_job(db_name)
|
||||
if not acquired:
|
||||
break
|
||||
|
||||
@@ -32,8 +32,7 @@ import StringIO
|
||||
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import platform
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
@@ -43,7 +42,6 @@ import werkzeug.serving
|
||||
import werkzeug.contrib.fixers
|
||||
|
||||
import openerp
|
||||
import openerp.modules
|
||||
import openerp.tools.config as config
|
||||
import websrv_lib
|
||||
|
||||
@@ -426,7 +424,11 @@ def serve(interface, port, threaded):
|
||||
"""
|
||||
|
||||
global httpd
|
||||
httpd = werkzeug.serving.make_server(interface, port, application, threaded=threaded)
|
||||
if not openerp.evented:
|
||||
httpd = werkzeug.serving.make_server(interface, port, application, threaded=threaded)
|
||||
else:
|
||||
from gevent.wsgi import WSGIServer
|
||||
httpd = WSGIServer((interface, port), application)
|
||||
httpd.serve_forever()
|
||||
|
||||
def start_service():
|
||||
@@ -446,8 +448,13 @@ def stop_service():
|
||||
The server is supposed to have been started by start_server() above.
|
||||
"""
|
||||
if httpd:
|
||||
httpd.shutdown()
|
||||
close_socket(httpd.socket)
|
||||
if not openerp.evented:
|
||||
httpd.shutdown()
|
||||
close_socket(httpd.socket)
|
||||
else:
|
||||
import gevent
|
||||
httpd.stop()
|
||||
gevent.shutdown()
|
||||
|
||||
def close_socket(sock):
|
||||
""" Closes a socket instance cleanly
|
||||
|
||||
@@ -20,7 +20,8 @@ class m(openerp.osv.osv.Model):
|
||||
raise openerp.exceptions.Warning('description')
|
||||
|
||||
def generate_redirect_warning(self, cr, uid, ids, context=None):
|
||||
raise openerp.exceptions.RedirectWarning('description', self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'menu_action_res_users'), 'go to the redirection')
|
||||
dummy, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'test_exceptions', 'action_test_exceptions')
|
||||
raise openerp.exceptions.RedirectWarning('description', action_id, 'go to the redirection')
|
||||
|
||||
def generate_access_denied(self, cr, uid, ids, context=None):
|
||||
raise openerp.exceptions.AccessDenied()
|
||||
@@ -34,4 +35,34 @@ class m(openerp.osv.osv.Model):
|
||||
def generate_undefined(self, cr, uid, ids, context=None):
|
||||
self.surely_undefined_symbol
|
||||
|
||||
|
||||
def generate_except_osv_safe_eval(self, cr, uid, ids, context=None):
|
||||
self.generate_safe_eval(cr, uid, ids, self.generate_except_osv, context)
|
||||
|
||||
def generate_except_orm_safe_eval(self, cr, uid, ids, context=None):
|
||||
self.generate_safe_eval(cr, uid, ids, self.generate_except_orm, context)
|
||||
|
||||
def generate_warning_safe_eval(self, cr, uid, ids, context=None):
|
||||
self.generate_safe_eval(cr, uid, ids, self.generate_warning, context)
|
||||
|
||||
def generate_redirect_warning_safe_eval(self, cr, uid, ids, context=None):
|
||||
self.generate_safe_eval(cr, uid, ids, self.generate_redirect_warning, context)
|
||||
|
||||
def generate_access_denied_safe_eval(self, cr, uid, ids, context=None):
|
||||
self.generate_safe_eval(cr, uid, ids, self.generate_access_denied, context)
|
||||
|
||||
def generate_access_error_safe_eval(self, cr, uid, ids, context=None):
|
||||
self.generate_safe_eval(cr, uid, ids, self.generate_access_error, context)
|
||||
|
||||
def generate_exc_access_denied_safe_eval(self, cr, uid, ids, context=None):
|
||||
self.generate_safe_eval(cr, uid, ids, self.generate_exc_access_denied, context)
|
||||
|
||||
def generate_undefined_safe_eval(self, cr, uid, ids, context=None):
|
||||
self.generate_safe_eval(cr, uid, ids, self.generate_undefined, context)
|
||||
|
||||
|
||||
def generate_safe_eval(self, cr, uid, ids, f, context):
|
||||
globals_dict = { 'generate': lambda *args: f(cr, uid, ids, context) }
|
||||
openerp.tools.safe_eval.safe_eval("generate()", mode='exec', globals_dict=globals_dict)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
<field name="model">test.exceptions.model</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Test exceptions">
|
||||
<label string="Each button generates a specific exception on the server. The text on the right is the expected representation of the exception when displayed on the client."/>
|
||||
<label string="Each button generates a specific exception on the server. The text on the right is the expected representation of the exception when displayed on the client. Button marked with a '*' use safe_eval()."/>
|
||||
<separator string="" colspan="8"/>
|
||||
<group colspan="8" col="8">
|
||||
<group colspan="4" col="8">
|
||||
<group colspan="8" col="8">
|
||||
<separator string="" colspan="8"/>
|
||||
<button name="generate_except_osv" string="except_osv" type="object" icon="gtk-ok" colspan="1"/>
|
||||
<label string="Warning-description"/>
|
||||
</group>
|
||||
@@ -41,6 +43,42 @@
|
||||
<button name="generate_undefined" string="Undefined" type="object" icon="gtk-ok" colspan="1"/>
|
||||
<label string="Server error-traceback"/>
|
||||
</group>
|
||||
</group>
|
||||
<group colspan="4" col="8">
|
||||
<group colspan="8" col="8">
|
||||
<button name="generate_except_osv_safe_eval" string="except_osv*" type="object" icon="gtk-ok" colspan="1"/>
|
||||
<label string="Warning-description"/>
|
||||
</group>
|
||||
<group colspan="8" col="8">
|
||||
<button name="generate_except_orm_safe_eval" string="except_orm*" type="object" icon="gtk-ok" colspan="1"/>
|
||||
<label string="Warning-description"/>
|
||||
</group>
|
||||
<group colspan="8" col="8">
|
||||
<button name="generate_warning_safe_eval" string="Warning*" type="object" icon="gtk-ok" colspan="1"/>
|
||||
<label string="Warning-description"/>
|
||||
</group>
|
||||
<group colspan="8" col="8">
|
||||
<button name="generate_redirect_warning_safe_eval" string="RedirectWarning*" type="object" icon="gtk-ok" colspan="1"/>
|
||||
<label string="Warning-description-redirection button"/>
|
||||
</group>
|
||||
<group colspan="8" col="8">
|
||||
<button name="generate_access_denied_safe_eval" string="AccessDenied*" type="object" icon="gtk-ok" colspan="1"/>
|
||||
<label string="Access denied-traceback"/>
|
||||
</group>
|
||||
<group colspan="8" col="8">
|
||||
<button name="generate_access_error_safe_eval" string="AccessError*" type="object" icon="gtk-ok" colspan="1"/>
|
||||
<label string="Access rights error-description"/>
|
||||
</group>
|
||||
<group colspan="8" col="8">
|
||||
<button name="generate_exc_access_denied_safe_eval" string="Exc AccessDenied*" type="object" icon="gtk-ok" colspan="1"/>
|
||||
<label string="Access denied-traceback"/>
|
||||
</group>
|
||||
<group colspan="8" col="8">
|
||||
<button name="generate_undefined_safe_eval" string="Undefined*" type="object" icon="gtk-ok" colspan="1"/>
|
||||
<label string="Server error-traceback"/>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user