[MERGE]:Merged from trunk

bzr revid: apa@tinyerp.com-20100924045528-zgmlmd1n2fvetyxw
This commit is contained in:
apa-tiny
2010-09-24 10:25:28 +05:30
36 changed files with 583 additions and 403 deletions
+24 -12
View File
@@ -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'))
+1 -1
View File
@@ -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',
+2 -2
View File
@@ -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>
+5 -14
View File
@@ -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>
+50 -33
View File
@@ -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"
+2 -2
View File
@@ -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,
+2 -2
View File
@@ -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>
+7 -7
View File
@@ -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:
+2 -2
View File
@@ -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:
+9 -17
View File
@@ -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):
+7 -7
View File
@@ -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>
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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]
+4 -4
View File
@@ -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:
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
#----------------------------------------------------------
+12 -2
View File
@@ -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)
+35 -1
View File
@@ -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"],
}