[MERGE]:Merged from trunk
bzr revid: apa@tinyerp.com-20100924045528-zgmlmd1n2fvetyxw
This commit is contained in:
+24
-12
@@ -757,16 +757,21 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs):
|
||||
|
||||
return has_updates
|
||||
|
||||
def _check_module_names(cr, module_names):
|
||||
mod_names = set(module_names)
|
||||
if 'base' in mod_names:
|
||||
# ignore dummy 'all' module
|
||||
if 'all' in mod_names:
|
||||
mod_names.remove('all')
|
||||
if mod_names:
|
||||
cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
|
||||
if cr.dictfetchone()['count'] != len(mod_names):
|
||||
# find out what module name(s) are incorrect:
|
||||
cr.execute("SELECT name FROM ir_module_module")
|
||||
incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
|
||||
logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
|
||||
|
||||
def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||
|
||||
def check_module_name(cr, mods, state):
|
||||
for mod in mods:
|
||||
id = modobj.search(cr, 1, ['&', ('state', '=', state), ('name', '=', mod)])
|
||||
if id:
|
||||
getattr(modobj, states[state])(cr, 1, id)
|
||||
elif mod != 'all':
|
||||
logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: invalid module name!' % (mod))
|
||||
|
||||
if not status:
|
||||
status = {}
|
||||
cr = db.cursor()
|
||||
@@ -795,16 +800,23 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||
|
||||
if update_module:
|
||||
modobj = pool.get('ir.module.module')
|
||||
states = {'installed': 'button_upgrade', 'uninstalled': 'button_install'}
|
||||
logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
|
||||
if ('base' in tools.config['init']) or ('base' in tools.config['update']):
|
||||
modobj.update_list(cr, 1)
|
||||
|
||||
_check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
|
||||
|
||||
mods = [k for k in tools.config['init'] if tools.config['init'][k]]
|
||||
check_module_name(cr, mods, 'uninstalled')
|
||||
if mods:
|
||||
ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
|
||||
if ids:
|
||||
modobj.button_install(cr, 1, ids)
|
||||
|
||||
mods = [k for k in tools.config['update'] if tools.config['update'][k]]
|
||||
check_module_name(cr, mods, 'installed')
|
||||
if mods:
|
||||
ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
|
||||
if ids:
|
||||
modobj.button_upgrade(cr, 1, ids)
|
||||
|
||||
cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
'module/wizard/base_language_install_view.xml',
|
||||
'module/wizard/base_import_language_view.xml',
|
||||
'module/wizard/base_module_upgrade_view.xml',
|
||||
#TODO: 'module/wizard/base_module_configuration_view.xml',
|
||||
'module/wizard/base_module_configuration_view.xml',
|
||||
'module/wizard/base_export_language_view.xml',
|
||||
'module/wizard/base_update_translations_view.xml',
|
||||
'res/res_request_view.xml',
|
||||
|
||||
@@ -1335,8 +1335,8 @@
|
||||
</record>
|
||||
|
||||
<record id="INR" model="res.currency">
|
||||
<field name="name">INR</field>
|
||||
<field name="code">Rs</field>
|
||||
<field name="name">Rs</field>
|
||||
<field name="code">INR</field>
|
||||
<field name="symbol">Rs</field>
|
||||
<field name="rounding">0.01</field>
|
||||
<field name="accuracy">4</field>
|
||||
|
||||
@@ -1906,11 +1906,10 @@
|
||||
<tree editable="bottom" string="Config Wizard Steps">
|
||||
<field name="sequence"/>
|
||||
<field name="action_id"/>
|
||||
<field name="restart"/>
|
||||
<field name="state" readonly="1"/>
|
||||
<button name="action_launch" states="open" string="Launch" type="object" icon="gtk-execute" help="Launch Configuration Wizard"/>
|
||||
<button name="action_open" states="cancel,skip,done"
|
||||
string="Todo" type="object" help="Set as Todo"
|
||||
icon="gtk-convert"/>
|
||||
<button name="action_open" states="cancel,skip,done" string="Set as todo" type="object" icon="gtk-convert"/>
|
||||
<button name="action_launch" states="open" string="Launch this wizard" type="object" icon="gtk-execute"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -1930,10 +1929,8 @@
|
||||
<field name="groups_id" nolabel="1" colspan="4"/>
|
||||
<group colspan="4" col="4">
|
||||
<field name="state" colspan="2" readonly="1"/>
|
||||
<button name="action_launch" states="open" string="Launch" type="object" icon="gtk-execute" help="Launch Configuration Wizard"/>
|
||||
<button name="action_open" states="cancel,skip,done"
|
||||
string="Set as Todo" type="object"
|
||||
icon="gtk-convert"/>
|
||||
<button name="action_launch" states="open" string="Launch" type="object" icon="gtk-execute" help="Launch this wizard"/>
|
||||
<button name="action_open" states="cancel,skip,done" string="Set as todo" type="object" icon="gtk-convert"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
@@ -1961,14 +1958,8 @@
|
||||
</record>
|
||||
<menuitem id="next_id_11" name="Configuration Wizards" parent="base.menu_config" sequence="1"/>
|
||||
|
||||
<wizard id="wizard_configuration" model="ir.module.module" name="module.configuration" string="Start Configuration"/>
|
||||
<menuitem action="act_ir_actions_todo_form" id="menu_ir_actions_todo_form"
|
||||
parent="next_id_11" groups="base.group_extended" sequence="20"/>
|
||||
|
||||
<menuitem
|
||||
action="wizard_configuration" name="Start Configuration"
|
||||
id="menu_configurator_module" parent="next_id_11"
|
||||
type="wizard"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -512,14 +512,21 @@ class actions_server(osv.osv):
|
||||
|
||||
return obj
|
||||
|
||||
def merge_message(self, cr, uid, keystr, action, context):
|
||||
def merge_message(self, cr, uid, keystr, action, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
logger = netsvc.Logger()
|
||||
def merge(match):
|
||||
obj_pool = self.pool.get(action.model_id.model)
|
||||
id = context.get('active_id')
|
||||
obj = obj_pool.browse(cr, uid, id)
|
||||
exp = str(match.group()[2:-2]).strip()
|
||||
result = eval(exp, {'object':obj, 'context': context,'time':time})
|
||||
result = eval(exp,
|
||||
{
|
||||
'object': obj,
|
||||
'context': dict(context), # copy context to prevent side-effects of eval
|
||||
'time': time,
|
||||
})
|
||||
if result in (None, False):
|
||||
return str("--------")
|
||||
return tools.ustr(result)
|
||||
@@ -536,13 +543,16 @@ class actions_server(osv.osv):
|
||||
# False : Finnished correctly
|
||||
# ACTION_ID : Action to launch
|
||||
|
||||
def run(self, cr, uid, ids, context={}):
|
||||
# FIXME: refactor all the eval() calls in run()!
|
||||
def run(self, cr, uid, ids, context=None):
|
||||
logger = netsvc.Logger()
|
||||
if context is None:
|
||||
context = {}
|
||||
for action in self.browse(cr, uid, ids, context):
|
||||
obj_pool = self.pool.get(action.model_id.model)
|
||||
obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
|
||||
cxt = {
|
||||
'context':context,
|
||||
'context': dict(context), # copy context to prevent side-effects of eval
|
||||
'object': obj,
|
||||
'time':time,
|
||||
'cr': cr,
|
||||
@@ -560,26 +570,19 @@ class actions_server(osv.osv):
|
||||
.read(cr, uid, action.action_id.id, context=context)
|
||||
|
||||
if action.state=='code':
|
||||
if config['server_actions_allow_code']:
|
||||
localdict = {
|
||||
'self': self.pool.get(action.model_id.model),
|
||||
'context': context,
|
||||
'time': time,
|
||||
'ids': ids,
|
||||
'cr': cr,
|
||||
'uid': uid,
|
||||
'object':obj,
|
||||
'obj': obj,
|
||||
}
|
||||
eval(action.code, localdict, mode="exec")
|
||||
if 'action' in localdict:
|
||||
return localdict['action']
|
||||
else:
|
||||
netsvc.Logger().notifyChannel(
|
||||
self._name, netsvc.LOG_ERROR,
|
||||
"%s is a `code` server action, but "
|
||||
"it isn't allowed in this configuration.\n\n"
|
||||
"See server options to enable it"%action)
|
||||
localdict = {
|
||||
'self': self.pool.get(action.model_id.model),
|
||||
'context': dict(context), # copy context to prevent side-effects of eval
|
||||
'time': time,
|
||||
'ids': ids,
|
||||
'cr': cr,
|
||||
'uid': uid,
|
||||
'object':obj,
|
||||
'obj': obj,
|
||||
}
|
||||
eval(action.code, localdict, mode="exec", nocopy=True) # nocopy allows to return 'action'
|
||||
if 'action' in localdict:
|
||||
return localdict['action']
|
||||
|
||||
if action.state == 'email':
|
||||
user = config['email_from']
|
||||
@@ -631,9 +634,9 @@ class actions_server(osv.osv):
|
||||
obj_pool = self.pool.get(action.model_id.model)
|
||||
obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
|
||||
cxt = {
|
||||
'context':context,
|
||||
'context': dict(context), # copy context to prevent side-effects of eval
|
||||
'object': obj,
|
||||
'time':time,
|
||||
'time': time,
|
||||
'cr': cr,
|
||||
'pool' : self.pool,
|
||||
'uid' : uid
|
||||
@@ -651,7 +654,11 @@ class actions_server(osv.osv):
|
||||
if exp.type == 'equation':
|
||||
obj_pool = self.pool.get(action.model_id.model)
|
||||
obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
|
||||
cxt = {'context':context, 'object': obj, 'time':time}
|
||||
cxt = {
|
||||
'context': dict(context), # copy context to prevent side-effects of eval
|
||||
'object': obj,
|
||||
'time': time,
|
||||
}
|
||||
expr = eval(euq, cxt)
|
||||
else:
|
||||
expr = exp.value
|
||||
@@ -687,7 +694,12 @@ class actions_server(osv.osv):
|
||||
if exp.type == 'equation':
|
||||
obj_pool = self.pool.get(action.model_id.model)
|
||||
obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
|
||||
expr = eval(euq, {'context':context, 'object': obj, 'time':time})
|
||||
expr = eval(euq,
|
||||
{
|
||||
'context': dict(context), # copy context to prevent side-effects of eval
|
||||
'object': obj,
|
||||
'time': time,
|
||||
})
|
||||
else:
|
||||
expr = exp.value
|
||||
res[exp.col1.name] = expr
|
||||
@@ -706,7 +718,12 @@ class actions_server(osv.osv):
|
||||
if exp.type == 'equation':
|
||||
obj_pool = self.pool.get(action.model_id.model)
|
||||
obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
|
||||
expr = eval(euq, {'context':context, 'object': obj, 'time':time})
|
||||
expr = eval(euq,
|
||||
{
|
||||
'context': dict(context), # copy context to prevent side-effects of eval
|
||||
'object': obj,
|
||||
'time': time,
|
||||
})
|
||||
else:
|
||||
expr = exp.value
|
||||
res[exp.col1.name] = expr
|
||||
@@ -736,7 +753,7 @@ act_window_close()
|
||||
TODO_STATES = [('open', 'To Do'),
|
||||
('done', 'Done'),
|
||||
('skip','Skipped'),
|
||||
('cancel','Cancel')]
|
||||
('cancel','Cancelled')]
|
||||
|
||||
class ir_actions_todo(osv.osv):
|
||||
_name = 'ir.actions.todo'
|
||||
@@ -752,9 +769,9 @@ class ir_actions_todo(osv.osv):
|
||||
'note':fields.text('Text', translate=True),
|
||||
}
|
||||
_defaults={
|
||||
'state': lambda *a: 'open',
|
||||
'sequence': lambda *a: 10,
|
||||
'restart': lambda *a: 'always',
|
||||
'state': 'open',
|
||||
'sequence': 10,
|
||||
'restart': 'onskip',
|
||||
}
|
||||
_order="sequence,id"
|
||||
|
||||
|
||||
@@ -105,8 +105,8 @@ class ir_attachment(osv.osv):
|
||||
'datas': fields.binary('Data'),
|
||||
'datas_fname': fields.char('Filename',size=256),
|
||||
'description': fields.text('Description'),
|
||||
'res_name': fields.function(_name_get_resname, type='char',
|
||||
string='Resource Name', method=True),
|
||||
'res_name': fields.function(_name_get_resname, type='char', size=128,
|
||||
string='Resource Name', method=True, store=True),
|
||||
'res_model': fields.char('Resource Object',size=64, readonly=True,
|
||||
help="The database object this attachment will be attached to"),
|
||||
'res_id': fields.integer('Resource ID', readonly=True,
|
||||
|
||||
@@ -88,7 +88,7 @@ class report_custom(osv.osv):
|
||||
c_f[i] = []
|
||||
tmp = report.fields_child0[i]
|
||||
for j in range(3):
|
||||
c_f[i].append((not isinstance(eval('tmp.field_child'+str(j)), browse_null) and eval('tmp.field_child'+str(j)+'.ttype')) or None)
|
||||
c_f[i].append((not isinstance(eval('tmp.field_child'+str(j),{},{'tmp':tmp}), browse_null) and eval('tmp.field_child'+str(j)+'.ttype',{},{'tmp':tmp})) or None)
|
||||
if not reduce(lambda x,y : x or y, map(lambda x: x in ['integer', 'float'], c_f[1])):
|
||||
raise osv.except_osv(_('Invalid operation'), _('Second field should be figures'))
|
||||
|
||||
@@ -101,7 +101,7 @@ class report_custom(osv.osv):
|
||||
c_f[i] = []
|
||||
tmp = report.fields_child0[i]
|
||||
for j in range(3):
|
||||
c_f[i].append((not isinstance(eval('tmp.field_child'+str(j)), browse_null) and eval('tmp.field_child'+str(j)+'.ttype')) or None)
|
||||
c_f[i].append((not isinstance(eval('tmp.field_child'+str(j),{},{'tmp':tmp}), browse_null) and eval('tmp.field_child'+str(j)+'.ttype',{},{'tmp':tmp})) or None)
|
||||
|
||||
if i == 0:
|
||||
pass
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="maintenance_contract_search_view" model="ir.ui.view">
|
||||
<field name="name">maintenance.contract.search</field>
|
||||
<field name="model">maintenance.contract</field>
|
||||
@@ -51,7 +51,7 @@
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="maintenance_contract_view_calendar" model="ir.ui.view">
|
||||
<field name="name">maintenance.contract.calendar</field>
|
||||
<field name="model">maintenance.contract</field>
|
||||
@@ -106,9 +106,9 @@
|
||||
</group>
|
||||
</group>
|
||||
<group colspan="4">
|
||||
<button type="object" string="_Close" icon="gtk-cancel" special="cancel" states="draft"/>
|
||||
<button type="object" string="_Cancel" icon="gtk-cancel" special="cancel" states="draft"/>
|
||||
<button type="object" string="_Validate" icon="gtk-apply" name="action_validate" states="draft"/>
|
||||
<button type="object" string="_Close" icon="gtk-close" special="cancel" states="validated,unvalidated"/>
|
||||
<button type="object" string="_Cancel" icon="gtk-cancel" special="cancel" states="validated,unvalidated"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
@@ -130,8 +130,8 @@ class module(osv.osv):
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'name': fields.char("Name", size=128, readonly=True, required=True),
|
||||
'category_id': fields.many2one('ir.module.category', 'Category', readonly=True),
|
||||
'name': fields.char("Name", size=128, readonly=True, required=True, select=True),
|
||||
'category_id': fields.many2one('ir.module.category', 'Category', readonly=True, select=True),
|
||||
'shortdesc': fields.char('Short Description', size=256, readonly=True, translate=True),
|
||||
'description': fields.text("Description", readonly=True, translate=True),
|
||||
'author': fields.char("Author", size=128, readonly=True),
|
||||
@@ -160,7 +160,7 @@ class module(osv.osv):
|
||||
('to upgrade','To be upgraded'),
|
||||
('to remove','To be removed'),
|
||||
('to install','To be installed')
|
||||
], string='State', readonly=True),
|
||||
], string='State', readonly=True, select=True),
|
||||
'demo': fields.boolean('Demo data'),
|
||||
'license': fields.selection([
|
||||
('GPL-2', 'GPL Version 2'),
|
||||
@@ -510,7 +510,7 @@ class web_module_dependency(osv.osv):
|
||||
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=128),
|
||||
'name': fields.char('Name', size=128, select=True),
|
||||
'module_id': fields.many2one('ir.module.module', 'Module', select=True, ondelete='cascade'),
|
||||
'web_module_id': fields.many2one('ir.module.web', 'Web Module', select=True, ondelete='cascade'),
|
||||
'state': fields.function(_state, method=True, type='selection', selection=[
|
||||
@@ -521,7 +521,7 @@ class web_module_dependency(osv.osv):
|
||||
('to remove','To be removed'),
|
||||
('to install','To be installed'),
|
||||
('unknown', 'Unknown'),
|
||||
], string='State', readonly=True),
|
||||
], string='State', readonly=True, select=True),
|
||||
}
|
||||
|
||||
web_module_dependency()
|
||||
@@ -542,7 +542,7 @@ class module_dependency(osv.osv):
|
||||
return result
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=128),
|
||||
'name': fields.char('Name', size=128, select=True),
|
||||
'module_id': fields.many2one('ir.module.module', 'Module', select=True, ondelete='cascade'),
|
||||
'state': fields.function(_state, method=True, type='selection', selection=[
|
||||
('uninstallable','Uninstallable'),
|
||||
@@ -552,7 +552,7 @@ class module_dependency(osv.osv):
|
||||
('to remove','To be removed'),
|
||||
('to install','To be installed'),
|
||||
('unknown', 'Unknown'),
|
||||
], string='State', readonly=True),
|
||||
], string='State', readonly=True, select=True),
|
||||
}
|
||||
module_dependency()
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -162,9 +162,9 @@
|
||||
<field name="installed_version"/>
|
||||
<field name="latest_version"/>
|
||||
<field name="state"/>
|
||||
<button name="button_install" states="uninstalled" string="Schedule for Installation" icon="terp-gtk-jump-to-ltr" type="object" help="Schedule for Installation"/>
|
||||
<button name="button_install" states="uninstalled" string="Schedule for Installation" icon="terp-gtk-jump-to-ltr" type="object"/>
|
||||
<button name="button_install_cancel" states="to install" string="Cancel Install" icon="gtk-cancel" type="object"/>
|
||||
<button name="button_upgrade" states="installed" string="Schedule Upgrade" icon="terp-gtk-go-back-rtl" type="object" help="Schedule Upgrade"/>
|
||||
<button name="button_upgrade" states="installed" string="Schedule Upgrade" icon="terp-gtk-go-back-rtl" type="object"/>
|
||||
<button name="button_uninstall" states="installed" string="Uninstall (beta)" icon="terp-dialog-close" type="object"/>
|
||||
<button name="button_uninstall_cancel" states="to remove" string="Cancel Uninstall" icon="gtk-cancel" type="object"/>
|
||||
<button name="button_upgrade_cancel" states="to upgrade" string="Cancel Upgrade" icon="gtk-cancel" type="object"/>
|
||||
|
||||
@@ -19,9 +19,6 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
#Old Wizard
|
||||
import wizard_configuration
|
||||
|
||||
import base_module_import
|
||||
import base_module_update
|
||||
import base_language_install
|
||||
|
||||
@@ -27,10 +27,10 @@
|
||||
<group col="2" colspan="2">
|
||||
</group>
|
||||
<group col="2" colspan="2">
|
||||
<button icon="gtk-cancel" name="act_cancel" special="cancel" states="choose" string="_Close" type="object"/>
|
||||
<button icon="gtk-cancel" name="act_cancel" special="cancel" states="choose" string="_Cancel" type="object"/>
|
||||
<button icon="gtk-ok" name="act_getfile" states="choose" string="_Export" type="object"></button>
|
||||
<button icon="gtk-close" name="act_destroy" special="cancel" states="get" string="_Close" type="object"/>
|
||||
</group>
|
||||
<button icon="gtk-cancel" name="act_destroy" special="cancel" states="get" string="_Cancel" type="object"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Help">
|
||||
<label align="0.0" colspan="4" string="The official translations pack of all OpenERP/OpenObjects module are managed through launchpad. We use their online interface to synchronize all translations efforts."/>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<group colspan="2" col="2">
|
||||
</group>
|
||||
<group colspan="2" col="2">
|
||||
<button special="cancel" string="_Close" icon="gtk-cancel"/>
|
||||
<button special="cancel" string="_Cancel" icon="gtk-cancel"/>
|
||||
<button name="import_lang" string="_Import" type="object" icon="gtk-ok"/>
|
||||
</group>
|
||||
</page>
|
||||
@@ -32,7 +32,7 @@
|
||||
<label string="You can also import .po files." colspan="4" align="0.0"/>
|
||||
</page>
|
||||
</notebook>
|
||||
|
||||
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -3,27 +3,24 @@
|
||||
<data>
|
||||
|
||||
<record id="view_base_language_install" model="ir.ui.view">
|
||||
<field name="name">Install Language</field>
|
||||
<field name="name">Load Official Translation</field>
|
||||
<field name="model">base.language.install</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="System Upgrade">
|
||||
<group colspan="2" col="4">
|
||||
<separator string="System Upgrade" colspan="6"/>
|
||||
<newline/>
|
||||
<label align="0.0" string="Choose a language to install:" colspan="4"/>
|
||||
<field name="lang" colspan="4"/>
|
||||
<label align="0.0" string="Note that this operation may take a few minutes." colspan="4"/>
|
||||
<separator string="" colspan="6"/>
|
||||
<button special="cancel" string="Close" icon="gtk-cancel"/>
|
||||
<button name="lang_install" string="Start installation" type="object" icon="gtk-ok"/>
|
||||
</group>
|
||||
</form>
|
||||
<form string="Load Official Translation">
|
||||
<separator string="Load an Official Translation" colspan="4"/>
|
||||
<group colspan="4"><field name="lang"/></group>
|
||||
<label string="" colspan="4"/>
|
||||
<label string="Please be patient, as this operation may take a few minutes..." colspan="4"/>
|
||||
<separator string="" colspan="4"/>
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
|
||||
<button name="lang_install" string="Load" type="object" icon="gtk-ok"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_base_language_install" model="ir.actions.act_window">
|
||||
<field name="name">Install Language</field>
|
||||
<field name="name">Load Official Translation</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">base.language.install</field>
|
||||
<field name="view_type">form</field>
|
||||
@@ -38,23 +35,21 @@
|
||||
parent="menu_translation"/>
|
||||
|
||||
<record id="view_base_language_install_msg" model="ir.ui.view">
|
||||
<field name="name">Install Language Message</field>
|
||||
<field name="name">Official Translation Loaded</field>
|
||||
<field name="model">base.module.import.msg</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Language file loaded.">
|
||||
<group colspan="2" col="4">
|
||||
<separator string="Installation Done" colspan="4"/>
|
||||
<label align="0.0" string="The selected language has been successfully installed.\nYou must change the preferences of the user and open a new menu to view changes." colspan="4"/>
|
||||
<separator string="" colspan="4"/>
|
||||
<button special="cancel" string="OK" icon="gtk-ok"/>
|
||||
</group>
|
||||
<form string="Official Translation Loaded">
|
||||
<separator string="Official Translation Loaded" colspan="4"/>
|
||||
<label string="The selected language has been successfully installed.\nYou must change the preferences of the user and open a new menu to view the changes." colspan="4"/>
|
||||
<separator string="" colspan="4"/>
|
||||
<button special="cancel" string="OK" icon="gtk-ok"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_base_language_install_msg" model="ir.actions.act_window">
|
||||
<field name="name">Install Language Message</field>
|
||||
<field name="name">Official Translation Loaded</field>
|
||||
<field name="res_model">base.module.import.msg</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
|
||||
@@ -19,15 +19,33 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import pooler
|
||||
from osv import osv, fields
|
||||
from osv import osv
|
||||
from tools.translate import _
|
||||
|
||||
class base_module_configuration(osv.osv_memory):
|
||||
|
||||
_name = "base.module.configuration"
|
||||
_name = "base.module.configuration"
|
||||
|
||||
def config(self, cr, uid, data, context=None):
|
||||
return self.pool.get('res.config').next(cr, uid, [], context=context)
|
||||
def start(self, cr, uid, ids, context=None):
|
||||
todo_ids = self.pool.get('ir.actions.todo').search(cr, uid, ['|', ('state', '=', 'open'), ('restart', '=', 'always')])
|
||||
if not todo_ids:
|
||||
# When there is no wizard todo it will display message
|
||||
data_obj = self.pool.get('ir.model.data')
|
||||
result = data_obj._get_id(cr, uid, 'base', 'view_base_module_configuration_form')
|
||||
view_id = data_obj.browse(cr, uid, result).res_id
|
||||
value = {
|
||||
'name': _('System Configuration done'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'base.module.configuration',
|
||||
'view_id': [view_id],
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'new'
|
||||
}
|
||||
return value
|
||||
# Run the config wizards
|
||||
config_pool = self.pool.get('res.config')
|
||||
return config_pool.start(cr, uid, ids, context=context)
|
||||
|
||||
base_module_configuration()
|
||||
|
||||
|
||||
@@ -2,35 +2,42 @@
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="view_base_module_configuration" model="ir.ui.view">
|
||||
<record id="view_base_module_configuration_form" model="ir.ui.view">
|
||||
<field name="name">Module Configuration</field>
|
||||
<field name="model">base.module.configuration</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="">
|
||||
</form>
|
||||
<form string="System Configuration Done">
|
||||
<label align="0.0" string="All pending configuration wizards have been executed. You may restart individual wizards via the list of configuration wizards." colspan="4"/>
|
||||
<separator string="" colspan="4"/>
|
||||
<newline/>
|
||||
<button special="cancel" string="_Ok" icon="gtk-ok"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_base_module_configuration" model="ir.actions.act_window">
|
||||
<field name="name">Module Configuration</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">base.module.configuration</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
|
||||
<record id="action_start_configurator" model="ir.actions.server">
|
||||
<field name="name">Start Configuration</field>
|
||||
<field name="model_id" ref="model_base_module_configuration"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">action = obj.start([])</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
name="Start Configuration"
|
||||
action="action_view_base_module_configuration"
|
||||
id="menu_view_base_module_configuration"
|
||||
parent="next_id_11"
|
||||
sequence="2"/>
|
||||
<menuitem name="Start Configuration"
|
||||
action="action_start_configurator"
|
||||
id="menu_view_base_module_configuration" parent="next_id_11"
|
||||
type="server" icon="STOCK_EXECUTE" sequence="2" />
|
||||
|
||||
<act_window id="action_view_base_module_configuration_window"
|
||||
key2="client_action_multi" name="Start Configuration"
|
||||
res_model="base.module.configuration" src_model="ir.module.module"
|
||||
view_mode="form" target="new" view_type="form" />
|
||||
<record model="ir.values" id="action_todo_config">
|
||||
<field name="model_id" ref="model_ir_actions_todo" />
|
||||
<field name="object" eval="1" />
|
||||
<field name="name">Start Configuration</field>
|
||||
<field name="key2">client_action_multi</field>
|
||||
<field name="value" eval="'ir.actions.server,' + str(ref('action_start_configurator'))" />
|
||||
<field name="key">action</field>
|
||||
<field name="model">ir.actions.todo</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</openerp>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<separator string="" colspan="4" />
|
||||
<newline/>
|
||||
<group colspan="6" >
|
||||
<button special="cancel" string="Close" icon="gtk-cancel"/>
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
|
||||
<button name="importzip" string="Import module" type="object" icon="gtk-ok"/>
|
||||
</group>
|
||||
</form>
|
||||
@@ -47,7 +47,7 @@
|
||||
<separator string="" colspan="4"/>
|
||||
<newline/>
|
||||
<group>
|
||||
<button name="action_module_open" string="Close" type="object" icon="gtk-cancel"/>
|
||||
<button name="action_module_open" string="Cancel" type="object" icon="gtk-cancel"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<separator string="" colspan="4" />
|
||||
<newline/>
|
||||
<group colspan="6" >
|
||||
<button special="cancel" string="Close" icon="gtk-cancel"/>
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
|
||||
<button name="update_module" string="Check new modules" type="object" icon="gtk-ok"/>
|
||||
</group>
|
||||
</form>
|
||||
@@ -47,7 +47,7 @@
|
||||
<separator string="" colspan="4" />
|
||||
<newline/>
|
||||
<group colspan="6" >
|
||||
<button special="cancel" string="Close" icon="gtk-cancel"/>
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
|
||||
<button name="action_module_open" string="OK" type="object" icon="gtk-ok"/>
|
||||
</group>
|
||||
</form>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<separator string="Modules to update" colspan="4"/>
|
||||
<field name="module_info" nolabel="1" colspan="4"/>
|
||||
<separator string="" colspan="4"/>
|
||||
<button special="cancel" string="Close" icon="gtk-cancel"/>
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
|
||||
<button name="upgrade_module" string="Start update" type="object" icon="gtk-ok"/>
|
||||
</group>
|
||||
</form>
|
||||
@@ -53,7 +53,7 @@
|
||||
<label align="0.0" string="We suggest to reload the menu tab to see the new menus (Ctrl+T then Ctrl+R)." colspan="4"/>
|
||||
<separator string="" colspan="4"/>
|
||||
<newline/>
|
||||
<button special="cancel" string="Close" icon="gtk-cancel"/>
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
|
||||
<button name="config" string="Start configuration" type="object" icon="gtk-ok"/>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<label align="0.0" colspan="4" string="This wizard will detect new terms in the application so that you can update them manually."/>
|
||||
<separator colspan="4"/>
|
||||
<group col="2" colspan="4" fill="0">
|
||||
<button icon="gtk-cancel" name="act_cancel" special="cancel" string="Close" type="object"/>
|
||||
<button icon="gtk-cancel" name="act_cancel" special="cancel" string="Cancel" type="object"/>
|
||||
<button icon="gtk-ok" name="act_update" string="Update" type="object"/>
|
||||
</group>
|
||||
</form>
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import wizard
|
||||
import pooler
|
||||
|
||||
class wizard_configuration(wizard.interface):
|
||||
|
||||
def _config(self, cr, uid, data, context=None):
|
||||
return pooler.get_pool(cr.dbname).get('res.config')\
|
||||
.next(cr, uid, [], context=context)
|
||||
|
||||
states = {
|
||||
'init':{
|
||||
'result': {
|
||||
'type': 'action',
|
||||
'action': _config,
|
||||
'state': 'end',
|
||||
},
|
||||
}
|
||||
}
|
||||
wizard_configuration('module.configuration')
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -31,7 +31,6 @@ class ir_property(osv.osv):
|
||||
|
||||
def _models_field_get(self, cr, uid, field_key, field_value, context=None):
|
||||
get = attrgetter(field_key, field_value)
|
||||
|
||||
obj = self.pool.get('ir.model.fields')
|
||||
ids = obj.search(cr, uid, [('view_load','=',1)], context=context)
|
||||
res = set()
|
||||
@@ -40,19 +39,19 @@ class ir_property(osv.osv):
|
||||
return res
|
||||
|
||||
def _models_get(self, cr, uid, context=None):
|
||||
return self._models_field_get(cr, uid, 'model', 'name', context)
|
||||
return self._models_field_get(cr, uid, 'model', 'model_id.name', context)
|
||||
|
||||
def _models_get2(self, cr, uid, context=None):
|
||||
return self._models_field_get(cr, uid, 'relation', 'relation', context)
|
||||
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=128),
|
||||
'name': fields.char('Name', size=128, select=1),
|
||||
|
||||
'res_id': fields.reference('Resource', selection=_models_get, size=128,
|
||||
help="If not set, act as default property"),
|
||||
'company_id': fields.many2one('res.company', 'Company'),
|
||||
'fields_id': fields.many2one('ir.model.fields', 'Fields', ondelete='cascade', required=True),
|
||||
help="If not set, acts as a default value for new resources", select=1),
|
||||
'company_id': fields.many2one('res.company', 'Company', select=1),
|
||||
'fields_id': fields.many2one('ir.model.fields', 'Field', ondelete='cascade', required=True, select=1),
|
||||
|
||||
'value_float' : fields.float('Value'),
|
||||
'value_integer' : fields.integer_big('Value'), # will contain (int, bigint)
|
||||
@@ -135,15 +134,7 @@ class ir_property(osv.osv):
|
||||
def create(self, cr, uid, values, context=None):
|
||||
return super(ir_property, self).create(cr, uid, self._update_values(cr, uid, None, values), context=context)
|
||||
|
||||
def get_by_id(self, cr, uid, record_ids, context=None):
|
||||
if isinstance(record_ids, (int, long)):
|
||||
record_ids = [record_ids]
|
||||
|
||||
if not record_ids:
|
||||
return False
|
||||
|
||||
record = self.browse(cr, uid, record_ids[0], context=context)
|
||||
|
||||
def get_by_record(self, cr, uid, record, context=None):
|
||||
if record.type in ('char', 'text'):
|
||||
return record.value_text
|
||||
elif record.type == 'float':
|
||||
@@ -162,7 +153,6 @@ class ir_property(osv.osv):
|
||||
if not record.value_datetime:
|
||||
return False
|
||||
return time.strftime('%Y-%m-%d', time.strptime(record.value_datetime, '%Y-%m-%d %H:%M:%S'))
|
||||
|
||||
return False
|
||||
|
||||
def get(self, cr, uid, name, model, res_id=False, context={}):
|
||||
@@ -170,7 +160,9 @@ class ir_property(osv.osv):
|
||||
if domain is not None:
|
||||
domain = [('res_id', '=', res_id)] + domain
|
||||
nid = self.search(cr, uid, domain, context=context)
|
||||
return self.get_by_id(cr, uid, nid, context=context)
|
||||
if not nid: return False
|
||||
record = self.browse(cr, uid, nid[0], context=context)
|
||||
return self.get_by_record(cr, uid, record, context=context)
|
||||
return False
|
||||
|
||||
def _get_domain_default(self, cr, uid, prop_name, model, context=None):
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<field name="model">res.request</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Requests">
|
||||
<tree string="Requests" colors="blue:state in ('draft');gray:state in ('closed')">
|
||||
<field name="create_date"/>
|
||||
<field name="act_from"/>
|
||||
<field name="act_to"/>
|
||||
@@ -87,7 +87,7 @@
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
</record>
|
||||
<record id="res_request_calendar_view" model="ir.ui.view">
|
||||
<field name="name">res.request.calendar</field>
|
||||
<field name="model">res.request</field>
|
||||
@@ -109,7 +109,7 @@
|
||||
<field name="context">{"search_default_act_to":uid}</field>
|
||||
</record>
|
||||
<menuitem action="res_request-act" id="menu_res_request_act" parent="base.next_id_4" sequence="1"/>
|
||||
|
||||
|
||||
<record id="res_request_link-view" model="ir.ui.view">
|
||||
<field name="name">res.request.link.form</field>
|
||||
<field name="model">res.request.link</field>
|
||||
@@ -144,7 +144,7 @@
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="res_request_link-act" model="ir.actions.act_window">
|
||||
<field name="name">References Link</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
@@ -154,8 +154,8 @@
|
||||
<field name="search_view_id" ref="res_request_link_search_view"/>
|
||||
</record>
|
||||
<menuitem action="res_request_link-act" id="menu_res_request_link_act" parent="base.next_id_4" sequence="4"/>
|
||||
|
||||
|
||||
|
||||
|
||||
<record id="res_request_history_tree-view" model="ir.ui.view">
|
||||
<field name="name">res.request.history.tree</field>
|
||||
<field name="model">res.request.history</field>
|
||||
@@ -169,7 +169,7 @@
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="res_request_history_form-view" model="ir.ui.view">
|
||||
<field name="name">res.request.history.form</field>
|
||||
<field name="model">res.request.history</field>
|
||||
|
||||
@@ -411,6 +411,7 @@
|
||||
<rng:ref name="overload"/>
|
||||
<rng:ref name="access_rights"/>
|
||||
<rng:optional><rng:attribute name="editable"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="domain_filter"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="attrs"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="string"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="completion"/></rng:optional>
|
||||
|
||||
+72
-29
@@ -35,6 +35,7 @@ import time
|
||||
import release
|
||||
from pprint import pformat
|
||||
import warnings
|
||||
import heapq
|
||||
|
||||
class Service(object):
|
||||
""" Base class for *Local* services
|
||||
@@ -132,6 +133,7 @@ class ExportService(object):
|
||||
LOG_NOTSET = 'notset'
|
||||
LOG_DEBUG_SQL = 'debug_sql'
|
||||
LOG_DEBUG_RPC = 'debug_rpc'
|
||||
LOG_DEBUG_RPC_ANSWER = 'debug_rpc_answer'
|
||||
LOG_DEBUG = 'debug'
|
||||
LOG_TEST = 'test'
|
||||
LOG_INFO = 'info'
|
||||
@@ -139,9 +141,11 @@ LOG_WARNING = 'warn'
|
||||
LOG_ERROR = 'error'
|
||||
LOG_CRITICAL = 'critical'
|
||||
|
||||
logging.DEBUG_RPC_ANSWER = logging.DEBUG - 4
|
||||
logging.addLevelName(logging.DEBUG_RPC_ANSWER, 'DEBUG_RPC_ANSWER')
|
||||
logging.DEBUG_RPC = logging.DEBUG - 2
|
||||
logging.addLevelName(logging.DEBUG_RPC, 'DEBUG_RPC')
|
||||
logging.DEBUG_SQL = logging.DEBUG_RPC - 2
|
||||
logging.DEBUG_SQL = logging.DEBUG_RPC - 3
|
||||
logging.addLevelName(logging.DEBUG_SQL, 'DEBUG_SQL')
|
||||
|
||||
logging.TEST = logging.INFO - 5
|
||||
@@ -157,6 +161,7 @@ COLOR_PATTERN = "%s%s%%s%s" % (COLOR_SEQ, COLOR_SEQ, RESET_SEQ)
|
||||
LEVEL_COLOR_MAPPING = {
|
||||
logging.DEBUG_SQL: (WHITE, MAGENTA),
|
||||
logging.DEBUG_RPC: (BLUE, WHITE),
|
||||
logging.DEBUG_RPC_ANSWER: (BLUE, WHITE),
|
||||
logging.DEBUG: (BLUE, DEFAULT),
|
||||
logging.INFO: (GREEN, DEFAULT),
|
||||
logging.TEST: (WHITE, BLUE),
|
||||
@@ -288,36 +293,76 @@ import tools
|
||||
init_logger()
|
||||
|
||||
class Agent(object):
|
||||
_timers = {}
|
||||
"""Singleton that keeps track of cancellable tasks to run at a given
|
||||
timestamp.
|
||||
The tasks are caracterised by:
|
||||
* a timestamp
|
||||
* the database on which the task run
|
||||
* the function to call
|
||||
* the arguments and keyword arguments to pass to the function
|
||||
|
||||
Implementation details:
|
||||
Tasks are stored as list, allowing the cancellation by setting
|
||||
the timestamp to 0.
|
||||
A heapq is used to store tasks, so we don't need to sort
|
||||
tasks ourself.
|
||||
"""
|
||||
__tasks = []
|
||||
__tasks_by_db = {}
|
||||
_logger = Logger()
|
||||
|
||||
__logger = logging.getLogger('timer')
|
||||
|
||||
def setAlarm(self, fn, dt, db_name, *args, **kwargs):
|
||||
wait = dt - time.time()
|
||||
if wait > 0:
|
||||
self.__logger.debug("Job scheduled in %.3g seconds for %s.%s" % (wait, fn.im_class.__name__, fn.func_name))
|
||||
timer = threading.Timer(wait, fn, args, kwargs)
|
||||
timer.start()
|
||||
self._timers.setdefault(db_name, []).append(timer)
|
||||
|
||||
for db in self._timers:
|
||||
for timer in self._timers[db]:
|
||||
if not timer.isAlive():
|
||||
self._timers[db].remove(timer)
|
||||
@classmethod
|
||||
def setAlarm(cls, function, timestamp, db_name, *args, **kwargs):
|
||||
task = [timestamp, db_name, function, args, kwargs]
|
||||
heapq.heappush(cls.__tasks, task)
|
||||
cls.__tasks_by_db.setdefault(db_name, []).append(task)
|
||||
|
||||
@classmethod
|
||||
def cancel(cls, db_name):
|
||||
"""Cancel all timers for a given database. If None passed, all timers are cancelled"""
|
||||
for db in cls._timers:
|
||||
if db_name is None or db == db_name:
|
||||
for timer in cls._timers[db]:
|
||||
timer.cancel()
|
||||
"""Cancel all tasks for a given database. If None is passed, all tasks are cancelled"""
|
||||
if db_name is None:
|
||||
cls.__tasks, cls.__tasks_by_db = [], {}
|
||||
else:
|
||||
if db_name in cls.__tasks_by_db:
|
||||
for task in cls.__tasks_by_db[db_name]:
|
||||
task[0] = 0
|
||||
|
||||
@classmethod
|
||||
def quit(cls):
|
||||
cls.cancel(None)
|
||||
|
||||
@classmethod
|
||||
def runner(cls):
|
||||
"""Neverending function (intended to be ran in a dedicated thread) that
|
||||
checks every 60 seconds tasks to run.
|
||||
"""
|
||||
current_thread = threading.currentThread()
|
||||
while True:
|
||||
while cls.__tasks and cls.__tasks[0][0] < time.time():
|
||||
task = heapq.heappop(cls.__tasks)
|
||||
timestamp, dbname, function, args, kwargs = task
|
||||
cls.__tasks_by_db[dbname].remove(task)
|
||||
if not timestamp:
|
||||
# null timestamp -> cancelled task
|
||||
continue
|
||||
current_thread.dbname = dbname # hack hack
|
||||
cls._logger.notifyChannel('timers', LOG_DEBUG, "Run %s.%s(*%r, **%r)" % (function.im_class.__name__, function.func_name, args, kwargs))
|
||||
delattr(current_thread, 'dbname')
|
||||
task_thread = threading.Thread(target=function, name='netsvc.Agent.task', args=args, kwargs=kwargs)
|
||||
# force non-daemon task threads (the runner thread must be daemon, and this property is inherited by default)
|
||||
task_thread.daemon = False
|
||||
task_thread.start()
|
||||
time.sleep(1)
|
||||
time.sleep(60)
|
||||
|
||||
agent_runner = threading.Thread(target=Agent.runner, name="netsvc.Agent.runner")
|
||||
# the agent runner is a typical daemon thread, that will never quit and must be
|
||||
# terminated when the main process exits - with no consequence (the processing
|
||||
# threads it spawns are not marked daemon)
|
||||
agent_runner.daemon = True
|
||||
agent_runner.start()
|
||||
|
||||
|
||||
import traceback
|
||||
|
||||
class Server:
|
||||
@@ -420,11 +465,11 @@ class OpenERPDispatcherException(Exception):
|
||||
self.traceback = traceback
|
||||
|
||||
class OpenERPDispatcher:
|
||||
def log(self, title, msg):
|
||||
def log(self, title, msg, channel=logging.DEBUG_RPC, depth=2):
|
||||
logger = logging.getLogger(title)
|
||||
if logger.isEnabledFor(logging.DEBUG_RPC):
|
||||
for line in pformat(msg).split('\n'):
|
||||
logger.log(logging.DEBUG_RPC, line)
|
||||
if logger.isEnabledFor(channel):
|
||||
for line in pformat(msg, depth=depth).split('\n'):
|
||||
logger.log(channel, line)
|
||||
|
||||
def dispatch(self, service_name, method, params):
|
||||
try:
|
||||
@@ -433,10 +478,8 @@ class OpenERPDispatcher:
|
||||
self.log('params', params)
|
||||
auth = getattr(self, 'auth_provider', None)
|
||||
result = ExportService.getService(service_name).dispatch(method, auth, params)
|
||||
self.log('result', result)
|
||||
# We shouldn't marshall None,
|
||||
if result == None:
|
||||
result = False
|
||||
logger = logging.getLogger('result')
|
||||
self.log('result', result, channel=logging.DEBUG_RPC_ANSWER, depth=(logger.isEnabledFor(logging.DEBUG_SQL) and 1 or None))
|
||||
return result
|
||||
except Exception, e:
|
||||
self.log('exception', tools.exception_to_unicode(e))
|
||||
|
||||
+65
-32
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
@@ -16,7 +16,7 @@
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
@@ -27,16 +27,19 @@ OpenERP is an ERP+CRM program for small and medium businesses.
|
||||
The whole source code is distributed under the terms of the
|
||||
GNU Public Licence.
|
||||
|
||||
(c) 2003-TODAY, Fabien Pinckaers - Tiny sprl
|
||||
(c) 2003-TODAY, Fabien Pinckaers - OpenERP s.a.
|
||||
"""
|
||||
|
||||
#----------------------------------------------------------
|
||||
# python imports
|
||||
#----------------------------------------------------------
|
||||
import sys
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import pwd
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
import release
|
||||
__author__ = release.author
|
||||
@@ -52,23 +55,22 @@ if pwd.getpwuid(os.getuid())[0] == 'root' :
|
||||
# get logger
|
||||
#----------------------------------------------------------
|
||||
import netsvc
|
||||
logger = netsvc.Logger()
|
||||
logger = logging.getLogger('server')
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# import the tools module so that the commandline parameters are parsed
|
||||
#-----------------------------------------------------------------------
|
||||
import tools
|
||||
|
||||
logger.notifyChannel("server", netsvc.LOG_INFO, "version - %s" % release.version )
|
||||
logger.info("OpenERP version - %s", release.version)
|
||||
for name, value in [('addons_path', tools.config['addons_path']),
|
||||
('database hostname', tools.config['db_host'] or 'localhost'),
|
||||
('database port', tools.config['db_port'] or '5432'),
|
||||
('database user', tools.config['db_user'])]:
|
||||
logger.notifyChannel("server", netsvc.LOG_INFO, "%s - %s" % ( name, value ))
|
||||
logger.info("%s - %s", name, value)
|
||||
|
||||
# Don't allow if the connection to PostgreSQL done by postgres user
|
||||
if tools.config['db_user'] == 'postgres':
|
||||
logger.notifyChannel("server", netsvc.LOG_ERROR, "%s" % ("Attempted to connect database with postgres user. This is a security flaws, aborting."))
|
||||
logger.error("Connecting to the database as 'postgres' user is forbidden, as it present major security issues. Shutting down.")
|
||||
sys.exit(1)
|
||||
|
||||
import time
|
||||
@@ -76,7 +78,7 @@ import time
|
||||
#----------------------------------------------------------
|
||||
# init net service
|
||||
#----------------------------------------------------------
|
||||
logger.notifyChannel("objects", netsvc.LOG_INFO, 'initialising distributed objects services')
|
||||
logger.info('initialising distributed objects services')
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# connect to the database and initialize it with base if needed
|
||||
@@ -117,8 +119,7 @@ if tools.config['db_name']:
|
||||
for dbname in tools.config['db_name'].split(','):
|
||||
db,pool = pooler.get_db_and_pool(dbname, update_module=tools.config['init'] or tools.config['update'], pooljobs=False)
|
||||
if tools.config["test_file"]:
|
||||
logger.notifyChannel("init", netsvc.LOG_INFO,
|
||||
'loading test file %s' % (tools.config["test_file"],))
|
||||
logger.info('loading test file %s', tools.config["test_file"])
|
||||
cr = db.cursor()
|
||||
tools.convert_yaml_import(cr, 'base', file(tools.config["test_file"]), {}, 'test', True)
|
||||
cr.rollback()
|
||||
@@ -134,16 +135,14 @@ if tools.config["translate_out"]:
|
||||
msg = "language %s" % (tools.config["language"],)
|
||||
else:
|
||||
msg = "new language"
|
||||
logger.notifyChannel("init", netsvc.LOG_INFO,
|
||||
'writing translation file for %s to %s' % (msg,
|
||||
tools.config["translate_out"]))
|
||||
logger.info('writing translation file for %s to %s', msg, tools.config["translate_out"])
|
||||
|
||||
fileformat = os.path.splitext(tools.config["translate_out"])[-1][1:].lower()
|
||||
buf = file(tools.config["translate_out"], "w")
|
||||
tools.trans_export(tools.config["language"], tools.config["translate_modules"], buf, fileformat)
|
||||
buf.close()
|
||||
|
||||
logger.notifyChannel("init", netsvc.LOG_INFO, 'translation file written successfully')
|
||||
logger.info('translation file written successfully')
|
||||
sys.exit(0)
|
||||
|
||||
if tools.config["translate_in"]:
|
||||
@@ -164,30 +163,64 @@ if tools.config["stop_after_init"]:
|
||||
#----------------------------------------------------------
|
||||
|
||||
LST_SIGNALS = ['SIGINT', 'SIGTERM']
|
||||
if os.name == 'posix':
|
||||
LST_SIGNALS.extend(['SIGUSR1','SIGQUIT'])
|
||||
|
||||
|
||||
SIGNALS = dict(
|
||||
[(getattr(signal, sign), sign) for sign in LST_SIGNALS]
|
||||
)
|
||||
|
||||
def handler(signum, _):
|
||||
netsvc.quit_signals_received = 0
|
||||
|
||||
def handler(signum, frame):
|
||||
"""
|
||||
:param signum: the signal number
|
||||
:param _:
|
||||
:param frame: the interrupted stack frame or None
|
||||
"""
|
||||
netsvc.quit_signals_received += 1
|
||||
if netsvc.quit_signals_received > 1:
|
||||
sys.stderr.write("Forced shutdown.\n")
|
||||
os._exit(0)
|
||||
|
||||
def dumpstacks(signum, frame):
|
||||
# code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
|
||||
|
||||
id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
|
||||
code = []
|
||||
for threadId, stack in sys._current_frames().items():
|
||||
code.append("\n# Thread: %s(%d)" % (id2name[threadId], threadId))
|
||||
for filename, lineno, name, line in traceback.extract_stack(stack):
|
||||
code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
|
||||
if line:
|
||||
code.append(" %s" % (line.strip()))
|
||||
|
||||
logging.getLogger('dumpstacks').info("\n".join(code))
|
||||
|
||||
for signum in SIGNALS:
|
||||
signal.signal(signum, handler)
|
||||
|
||||
if os.name == 'posix':
|
||||
signal.signal(signal.SIGQUIT, dumpstacks)
|
||||
|
||||
def quit():
|
||||
netsvc.Agent.quit()
|
||||
netsvc.Server.quitAll()
|
||||
if tools.config['pidfile']:
|
||||
os.unlink(tools.config['pidfile'])
|
||||
logger.notifyChannel('shutdown', netsvc.LOG_INFO,
|
||||
"Shutdown Server! - %s" % ( SIGNALS[signum], ))
|
||||
logger.shutdown()
|
||||
sys.exit(0)
|
||||
logger = logging.getLogger('shutdown')
|
||||
logger.info("Initiating OpenERP Server shutdown")
|
||||
logger.info("Hit CTRL-C again or send a second signal to immediately terminate the server...")
|
||||
logging.shutdown()
|
||||
|
||||
for signum in SIGNALS:
|
||||
signal.signal(signum, handler)
|
||||
# manually join() all threads before calling sys.exit() to allow a second signal
|
||||
# to trigger _force_quit() in case some non-daemon threads won't exit cleanly.
|
||||
# threading.Thread.join() should not mask signals (at least in python 2.5)
|
||||
for thread in threading.enumerate():
|
||||
if thread != threading.currentThread() and not thread.daemon:
|
||||
while thread.isAlive():
|
||||
# need a busyloop here as thread.join() masks signals
|
||||
# and would present the forced shutdown
|
||||
thread.join(0.05)
|
||||
time.sleep(0.05)
|
||||
sys.exit(0)
|
||||
|
||||
if tools.config['pidfile']:
|
||||
fd = open(tools.config['pidfile'], 'w')
|
||||
@@ -195,13 +228,13 @@ if tools.config['pidfile']:
|
||||
fd.write(pidtext)
|
||||
fd.close()
|
||||
|
||||
|
||||
netsvc.Server.startAll()
|
||||
|
||||
logger.notifyChannel("web-services", netsvc.LOG_INFO,
|
||||
'the server is running, waiting for connections...')
|
||||
logger.info('OpenERP server is running, waiting for connections...')
|
||||
|
||||
while True:
|
||||
while netsvc.quit_signals_received == 0:
|
||||
time.sleep(60)
|
||||
|
||||
quit()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
+58
-52
@@ -303,10 +303,9 @@ class many2one(_column):
|
||||
return result
|
||||
|
||||
def get(self, cr, obj, ids, name, user=None, context=None, values=None):
|
||||
if not context:
|
||||
context = {}
|
||||
if not values:
|
||||
values = {}
|
||||
context = context or {}
|
||||
values = values or {}
|
||||
|
||||
res = {}
|
||||
for r in values:
|
||||
res[r['id']] = r[name]
|
||||
@@ -315,21 +314,14 @@ class many2one(_column):
|
||||
obj = obj.pool.get(self._obj)
|
||||
|
||||
# build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
|
||||
from orm import except_orm
|
||||
names = {}
|
||||
for record in list(set(filter(None, res.values()))):
|
||||
try:
|
||||
record_name = dict(obj.name_get(cr, user, [record], context))
|
||||
except except_orm:
|
||||
record_name = {}
|
||||
record_name[record] = '// Access Denied //'
|
||||
names.update(record_name)
|
||||
|
||||
for r in res.keys():
|
||||
if res[r] and res[r] in names:
|
||||
res[r] = (res[r], names[res[r]])
|
||||
# we use uid=1 because the visibility of a many2one field value (just id and name)
|
||||
# must be the access right of the parent form and not the linked object itself.
|
||||
records = dict(obj.name_get(cr, 1, list(set(filter(None, res.values()))), context=context))
|
||||
for id in res:
|
||||
if res[id] in records:
|
||||
res[id] = (res[id], records[res[id]])
|
||||
else:
|
||||
res[r] = False
|
||||
res[id] = False
|
||||
return res
|
||||
|
||||
def set(self, cr, obj_src, id, field, values, user=None, context=None):
|
||||
@@ -785,7 +777,7 @@ class related(function):
|
||||
if not ids: return {}
|
||||
relation = obj._name
|
||||
if self._type in ('one2many', 'many2many'):
|
||||
res = {}.fromkeys(ids, [])
|
||||
res = dict([(i, []) for i in ids])
|
||||
else:
|
||||
res = {}.fromkeys(ids, False)
|
||||
|
||||
@@ -881,38 +873,44 @@ class serialized(_column):
|
||||
super(serialized, self).__init__(string=string, **args)
|
||||
|
||||
|
||||
# TODO: review completly this class for speed improvement
|
||||
class property(function):
|
||||
|
||||
def _get_default(self, obj, cr, uid, prop_name, context=None):
|
||||
from orm import browse_record
|
||||
prop = obj.pool.get('ir.property')
|
||||
domain = prop._get_domain_default(cr, uid, prop_name, obj._name, context)
|
||||
ids = prop.search(cr, uid, domain, order='company_id', context=context)
|
||||
if not ids:
|
||||
return False
|
||||
return self._get_defaults(obj, cr, uid, [prop_name], context=None)[0][prop_name]
|
||||
|
||||
default_value = prop.get_by_id(cr, uid, ids, context=context)
|
||||
if isinstance(default_value, browse_record):
|
||||
return default_value.id
|
||||
return default_value or False
|
||||
def _get_defaults(self, obj, cr, uid, prop_name, context=None):
|
||||
prop = obj.pool.get('ir.property')
|
||||
domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name), ('res_id','=',False)]
|
||||
ids = prop.search(cr, uid, domain, context=context)
|
||||
replaces = {}
|
||||
default_value = {}.fromkeys(prop_name, False)
|
||||
for prop_rec in prop.browse(cr, uid, ids, context=context):
|
||||
if default_value.get(prop_rec.fields_id.name, False):
|
||||
continue
|
||||
value = prop.get_by_record(cr, uid, prop_rec, context=context) or False
|
||||
default_value[prop_rec.fields_id.name] = value
|
||||
if value and (prop_rec.type == 'many2one'):
|
||||
replaces.setdefault(value._name, {})
|
||||
replaces[value._name][value.id] = True
|
||||
return default_value, replaces
|
||||
|
||||
def _get_by_id(self, obj, cr, uid, prop_name, ids, context=None):
|
||||
prop = obj.pool.get('ir.property')
|
||||
vids = [obj._name + ',' + str(oid) for oid in ids]
|
||||
|
||||
domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
|
||||
if domain is not None:
|
||||
domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name)]
|
||||
#domain = prop._get_domain(cr, uid, prop_name, obj._name, context)
|
||||
if vids:
|
||||
domain = [('res_id', 'in', vids)] + domain
|
||||
return prop.search(cr, uid, domain, context=context)
|
||||
else:
|
||||
return []
|
||||
|
||||
return prop.search(cr, uid, domain, context=context)
|
||||
|
||||
# TODO: to rewrite more clean
|
||||
def _fnct_write(self, obj, cr, uid, id, prop_name, id_val, obj_dest, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
nids = self._get_by_id(obj, cr, uid, prop_name, [id], context)
|
||||
nids = self._get_by_id(obj, cr, uid, [prop_name], [id], context)
|
||||
if nids:
|
||||
cr.execute('DELETE FROM ir_property WHERE id IN %s', (tuple(nids),))
|
||||
|
||||
@@ -933,29 +931,37 @@ class property(function):
|
||||
'company_id': cid,
|
||||
'fields_id': def_id,
|
||||
'type': self._type,
|
||||
}, context=context)
|
||||
}, context=context)
|
||||
return False
|
||||
|
||||
|
||||
def _fnct_read(self, obj, cr, uid, ids, prop_name, obj_dest, context=None):
|
||||
from orm import browse_record
|
||||
properties = obj.pool.get('ir.property')
|
||||
|
||||
default_val = self._get_default(obj, cr, uid, prop_name, context)
|
||||
|
||||
nids = self._get_by_id(obj, cr, uid, prop_name, ids, context)
|
||||
domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name)]
|
||||
domain += [('res_id','in', [obj._name + ',' + str(oid) for oid in ids])]
|
||||
nids = properties.search(cr, uid, domain, context=context)
|
||||
default_val,replaces = self._get_defaults(obj, cr, uid, prop_name, context)
|
||||
|
||||
res = {}
|
||||
for id in ids:
|
||||
res[id] = default_val
|
||||
for prop in properties.browse(cr, uid, nids):
|
||||
value = prop.get_by_id(context=context)
|
||||
if isinstance(value, browse_record):
|
||||
if not value.exists():
|
||||
cr.execute('DELETE FROM ir_property WHERE id=%s', (prop.id,))
|
||||
continue
|
||||
value = value.id
|
||||
res[prop.res_id.id] = value or False
|
||||
res[id] = default_val.copy()
|
||||
|
||||
brs = properties.browse(cr, uid, nids, context=context)
|
||||
for prop in brs:
|
||||
value = properties.get_by_record(cr, uid, prop, context=context)
|
||||
res[prop.res_id.id][prop.fields_id.name] = value or False
|
||||
if value and (prop.type == 'many2one'):
|
||||
replaces.setdefault(value._name, {})
|
||||
replaces[value._name][value.id] = True
|
||||
|
||||
for rep in replaces:
|
||||
replaces[rep] = dict(obj.pool.get(rep).name_get(cr, uid, replaces[rep].keys(), context=context))
|
||||
|
||||
for prop in prop_name:
|
||||
for id in ids:
|
||||
if res[id][prop] and hasattr(res[id][prop], '_name'):
|
||||
res[id][prop] = (res[id][prop].id , replaces[res[id][prop]._name].get(res[id][prop].id, False))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@@ -972,7 +978,7 @@ class property(function):
|
||||
# TODO remove obj_prop parameter (use many2one type)
|
||||
self.field_id = {}
|
||||
function.__init__(self, self._fnct_read, False, self._fnct_write,
|
||||
obj_prop, **args)
|
||||
obj_prop, multi='properties', **args)
|
||||
|
||||
def restart(self):
|
||||
self.field_id = {}
|
||||
|
||||
+127
-55
@@ -1541,6 +1541,14 @@ class orm_template(object):
|
||||
else:
|
||||
for n in node.getiterator(node2.tag):
|
||||
res = True
|
||||
if node2.tag == 'field':
|
||||
# only compare field names, a field can be only once in a given view
|
||||
# at a given level (and for multilevel expressions, we should use xpath
|
||||
# inheritance spec anyway)
|
||||
if node2.get('name') == n.get('name'):
|
||||
return n
|
||||
else:
|
||||
continue
|
||||
for attr in node2.attrib:
|
||||
if attr == 'position':
|
||||
continue
|
||||
@@ -1809,10 +1817,10 @@ class orm_template(object):
|
||||
|
||||
def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
|
||||
"""
|
||||
Private implementation of search() method, allowing specifying the uid to use for the access right check.
|
||||
Private implementation of search() method, allowing specifying the uid to use for the access right check.
|
||||
This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
|
||||
by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
|
||||
|
||||
|
||||
:param access_rights_uid: optional user ID to use when checking access rights
|
||||
(not for ir.rules, this is only for ir.model.access)
|
||||
"""
|
||||
@@ -1841,7 +1849,7 @@ class orm_template(object):
|
||||
|
||||
def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
|
||||
"""
|
||||
Search for records and their display names according to a search domain.
|
||||
Search for records and their display names according to a search domain.
|
||||
|
||||
:param cr: database cursor
|
||||
:param user: current user id
|
||||
@@ -2207,7 +2215,7 @@ class orm(orm_template):
|
||||
previous_tables = list(tables)
|
||||
if self._apply_ir_rules(cr, uid, where_clause, where_clause_params, tables, 'read', model_name=inherited_model, context=context):
|
||||
# if some rules were applied, need to add the missing JOIN for them to make sense, passing the previous
|
||||
# list of table in case the inherited table was not in the list before (as that means the corresponding
|
||||
# list of table in case the inherited table was not in the list before (as that means the corresponding
|
||||
# JOIN(s) was(were) not present)
|
||||
self._inherits_join_add(inherited_model, previous_tables, where_clause)
|
||||
tables = list(set(tables).union(set(previous_tables)))
|
||||
@@ -2215,7 +2223,7 @@ class orm(orm_template):
|
||||
# Take care of adding join(s) if groupby is an '_inherits'ed field
|
||||
groupby_list = groupby
|
||||
if groupby:
|
||||
if groupby and isinstance(groupby, list):
|
||||
if isinstance(groupby, list):
|
||||
groupby = groupby[0]
|
||||
tables, where_clause, qfield = self._inherits_join_calc(groupby, tables, where_clause)
|
||||
|
||||
@@ -2234,14 +2242,14 @@ class orm(orm_template):
|
||||
flist = ''
|
||||
group_by = groupby
|
||||
if groupby:
|
||||
if fget.get(groupby, False):
|
||||
if fget.get(groupby):
|
||||
if fget[groupby]['type'] in ('date', 'datetime'):
|
||||
flist = "to_char(%s,'yyyy-mm') as %s " % (groupby, groupby)
|
||||
groupby = "to_char(%s,'yyyy-mm')" % (groupby)
|
||||
else:
|
||||
flist = groupby
|
||||
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'),
|
||||
_('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(groupby,))
|
||||
|
||||
@@ -2256,10 +2264,7 @@ class orm(orm_template):
|
||||
flist += ','
|
||||
flist += operator+'('+f+') as '+f
|
||||
|
||||
if groupby:
|
||||
gb = ' group by '+groupby
|
||||
else:
|
||||
gb = ''
|
||||
gb = groupby and (' group by '+groupby) or ''
|
||||
cr.execute('select min(%s.id) as id,' % self._table + flist + ' from ' + ','.join(tables) + where_clause + gb + limit_str + offset_str, where_clause_params)
|
||||
alldata = {}
|
||||
groupby = group_by
|
||||
@@ -2268,7 +2273,18 @@ class orm(orm_template):
|
||||
if val == None: r[fld] = False
|
||||
alldata[r['id']] = r
|
||||
del r['id']
|
||||
data = self.read(cr, uid, alldata.keys(), groupby and [groupby] or ['id'], context=context)
|
||||
if groupby and fget[groupby]['type'] == 'many2one':
|
||||
data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=groupby, context=context)
|
||||
# the IDS of the records that has groupby field value = False or ''
|
||||
# should be added too
|
||||
data_ids += filter(lambda x:x not in data_ids, alldata.keys())
|
||||
data = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context)
|
||||
# restore order of the search as read() uses the default _order (this is only for groups, so the size of data_read shoud be small):
|
||||
data.sort(lambda x,y: cmp(data_ids.index(x['id']), data_ids.index(y['id'])))
|
||||
else:
|
||||
data = self.read(cr, uid, alldata.keys(), groupby and [groupby] or ['id'], context=context)
|
||||
if groupby:
|
||||
data.sort(lambda x,y:cmp(x[groupby],y[groupby]))
|
||||
for d in data:
|
||||
if groupby:
|
||||
d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
|
||||
@@ -2314,9 +2330,9 @@ class orm(orm_template):
|
||||
:param tables: list of table._table names enclosed in double quotes as returned
|
||||
by _where_calc()
|
||||
:param where_clause: current list of WHERE clause params
|
||||
:return: (table, where_clause, qualified_field) where ``table`` and ``where_clause`` are the updated
|
||||
:return: (tables, where_clause, qualified_field) where ``tables`` and ``where_clause`` are the updated
|
||||
versions of the parameters, and ``qualified_field`` is the qualified name of ``field``
|
||||
in the form ``table.field``, to be referenced in queries.
|
||||
in the form ``table.field``, to be referenced in queries.
|
||||
"""
|
||||
current_table = self
|
||||
while field in current_table._inherit_fields and not field in current_table._columns:
|
||||
@@ -2988,19 +3004,20 @@ class orm(orm_template):
|
||||
column = self._inherit_fields[key][2]
|
||||
else:
|
||||
continue
|
||||
if v and column._type == 'reference':
|
||||
model_name, ref_id = v.split(',', 1)
|
||||
model = self.pool.get(model_name)
|
||||
if not model:
|
||||
reset = True
|
||||
else:
|
||||
cr.execute('SELECT count(1) FROM "%s" WHERE id=%%s' % (model._table,), (ref_id,))
|
||||
reset = not cr.fetchone()[0]
|
||||
if reset:
|
||||
if column._classic_write:
|
||||
query = 'UPDATE "%s" SET "%s"=NULL WHERE id=%%s' % (self._table, key)
|
||||
cr.execute(query, (r['id'],))
|
||||
r[key] = False
|
||||
# TODO: removed this, it's too slow
|
||||
# if v and column._type == 'reference':
|
||||
# model_name, ref_id = v.split(',', 1)
|
||||
# model = self.pool.get(model_name)
|
||||
# if not model:
|
||||
# reset = True
|
||||
# else:
|
||||
# cr.execute('SELECT count(1) FROM "%s" WHERE id=%%s' % (model._table,), (ref_id,))
|
||||
# reset = not cr.fetchone()[0]
|
||||
# if reset:
|
||||
# if column._classic_write:
|
||||
# query = 'UPDATE "%s" SET "%s"=NULL WHERE id=%%s' % (self._table, key)
|
||||
# cr.execute(query, (r['id'],))
|
||||
# r[key] = False
|
||||
|
||||
if isinstance(ids, (int, long, dict)):
|
||||
return result and result[0] or False
|
||||
@@ -3888,13 +3905,13 @@ class orm(orm_template):
|
||||
:param args: the domain to compute
|
||||
:type args: list
|
||||
:param active_test: whether the default filtering of records with ``active``
|
||||
field set to ``False`` should be applied.
|
||||
field set to ``False`` should be applied.
|
||||
:return: tuple with 3 elements: (where_clause, where_clause_params, tables) where
|
||||
``where_clause`` contains a list of where clause elements (to be joined with 'AND'),
|
||||
``where_clause_params`` is a list of parameters to be passed to the db layer
|
||||
for the where_clause expansion, and ``tables`` is the list of double-quoted
|
||||
table names that need to be included in the FROM clause.
|
||||
:rtype: tuple
|
||||
table names that need to be included in the FROM clause.
|
||||
:rtype: tuple
|
||||
"""
|
||||
if not context:
|
||||
context = {}
|
||||
@@ -3926,7 +3943,7 @@ class orm(orm_template):
|
||||
|
||||
def _check_qorder(self, word):
|
||||
if not regex_order.match(word):
|
||||
raise except_orm(_('AccessError'), _('Bad query.'))
|
||||
raise except_orm(_('AccessError'), _('Invalid "order" specified. A valid "order" specification is a comma-separated list of valid field names (optionally followed by asc/desc for the direction)'))
|
||||
return True
|
||||
|
||||
def _apply_ir_rules(self, cr, uid, where_clause, where_clause_params, tables, mode='read', model_name=None, context=None):
|
||||
@@ -3939,7 +3956,7 @@ class orm(orm_template):
|
||||
in ``where_clause``
|
||||
:param model_name: optional name of the model whose ir.rules should be applied (default:``self._name``)
|
||||
This could be useful for inheritance for example, but there is no provision to include
|
||||
the appropriate JOIN for linking the current model to the one referenced in model_name.
|
||||
the appropriate JOIN for linking the current model to the one referenced in model_name.
|
||||
:return: True if additional clauses where applied.
|
||||
"""
|
||||
added_clause, added_params, added_tables = self.pool.get('ir.rule').domain_get(cr, uid, model_name or self._name, mode, context=context)
|
||||
@@ -3952,13 +3969,86 @@ class orm(orm_template):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _generate_m2o_order_by(self, order_field, tables, where_clause):
|
||||
"""
|
||||
Add possibly missing JOIN and generate the ORDER BY clause for m2o fields,
|
||||
either native m2o fields or function/related fields that are stored, including
|
||||
intermediate JOINs for inheritance if required.
|
||||
"""
|
||||
if order_field not in self._columns and order_field in self._inherit_fields:
|
||||
# also add missing joins for reaching the table containing the m2o field
|
||||
tables, where_clause, qualified_field = self._inherits_join_calc(order_field, tables, where_clause)
|
||||
order_field_column = self._inherit_fields[order_field][2]
|
||||
else:
|
||||
qualified_field = '"%s"."%s"' % (self._table, order_field)
|
||||
order_field_column = self._columns[order_field]
|
||||
|
||||
assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
|
||||
assert order_field_column._classic_write or getattr(order_field_column, 'store', False), "Many2one function/related fields must be stored to be used as ordering fields"
|
||||
|
||||
# figure out the applicable order_by for the m2o
|
||||
dest_model = self.pool.get(order_field_column._obj)
|
||||
m2o_order = dest_model._order
|
||||
if not regex_order.match(m2o_order):
|
||||
# _order is complex, can't use it here, so we default to _rec_name
|
||||
m2o_order = dest_model._rec_name
|
||||
else:
|
||||
# extract the first field name, to be able to qualify it and add desc/asc
|
||||
m2o_order = m2o_order.split(",",1)[0].strip().split(" ",1)[0]
|
||||
|
||||
# the perhaps missing join:
|
||||
quoted_model_table = '"%s"' % dest_model._table
|
||||
if quoted_model_table not in tables:
|
||||
tables.append(quoted_model_table)
|
||||
where_clause.append('%s = %s.id' % (qualified_field, quoted_model_table))
|
||||
|
||||
return ('%s.%s' % (quoted_model_table, m2o_order), tables, where_clause)
|
||||
|
||||
|
||||
def _generate_order_by(self, order_spec, tables, where_clause):
|
||||
"""
|
||||
Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
|
||||
a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
|
||||
|
||||
:raise" except_orm in case order_spec is malformed
|
||||
"""
|
||||
order_by_clause = self._order
|
||||
if order_spec:
|
||||
order_by_elements = []
|
||||
self._check_qorder(order_spec)
|
||||
for order_part in order_spec.split(','):
|
||||
order_split = order_part.strip().split(' ')
|
||||
order_field = order_split[0].strip()
|
||||
order_direction = order_split[1].strip() if len(order_split) == 2 else ''
|
||||
if order_field in self._columns:
|
||||
order_column = self._columns[order_field]
|
||||
if order_column._classic_read:
|
||||
order_by_clause = '"%s"."%s"' % (self._table, order_field)
|
||||
elif order_column._type == 'many2one':
|
||||
order_by_clause, tables, where_clause = self._generate_m2o_order_by(order_field, tables, where_clause)
|
||||
else:
|
||||
continue # ignore non-readable or "non-joignable" fields
|
||||
elif order_field in self._inherit_fields:
|
||||
parent_obj = self.pool.get(self._inherit_fields[order_field][0])
|
||||
order_column = parent_obj._columns[order_field]
|
||||
if order_column._classic_read:
|
||||
tables, where_clause, order_by_clause = self._inherits_join_calc(order_field, tables, where_clause)
|
||||
elif order_column._type == 'many2one':
|
||||
order_by_clause, tables, where_clause = self._generate_m2o_order_by(order_field, tables, where_clause)
|
||||
else:
|
||||
continue # ignore non-readable or "non-joignable" fields
|
||||
order_by_elements.append("%s %s" % (order_by_clause, order_direction))
|
||||
order_by_clause = ",".join(order_by_elements)
|
||||
|
||||
return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
|
||||
|
||||
def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
|
||||
"""
|
||||
Private implementation of search() method, allowing specifying the uid to use for the access right check.
|
||||
Private implementation of search() method, allowing specifying the uid to use for the access right check.
|
||||
This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
|
||||
by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
|
||||
This is ok at the security level because this method is private and not callable through XML-RPC.
|
||||
|
||||
|
||||
:param access_rights_uid: optional user ID to use when checking access rights
|
||||
(not for ir.rules, this is only for ir.model.access)
|
||||
"""
|
||||
@@ -3982,35 +4072,17 @@ class orm(orm_template):
|
||||
tables = list(set(tables).union(set(previous_tables)))
|
||||
|
||||
where = where_clause
|
||||
|
||||
order_by = self._order
|
||||
if order:
|
||||
self._check_qorder(order)
|
||||
o = order.split(' ')[0]
|
||||
if (o in self._columns):
|
||||
# we can only do efficient sort if the fields is stored in database
|
||||
if getattr(self._columns[o], '_classic_read'):
|
||||
order_by = order
|
||||
elif (o in self._inherit_fields):
|
||||
parent_obj = self.pool.get(self._inherit_fields[o][0])
|
||||
if getattr(parent_obj._columns[o], '_classic_read'):
|
||||
# Allowing inherits'ed field for server side sorting, if they can be sorted by the dbms
|
||||
inherited_tables, inherit_join, order_by = self._inherits_join_calc(o, tables, where_clause)
|
||||
|
||||
order_by = self._generate_order_by(order, tables, where_clause)
|
||||
limit_str = limit and ' limit %d' % limit or ''
|
||||
offset_str = offset and ' offset %d' % offset or ''
|
||||
|
||||
if where:
|
||||
where_str = " WHERE %s" % " AND ".join(where)
|
||||
else:
|
||||
where_str = ""
|
||||
where_str = where and (" WHERE %s" % " AND ".join(where)) or ''
|
||||
|
||||
if count:
|
||||
cr.execute('select count(%s.id) from ' % self._table +
|
||||
','.join(tables) + where_str + limit_str + offset_str, where_clause_params)
|
||||
res = cr.fetchall()
|
||||
return res[0][0]
|
||||
cr.execute('select %s.id from ' % self._table + ','.join(tables) + where_str +' order by '+order_by+limit_str+offset_str, where_clause_params)
|
||||
cr.execute('select %s.id from ' % self._table + ','.join(tables) + where_str + order_by + limit_str+offset_str, where_clause_params)
|
||||
res = cr.fetchall()
|
||||
return [x[0] for x in res]
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ class report_custom(report_int):
|
||||
for cond in conditions:
|
||||
if cond and cond[0]:
|
||||
c = cond[0]
|
||||
temp = c[0](eval('obj.'+c[1]))
|
||||
temp = c[0](eval('obj.'+c[1],{'obj': obj}))
|
||||
if not eval('\''+temp+'\''+' '+c[2]+' '+'\''+str(c[3])+'\''):
|
||||
tobreak = True
|
||||
if tobreak:
|
||||
@@ -87,7 +87,7 @@ class report_custom(report_int):
|
||||
row_canvas[i]=False
|
||||
elif len(fields[i])==1:
|
||||
if not isinstance(obj, browse_null):
|
||||
row.append(str(eval('obj.'+fields[i][0])))
|
||||
row.append(str(eval('obj.'+fields[i][0],{'obj': obj})))
|
||||
else:
|
||||
row.append(None)
|
||||
else:
|
||||
@@ -107,7 +107,7 @@ class report_custom(report_int):
|
||||
else:
|
||||
key = levels.keys()
|
||||
for l in key:
|
||||
objs = eval('obj.'+l)
|
||||
objs = eval('obj.'+l,{'obj': obj})
|
||||
if not isinstance(objs, browse_record_list) and type(objs) <> type([]):
|
||||
objs = [objs]
|
||||
field_new = []
|
||||
@@ -192,7 +192,7 @@ class report_custom(report_int):
|
||||
def build_tree(obj, level, depth):
|
||||
res = self._row_get(cr, uid,[obj], new_fields, new_cond)
|
||||
level.append(depth)
|
||||
new_obj = eval('obj.'+report['field_parent'][1])
|
||||
new_obj = eval('obj.'+report['field_parent'][1],{'obj': obj})
|
||||
if not isinstance(new_obj, list) :
|
||||
new_obj = [new_obj]
|
||||
for o in new_obj:
|
||||
|
||||
@@ -137,7 +137,7 @@ class BaseHttpDaemon(threading.Thread, netsvc.Server):
|
||||
_RealProto = '??'
|
||||
|
||||
def __init__(self, interface, port, handler):
|
||||
threading.Thread.__init__(self)
|
||||
threading.Thread.__init__(self, name='%sDaemon-%d'%(self._RealProto, port))
|
||||
netsvc.Server.__init__(self)
|
||||
self.__port = port
|
||||
self.__interface = interface
|
||||
|
||||
@@ -102,7 +102,7 @@ class TinySocketClientThread(threading.Thread, netsvc.OpenERPDispatcher):
|
||||
|
||||
class TinySocketServerThread(threading.Thread,netsvc.Server):
|
||||
def __init__(self, interface, port, secure=False):
|
||||
threading.Thread.__init__(self, name="Net-RPC socket")
|
||||
threading.Thread.__init__(self, name="NetRPCDaemon-%d"%port)
|
||||
netsvc.Server.__init__(self)
|
||||
self.__port = port
|
||||
self.__interface = interface
|
||||
|
||||
+1
-2
@@ -113,8 +113,6 @@ class Cursor(object):
|
||||
self.__logger.warn(query)
|
||||
self.__logger.warn("SQL queries cannot contain %d or %f anymore. "
|
||||
"Use only %s")
|
||||
if params:
|
||||
query = query.replace('%d', '%s').replace('%f', '%s')
|
||||
|
||||
if self.sql_log:
|
||||
now = mdt.now()
|
||||
@@ -167,6 +165,7 @@ class Cursor(object):
|
||||
sqllogitems = sqllogs[type].items()
|
||||
sqllogitems.sort(key=lambda k: k[1][1])
|
||||
self.__logger.log(logging.DEBUG_SQL, "SQL LOG %s:", type)
|
||||
sqllogitems.sort(lambda x,y: cmp(x[1][0], y[1][0]))
|
||||
for r in sqllogitems:
|
||||
delay = timedelta(microseconds=r[1][1])
|
||||
self.__logger.log(logging.DEBUG_SQL, "table: %s: %s/%s",
|
||||
|
||||
+2
-5
@@ -98,7 +98,7 @@ class configmanager(object):
|
||||
self.has_ssl = check_ssl()
|
||||
|
||||
self._LOGLEVELS = dict([(getattr(netsvc, 'LOG_%s' % x), getattr(logging, x))
|
||||
for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'TEST', 'DEBUG', 'DEBUG_RPC', 'DEBUG_SQL', 'NOTSET')])
|
||||
for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'TEST', 'DEBUG', 'DEBUG_RPC', 'DEBUG_SQL', 'DEBUG_RPC_ANSWER','NOTSET')])
|
||||
|
||||
version = "%s %s" % (release.description, release.version)
|
||||
self.parser = parser = optparse.OptionParser(version=version)
|
||||
@@ -221,9 +221,6 @@ class configmanager(object):
|
||||
|
||||
security = optparse.OptionGroup(parser, 'Security-related options')
|
||||
security.add_option('--no-database-list', action="store_false", dest='list_db', help="disable the ability to return the list of databases")
|
||||
security.add_option('--enable-code-actions', action='store_true',
|
||||
dest='server_actions_allow_code', default=False,
|
||||
help='Enables server actions of state "code". Warning, this is a security risk.')
|
||||
parser.add_option_group(security)
|
||||
|
||||
def parse_config(self):
|
||||
@@ -287,7 +284,7 @@ class configmanager(object):
|
||||
keys = [
|
||||
'language', 'translate_out', 'translate_in', 'debug_mode', 'smtp_ssl',
|
||||
'stop_after_init', 'logrotate', 'without_demo', 'netrpc', 'xmlrpc', 'syslog',
|
||||
'list_db', 'server_actions_allow_code', 'xmlrpcs',
|
||||
'list_db', 'xmlrpcs',
|
||||
'test_file', 'test_disable', 'test_commit', 'test_report_directory'
|
||||
]
|
||||
|
||||
|
||||
+8
-10
@@ -62,9 +62,7 @@ _logger = logging.getLogger('tools')
|
||||
def init_db(cr):
|
||||
import addons
|
||||
f = addons.get_module_resource('base', 'base.sql')
|
||||
for line in file_open(f).read().split(';'):
|
||||
if (len(line)>0) and (not line.isspace()):
|
||||
cr.execute(line)
|
||||
cr.execute(file_open(f).read())
|
||||
cr.commit()
|
||||
|
||||
for i in addons.get_modules():
|
||||
@@ -421,6 +419,13 @@ def _email_send(smtp_from, smtp_to_list, message, openobject_id=None, ssl=False,
|
||||
:return: True if the mail was delivered successfully to the smtp,
|
||||
else False (+ exception logged)
|
||||
"""
|
||||
class WriteToLogger(object):
|
||||
def __init__(self):
|
||||
self.logger = netsvc.Logger()
|
||||
|
||||
def write(self, s):
|
||||
self.logger.notifyChannel('email_send', netsvc.LOG_DEBUG, s)
|
||||
|
||||
if openobject_id:
|
||||
message['Message-Id'] = generate_tracking_message_id(openobject_id)
|
||||
|
||||
@@ -536,13 +541,6 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
|
||||
part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
|
||||
msg.attach(part)
|
||||
|
||||
class WriteToLogger(object):
|
||||
def __init__(self):
|
||||
self.logger = netsvc.Logger()
|
||||
|
||||
def write(self, s):
|
||||
self.logger.notifyChannel('email_send', netsvc.LOG_DEBUG, s)
|
||||
|
||||
return _email_send(email_from, flatten([email_to, email_cc, email_bcc]), msg, openobject_id=openobject_id, ssl=ssl, debug=debug)
|
||||
|
||||
#----------------------------------------------------------
|
||||
|
||||
@@ -70,14 +70,16 @@ def is_url(node):
|
||||
|
||||
def is_eval(node):
|
||||
return isinstance(node, yaml_tag.Eval)
|
||||
|
||||
|
||||
def is_ref(node):
|
||||
return isinstance(node, yaml_tag.Ref) \
|
||||
or _is_yaml_mapping(node, yaml_tag.Ref)
|
||||
|
||||
|
||||
def is_ir_set(node):
|
||||
return _is_yaml_mapping(node, yaml_tag.IrSet)
|
||||
|
||||
def is_string(node):
|
||||
return isinstance(node, basestring)
|
||||
|
||||
class TestReport(object):
|
||||
def __init__(self):
|
||||
@@ -374,6 +376,14 @@ class YamlInterpreter(object):
|
||||
elif column._type == "many2many":
|
||||
ids = [self.get_id(xml_id) for xml_id in expression]
|
||||
value = [(6, 0, ids)]
|
||||
elif column._type == "date" and is_string(expression):
|
||||
# enforce ISO format for string date values, to be locale-agnostic during tests
|
||||
time.strptime(expression, misc.DEFAULT_SERVER_DATE_FORMAT)
|
||||
value = expression
|
||||
elif column._type == "datetime" and is_string(expression):
|
||||
# enforce ISO format for string datetime values, to be locale-agnostic during tests
|
||||
time.strptime(expression, misc.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
value = expression
|
||||
else: # scalar field
|
||||
if is_eval(expression):
|
||||
value = self.process_eval(expression)
|
||||
|
||||
@@ -51,6 +51,38 @@ if 'bdist_rpm' in sys.argv:
|
||||
# get python short version
|
||||
py_short_version = '%s.%s' % sys.version_info[:2]
|
||||
|
||||
# backports os.walk with followlinks from python 2.6
|
||||
def walk_followlinks(top, topdown=True, onerror=None, followlinks=False):
|
||||
from os.path import join, isdir, islink
|
||||
from os import listdir, error
|
||||
|
||||
try:
|
||||
names = listdir(top)
|
||||
except error, err:
|
||||
if onerror is not None:
|
||||
onerror(err)
|
||||
return
|
||||
|
||||
dirs, nondirs = [], []
|
||||
for name in names:
|
||||
if isdir(join(top, name)):
|
||||
dirs.append(name)
|
||||
else:
|
||||
nondirs.append(name)
|
||||
|
||||
if topdown:
|
||||
yield top, dirs, nondirs
|
||||
for name in dirs:
|
||||
path = join(top, name)
|
||||
if followlinks or not islink(path):
|
||||
for x in walk_followlinks(path, topdown, onerror, followlinks):
|
||||
yield x
|
||||
if not topdown:
|
||||
yield top, dirs, nondirs
|
||||
|
||||
if sys.version_info < (2, 6):
|
||||
os.walk = walk_followlinks
|
||||
|
||||
def find_addons():
|
||||
for root, _, names in os.walk(join('bin', 'addons'), followlinks=True):
|
||||
if '__openerp__.py' in names or '__terp__.py' in names:
|
||||
@@ -64,6 +96,8 @@ def data_files():
|
||||
files.append((root, [join(root, name) for name in names]))
|
||||
for root, _, names in os.walk('doc'):
|
||||
files.append((root, [join(root, name) for name in names]))
|
||||
for root, _, names in os.walk('pixmaps'):
|
||||
files.append((root, [join(root, name) for name in names]))
|
||||
files.append(('.', [join('bin', 'import_xml.rng'),
|
||||
join('bin', 'server.pkey'),
|
||||
join('bin', 'server.cert')]))
|
||||
@@ -139,7 +173,7 @@ options = {
|
||||
"encodings", "dateutil", "wizard", "pychart", "PIL", "pyparsing",
|
||||
"pydot", "asyncore","asynchat", "reportlab", "vobject",
|
||||
"HTMLParser", "select", "mako", "poplib",
|
||||
"imaplib", "smtplib", "email", "yaml","pywebdav",
|
||||
"imaplib", "smtplib", "email", "yaml", "DAV",
|
||||
],
|
||||
"excludes" : ["Tkconstants","Tkinter","tcl"],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user