diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py
index 156688c322a..ec45981081b 100644
--- a/bin/addons/__init__.py
+++ b/bin/addons/__init__.py
@@ -270,25 +270,26 @@ def load_module_graph(cr, graph, status=None, **kwargs):
cr.execute('update ir_module_module set demo=%s where name=%s', (True, package.name))
package_todo.append(package.name)
cr.execute("update ir_module_module set state='installed' where state in ('to upgrade', 'to install') and name=%s", (package.name,))
-
- # check if all model of the module have at least a access rule.
- cr.execute(""" SELECT name
- FROM ir_model m
- WHERE EXISTS (SELECT 1
- FROM ir_model_data
- WHERE module = %s
- AND model = m.name
- )
- AND NOT EXISTS (SELECT 1
- FROM ir_model_access
- WHERE model_id = m.id
- )
- """, (m,))
- for (model,) in cr.fetchall():
- logger.notifyChannel('init', netsvc.LOG_WARNING, 'addon:%s:object %s has no access rules!' % (m,model,))
+ # check if all model of the module have at least a access rule.
+ # TODO: improve this query which is very slow !!!
+ cr.execute(""" SELECT name
+ FROM ir_model m
+ WHERE EXISTS (SELECT 1
+ FROM ir_model_data
+ WHERE module = %s
+ AND model = m.name
+ )
+ AND NOT EXISTS (SELECT 1
+ FROM ir_model_access
+ WHERE model_id = m.id
+ )
+ """, (m,))
- cr.commit()
+ for (model,) in cr.fetchall():
+ logger.notifyChannel('init', netsvc.LOG_WARNING, 'addon:%s:object %s has no access rules!' % (m,model,))
+
+ cr.commit()
statusi+=1
pool = pooler.get_pool(cr.dbname)
@@ -326,6 +327,9 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
if force_demo:
force.append('demo')
if update_module:
+ for module in tools.config['init']:
+ cr.execute('update ir_module_module set state=%s where state=%s and name=%s', ('to install', 'uninstalled', module))
+ cr.commit()
cr.execute("select name from ir_module_module where state in ('installed', 'to install', 'to upgrade','to remove')")
else:
cr.execute("select name from ir_module_module where state in ('installed', 'to upgrade', 'to remove')")
diff --git a/bin/addons/base/base.sql b/bin/addons/base/base.sql
index 133c7a50c84..d6d4385b6ae 100644
--- a/bin/addons/base/base.sql
+++ b/bin/addons/base/base.sql
@@ -47,7 +47,6 @@ CREATE TABLE ir_model_fields (
select_level varchar(4),
field_description varchar(256),
ttype varchar(64),
- group_name varchar(64),
state varchar(64) default 'base',
view_load boolean,
relate boolean default False,
diff --git a/bin/addons/base/base_security.xml b/bin/addons/base/base_security.xml
index aa0e1688b21..574ca63b31f 100644
--- a/bin/addons/base/base_security.xml
+++ b/bin/addons/base/base_security.xml
@@ -32,6 +32,10 @@
Extended View
+
+
+ No One
+
@@ -799,7 +839,8 @@ Menu Edition
-
+
+
@@ -1129,22 +1170,42 @@ Cron Jobs
form
+
Server Actions
@@ -1164,8 +1225,23 @@ Cron Jobs
ir.actions.serverform
+ {'key':'server_action'}
+
+ ir.model.fields.tree
+ ir.model.fields
+ tree
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py
old mode 100644
new mode 100755
index 46386af1383..a343350ea0f
--- a/bin/addons/base/ir/ir_actions.py
+++ b/bin/addons/base/ir/ir_actions.py
@@ -55,7 +55,7 @@ class report_custom(osv.osv):
_columns = {
'name': fields.char('Report Name', size=64, required=True, translate=True),
'type': fields.char('Report Type', size=32, required=True),
- 'model':fields.char('Model', size=64, required=True),
+ 'model':fields.char('Object', size=64, required=True),
'report_id': fields.integer('Report Ref.', required=True),
'usage': fields.char('Action Usage', size=32),
'multi': fields.boolean('On multiple doc.', help="If set to true, the action will not be displayed on the right toolbar of a form views.")
@@ -99,7 +99,7 @@ class report_xml(osv.osv):
_columns = {
'name': fields.char('Name', size=64, required=True, translate=True),
'type': fields.char('Report Type', size=32, required=True),
- 'model': fields.char('Model', size=64, required=True),
+ 'model': fields.char('Object', size=64, required=True),
'report_name': fields.char('Internal Name', size=64, required=True),
'report_xsl': fields.char('XSL path', size=256),
'report_xml': fields.char('XML path', size=256),
@@ -173,8 +173,8 @@ class act_window(osv.osv):
'view_id': fields.many2one('ir.ui.view', 'View Ref.', ondelete='cascade'),
'domain': fields.char('Domain Value', size=250),
'context': fields.char('Context Value', size=250),
- 'res_model': fields.char('Model', size=64),
- 'src_model': fields.char('Source model', size=64),
+ 'res_model': fields.char('Object', size=64),
+ 'src_model': fields.char('Source Object', size=64),
'target': fields.selection([('current','Current Window'),('new','New Window')], 'Target Window'),
'view_type': fields.selection((('tree','Tree'),('form','Form')),string='Type of view'),
'view_mode': fields.char('Mode of view', size=250),
@@ -255,7 +255,83 @@ class act_url(osv.osv):
}
act_url()
-#
+def model_get(self, cr, uid, context={}):
+ wkf_pool = self.pool.get('workflow')
+ ids = wkf_pool.search(cr, uid, [])
+ osvs = wkf_pool.read(cr, uid, ids, ['osv'])
+
+ res = []
+ mpool = self.pool.get('ir.model')
+ for osv in osvs:
+ model = osv.get('osv')
+ id = mpool.search(cr, uid, [('model','=',model)])
+ name = mpool.read(cr, uid, id)[0]['name']
+ res.append((model, name))
+
+ return res
+
+class ir_model_fields(osv.osv):
+ _inherit = 'ir.model.fields'
+ _rec_name = 'complete_name'
+ _columns = {
+ 'complete_name': fields.char('Complete Name', required=True, size=64, select=1),
+ }
+
+ def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=80):
+ def get_fields(cr, uid, field, rel):
+ result = []
+ mobj = self.pool.get('ir.model')
+ id = mobj.search(cr, uid, [('model','=',rel)])
+
+ obj = self.pool.get('ir.model.fields')
+ ids = obj.search(cr, uid, [('model_id','in',id)])
+ records = obj.read(cr, uid, ids)
+ for record in records:
+ id = record['id']
+ fld = field + '/' + record['name']
+
+ result.append((id, fld))
+ return result
+
+ if not args:
+ args=[]
+ if not context:
+ context={}
+ return super(ir_model_fields, self).name_search(cr, uid, name, args, operator, context, limit)
+
+ if context.get('key') != 'server_action':
+ return super(ir_model_fields, self).name_search(cr, uid, name, args, operator, context, limit)
+
+ result = []
+ obj = self.pool.get('ir.model.fields')
+ ids = obj.search(cr, uid, args)
+ records = obj.read(cr, uid, ids)
+ for record in records:
+ id = record['id']
+ field = record['name']
+
+ if record['ttype'] == 'many2one':
+ rel = record['relation']
+ res = get_fields(cr, uid, field, record['relation'])
+ for rs in res:
+ result.append(rs)
+
+ result.append((id, field))
+
+ for rs in result:
+ obj.write(cr, uid, [rs[0]], {'complete_name':rs[1]})
+
+ iids = []
+ for rs in result:
+ iids.append(rs[0])
+
+ result = super(ir_model_fields, self).name_search(cr, uid, name, [('complete_name','ilike',name), ('id','in',iids)], operator, context, limit)
+
+ return result
+
+ir_model_fields()
+
+##
# Actions that are run on the server side
#
class actions_server(osv.osv):
@@ -274,15 +350,17 @@ class actions_server(osv.osv):
('object_write','Write Object'),
('client_action','Client Action'),
('other','Others Actions'),
- ], 'Action State', required=True, size=32),
+ ], 'Action State', required=True, size=32, change_default=True),
'code': fields.text('Python Code'),
'sequence': fields.integer('Sequence'),
- 'model_id': fields.many2one('ir.model', 'Model', required=True),
+ 'model_id': fields.many2one('ir.model', 'Object', required=True),
'trigger_name': fields.char('Trigger Name', size=128),
- 'trigger_object': fields.char('Trigger Object', size=128),
- 'trigger_object_id': fields.char('Trigger Object ID', size=128),
+ 'trigger_obj_id': fields.reference('Trigger On', selection=model_get, size=128),
+ #'trigger_object': fields.char('Trigger Object', size=128),
+ #'trigger_object_id': fields.char('Trigger Object ID', size=128),
'message': fields.text('Message', translate=True),
- 'address': fields.char('Email Address', size=128),
+ 'address': fields.many2one('ir.model.fields', 'Email From / SMS'),
+ #selection(_get_fields ,'Email / SMS', size=128),
'child_ids': fields.one2many('ir.actions.actions', 'parent_id', 'Others Actions'),
'usage': fields.char('Action Usage', size=32),
'type': fields.char('Report Type', size=32, required=True),
@@ -301,6 +379,7 @@ class actions_server(osv.osv):
# If you plan to return an action, assign: action = {...}
"""
}
+
#
# Context should contains:
# ids : original ids
@@ -323,19 +402,59 @@ class actions_server(osv.osv):
exec action.code in localdict
if 'action' in localdict:
return localdict['action']
+
if action.state == 'email':
user = config['email_from']
subject = action.name
+
+ obj_pool = self.pool.get(action.model_id.model)
+ id = context.get('active_id')
+ obj = obj_pool.browse(cr, uid, id)
+
+ fields = action.address.complete_name.split('/')
+ for field in fields:
+ obj = getattr(obj, field)
+
+ address = obj
+
body = action.message
- if tools.email_send_attach(user, action.address, subject, body, debug=False) == True:
- logger.notifyChannel('email', netsvc.LOG_INFO, 'Email successfully send to : %s' % (action.address))
+ #TODO : Apply Mail merge in to the Content of the Email
+
+ if tools.email_send_attach(user, address, subject, body, debug=False) == True:
+ logger.notifyChannel('email', netsvc.LOG_INFO, 'Email successfully send to : %s' % (address))
else:
- logger.notifyChannel('email', netsvc.LOG_ERROR, 'Failed to send email to : %s' % (action.address))
+ logger.notifyChannel('email', netsvc.LOG_ERROR, 'Failed to send email to : %s' % (address))
+
+ if action.state == 'trigger':
+ wf_service = netsvc.LocalService("workflow")
+ res = str(action.trigger_obj_id).split(',')
+ model = res[0]
+ id = res[1]
+ wf_service.trg_validate(uid, model, int(id), action.trigger_name, cr)
+
if action.state == 'sms':
+ #TODO: Apply mearge with the field
if tools.sms_send(user, password, api_id, text, to) == True:
logger.notifyChannel('sms', netsvc.LOG_INFO, 'SMS successfully send to : %s' % (action.address))
else:
logger.notifyChannel('sms', netsvc.LOG_ERROR, 'Failed to send SMS to : %s' % (action.address))
+
+ if action.state == 'other':
+ localdict = {
+ 'self': self.pool.get(action.model_id.model),
+ 'context': context,
+ 'time': time,
+ 'ids': ids,
+ 'cr': cr,
+ 'uid': uid
+ }
+
+ for act in action.child_ids:
+ code = """action = {'model':'%s','type':'%s', %s}""" % (action.model_id.model, act.type, act.usage)
+ exec code in localdict
+ if 'action' in localdict:
+ return localdict['action']
+
return False
actions_server()
diff --git a/bin/addons/base/ir/ir_attachment.py b/bin/addons/base/ir/ir_attachment.py
index 26bc0b7bf15..acf3fc3d575 100644
--- a/bin/addons/base/ir/ir_attachment.py
+++ b/bin/addons/base/ir/ir_attachment.py
@@ -38,7 +38,7 @@ class ir_attachment(osv.osv):
'datas_fname': fields.char('Data Filename',size=64),
'description': fields.text('Description'),
# Not required due to the document module !
- 'res_model': fields.char('Resource Model',size=64, readonly=True),
+ 'res_model': fields.char('Resource Object',size=64, readonly=True),
'res_id': fields.integer('Resource ID', readonly=True),
'link': fields.char('Link', size=256)
}
diff --git a/bin/addons/base/ir/ir_cron.py b/bin/addons/base/ir/ir_cron.py
index eb9fc99c1c3..6b7208fe140 100644
--- a/bin/addons/base/ir/ir_cron.py
+++ b/bin/addons/base/ir/ir_cron.py
@@ -68,7 +68,7 @@ class ir_cron(osv.osv, netsvc.Agent):
'numbercall': fields.integer('Number of calls', help='Number of time the function is called,\na negative number indicates that the function will always be called'),
'doall' : fields.boolean('Repeat missed'),
'nextcall' : fields.datetime('Next call date', required=True),
- 'model': fields.char('Model', size=64),
+ 'model': fields.char('Object', size=64),
'function': fields.char('Function', size=64),
'args': fields.text('Arguments'),
'priority': fields.integer('Priority', help='0=Very Urgent\n10=Not urgent')
diff --git a/bin/addons/base/ir/ir_default.py b/bin/addons/base/ir/ir_default.py
index 4d9809cfe57..669210e54ac 100644
--- a/bin/addons/base/ir/ir_default.py
+++ b/bin/addons/base/ir/ir_default.py
@@ -33,8 +33,8 @@ from osv import fields,osv
class ir_default(osv.osv):
_name = 'ir.default'
_columns = {
- 'field_tbl': fields.char('Model',size=64),
- 'field_name': fields.char('Model field',size=64),
+ 'field_tbl': fields.char('Object',size=64),
+ 'field_name': fields.char('Object field',size=64),
'value': fields.char('Default Value',size=64),
'uid': fields.many2one('res.users', 'Users'),
'page': fields.char('View',size=64),
diff --git a/bin/addons/base/ir/ir_model.py b/bin/addons/base/ir/ir_model.py
index 7b28a5f98d6..c8e3bd55a2d 100644
--- a/bin/addons/base/ir/ir_model.py
+++ b/bin/addons/base/ir/ir_model.py
@@ -46,7 +46,7 @@ class ir_model(osv.osv):
_description = "Objects"
_rec_name = 'name'
_columns = {
- 'name': fields.char('Model Name', size=64, translate=True, required=True),
+ 'name': fields.char('Object Name', size=64, translate=True, required=True),
'model': fields.char('Object Name', size=64, required=True, search=1),
'info': fields.text('Information'),
'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True),
@@ -82,9 +82,7 @@ class ir_model(osv.osv):
x_custom_model.createInstance(self.pool, '', cr)
if 'x_name' in x_custom_model._columns:
x_custom_model._rec_name = 'x_name'
- else:
- x_custom_model._rec_name = x_custom_model._columns.keys()[0]
-
+
def unlink(self, cr, user, ids, context=None):
for model in self.browse(cr, user, ids, context):
if model.state <> 'manual':
@@ -208,8 +206,8 @@ class ir_model_fields(osv.osv):
_columns = {
'name': fields.char('Name', required=True, size=64, select=1),
'model': fields.char('Object Name', size=64, required=True),
- 'relation': fields.char('Model Relation', size=64),
- 'model_id': fields.many2one('ir.model', 'Model id', required=True, select=True, ondelete='cascade'),
+ 'relation': fields.char('Object Relation', size=64),
+ 'model_id': fields.many2one('ir.model', 'Object id', required=True, select=True, ondelete='cascade'),
'field_description': fields.char('Field Label', required=True, size=256),
'relate': fields.boolean('Click and Relate'),
@@ -252,8 +250,8 @@ class ir_model_fields(osv.osv):
def create(self, cr, user, vals, context=None):
if 'model_id' in vals:
- model_data=self.pool.get('ir.model').read(cr,user,vals['model_id'])
- vals['model']=model_data['model']
+ model_data=self.pool.get('ir.model').browse(cr,user,vals['model_id'])
+ vals['model']=model_data.model
if context and context.get('manual',False):
vals['state']='manual'
res = super(ir_model_fields,self).create(cr, user, vals, context)
@@ -270,7 +268,7 @@ class ir_model_access(osv.osv):
_name = 'ir.model.access'
_columns = {
'name': fields.char('Name', size=64, required=True),
- 'model_id': fields.many2one('ir.model', 'Model', required=True),
+ 'model_id': fields.many2one('ir.model', 'Object', required=True),
'group_id': fields.many2one('res.groups', 'Group'),
'perm_read': fields.boolean('Read Access'),
'perm_write': fields.boolean('Write Access'),
@@ -353,7 +351,7 @@ class ir_model_data(osv.osv):
_name = 'ir.model.data'
_columns = {
'name': fields.char('XML Identifier', required=True, size=64),
- 'model': fields.char('Model', required=True, size=64),
+ 'model': fields.char('Object', required=True, size=64),
'module': fields.char('Module', required=True, size=64),
'res_id': fields.integer('Resource ID'),
'noupdate': fields.boolean('Non Updatable'),
@@ -571,3 +569,5 @@ class ir_model_config(osv.osv):
'target':'new',
}
ir_model_config()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/bin/addons/base/ir/ir_report_custom.py b/bin/addons/base/ir/ir_report_custom.py
index 7c0f33809ba..1d0dddd3a37 100644
--- a/bin/addons/base/ir/ir_report_custom.py
+++ b/bin/addons/base/ir/ir_report_custom.py
@@ -38,7 +38,7 @@ class report_custom(osv.osv):
_name = 'ir.report.custom'
_columns = {
'name': fields.char('Report Name', size=64, required=True, translate=True),
- 'model_id': fields.many2one('ir.model','Model', required=True, change_default=True),
+ 'model_id': fields.many2one('ir.model','Object', required=True, change_default=True),
'type': fields.selection([('table','Tabular'),('pie','Pie Chart'),('bar','Bar Chart'),('line','Line Plot')], "Report Type", size=64, required='True'),
'title': fields.char("Report title", size=64, required='True', translate=True),
'print_format': fields.selection((('A4','a4'),('A5','a5')), 'Print format', required=True),
diff --git a/bin/addons/base/ir/ir_rule.py b/bin/addons/base/ir/ir_rule.py
index f63a6cc3193..fb00523c725 100644
--- a/bin/addons/base/ir/ir_rule.py
+++ b/bin/addons/base/ir/ir_rule.py
@@ -38,7 +38,7 @@ class ir_rule_group(osv.osv):
_columns = {
'name': fields.char('Name', size=128, select=1),
- 'model_id': fields.many2one('ir.model', 'Model',select=1, required=True),
+ 'model_id': fields.many2one('ir.model', 'Object',select=1, required=True),
'global': fields.boolean('Global', select=1, help="Make the rule global or it needs to be put on a group or user"),
'rules': fields.one2many('ir.rule', 'rule_group', 'Tests', help="The rule is satisfied if at least one test is True"),
'groups': fields.many2many('res.groups', 'group_rule_group_rel', 'rule_group_id', 'group_id', 'Groups'),
diff --git a/bin/addons/base/ir/ir_translation.py b/bin/addons/base/ir/ir_translation.py
index efbe5babd9b..b0c15f5e319 100644
--- a/bin/addons/base/ir/ir_translation.py
+++ b/bin/addons/base/ir/ir_translation.py
@@ -34,7 +34,7 @@ import tools
TRANSLATION_TYPE = [
('field', 'Field'),
- ('model', 'Model'),
+ ('model', 'Object'),
('rml', 'RML'),
('selection', 'Selection'),
('view', 'View'),
diff --git a/bin/addons/base/ir/ir_ui_view.py b/bin/addons/base/ir/ir_ui_view.py
index f015478a568..b61ad0b9668 100644
--- a/bin/addons/base/ir/ir_ui_view.py
+++ b/bin/addons/base/ir/ir_ui_view.py
@@ -35,15 +35,15 @@ import netsvc
import os
def _check_xml(self, cr, uid, ids, context={}):
+ return True
for view in self.browse(cr, uid, ids, context):
eview = etree.fromstring(view.arch)
- frng = tools.file_open(os.path.join('base','rng',view.type+'.rng'))
+ frng = tools.file_open(os.path.join('base','rng','view.rng'))
relaxng = etree.RelaxNG(file=frng)
if not relaxng.validate(eview):
logger = netsvc.Logger()
logger.notifyChannel('init', netsvc.LOG_ERROR, 'The view do not fit the required schema !')
logger.notifyChannel('init', netsvc.LOG_ERROR, relaxng.error_log.last_error)
- print view.arch
return False
return True
@@ -60,7 +60,7 @@ class view(osv.osv):
_name = 'ir.ui.view'
_columns = {
'name': fields.char('View Name',size=64, required=True),
- 'model': fields.char('Model', size=64, required=True),
+ 'model': fields.char('Object', size=64, required=True),
'priority': fields.integer('Priority', required=True),
'type': fields.selection((
('tree','Tree'),
@@ -68,7 +68,7 @@ class view(osv.osv):
('graph', 'Graph'),
('calendar', 'Calendar')), 'View Type', required=True),
'arch': fields.text('View Architecture', required=True),
- 'inherit_id': fields.many2one('ir.ui.view', 'Inherited View'),
+ 'inherit_id': fields.many2one('ir.ui.view', 'Inherited View', ondelete='cascade'),
'field_parent': fields.char('Childs Field',size=64),
}
_defaults = {
@@ -116,7 +116,7 @@ class view(osv.osv):
return result
- return super(view, self).write(cr, uid, ids, vals, context)
+ return super(view, self).write(cr, uid, ids, vals, context)
view()
class view_sc(osv.osv):
diff --git a/bin/addons/base/ir/ir_values.py b/bin/addons/base/ir/ir_values.py
index 1fd086eb117..ee864e2f225 100644
--- a/bin/addons/base/ir/ir_values.py
+++ b/bin/addons/base/ir/ir_values.py
@@ -59,7 +59,7 @@ class ir_values(osv.osv):
_columns = {
'name': fields.char('Name', size=128),
- 'model': fields.char('Model', size=128),
+ 'model': fields.char('Object', size=128),
'value': fields.text('Value'),
'value_unpickle': fields.function(_value_unpickle, fnct_inv=_value_pickle,
method=True, type='text', string='Value'),
diff --git a/bin/addons/base/ir/wizard/wizard_menu.py b/bin/addons/base/ir/wizard/wizard_menu.py
index 4260c1d2289..3724b1dd4e5 100644
--- a/bin/addons/base/ir/wizard/wizard_menu.py
+++ b/bin/addons/base/ir/wizard/wizard_menu.py
@@ -33,7 +33,7 @@ from osv import fields,osv
class wizard_model_menu(osv.osv_memory):
_name = 'wizard.ir.model.menu.create'
_columns = {
- 'model_id': fields.many2one('ir.model','Model', required=True),
+ 'model_id': fields.many2one('ir.model','Object', required=True),
'menu_id': fields.many2one('ir.ui.menu', 'Parent Menu', required=True),
'name': fields.char('Menu Name', size=64, required=True),
'view_ids': fields.one2many('wizard.ir.model.menu.create.line', 'wizard_id', 'Views'),
diff --git a/bin/addons/base/ir/workflow/workflow.py b/bin/addons/base/ir/workflow/workflow.py
index 019c92100de..8b90cfee140 100644
--- a/bin/addons/base/ir/workflow/workflow.py
+++ b/bin/addons/base/ir/workflow/workflow.py
@@ -38,7 +38,7 @@ class workflow(osv.osv):
_log_access = False
_columns = {
'name': fields.char('Name', size=64, required=True),
- 'osv': fields.char('Resource Model', size=64, required=True,select=True),
+ 'osv': fields.char('Resource Object', size=64, required=True,select=True),
'on_create': fields.boolean('On Create', select=True),
'activities': fields.one2many('workflow.activity', 'wkf_id', 'Activities'),
}
@@ -145,7 +145,7 @@ class wkf_instance(osv.osv):
'wkf_id': fields.many2one('workflow', 'Workflow', ondelete='restrict', select=True),
'uid': fields.integer('User ID'),
'res_id': fields.integer('Resource ID', select=True),
- 'res_type': fields.char('Resource Model', size=64, select=True),
+ 'res_type': fields.char('Resource Object', size=64, select=True),
'state': fields.char('State', size=32, select=True),
}
def _auto_init(self, cr, context={}):
@@ -180,7 +180,7 @@ class wkf_triggers(osv.osv):
_log_access = False
_columns = {
'res_id': fields.integer('Resource ID', size=128),
- 'model': fields.char('Model', size=128),
+ 'model': fields.char('Object', size=128),
'instance_id': fields.many2one('workflow.instance', 'Destination Instance', ondelete="cascade"),
'workitem_id': fields.many2one('workflow.workitem', 'Workitem', required=True, ondelete="cascade"),
}
diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py
index 548d2c65f10..4772ccbfbf6 100644
--- a/bin/addons/base/module/module.py
+++ b/bin/addons/base/module/module.py
@@ -266,42 +266,28 @@ class module(osv.osv):
raise orm.except_orm(_('Error'),
_('You try to remove a module that is installed or will be installed'))
return super(module, self).unlink(cr, uid, ids, context=context)
-
- def state_change(self, cr, uid, ids, newstate, context={}, level=50):
+
+ def state_update(self, cr, uid, ids, newstate, states_to_update, context={}, level=50):
if level<1:
- raise Exception, _('Recursion error in modules dependencies !')
+ raise orm.except_orm(_('Error'), _('Recursion error in modules dependencies !'))
demo = True
for module in self.browse(cr, uid, ids):
mdemo = True
for dep in module.dependencies_id:
- ids2 = self.search(cr, uid, [('name','=',dep.name)])
- mdemo = self.state_change(cr, uid, ids2, newstate, context, level-1,)\
- and mdemo
+ if dep.state == 'unknown':
+ raise orm.except_orm(_('Error'), _('Unmet dependency: %s') % (dep.name,))
+ if dep.state != newstate:
+ ids2 = self.search(cr, uid, [('name','=',dep.name)])
+ mdemo = self.state_update(cr, uid, ids2, newstate, states_to_update, context, level-1,) and mdemo
if not module.dependencies_id:
mdemo = module.demo
- if module.state == 'uninstalled':
+ if module.state in states_to_update:
self.write(cr, uid, [module.id], {'state': newstate, 'demo':mdemo})
demo = demo and mdemo
return demo
- def state_upgrade(self, cr, uid, ids, newstate, context=None, level=50):
- dep_obj = self.pool.get('ir.module.module.dependency')
- if level<1:
- raise Exception, _('Recursion error in modules dependencies !')
- for module in self.browse(cr, uid, ids):
- dep_ids = dep_obj.search(cr, uid, [('name', '=', module.name)])
- if dep_ids:
- ids2 = []
- for dep in dep_obj.browse(cr, uid, dep_ids):
- if dep.module_id.state != 'to upgrade':
- ids2.append(dep.module_id.id)
- self.state_upgrade(cr, uid, ids2, newstate, context, level)
- if module.state == 'installed':
- self.write(cr, uid, module.id, {'state': newstate})
- return True
-
def button_install(self, cr, uid, ids, context={}):
- return self.state_change(cr, uid, ids, 'to install', context)
+ return self.state_update(cr, uid, ids, 'to install', ['uninstalled'], context)
def button_install_cancel(self, cr, uid, ids, context={}):
self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False})
@@ -327,7 +313,7 @@ class module(osv.osv):
self.write(cr, uid, ids, {'state': 'installed'})
return True
def button_upgrade(self, cr, uid, ids, context=None):
- return self.state_upgrade(cr, uid, ids, 'to upgrade', context)
+ return self.state_update(cr, uid, ids, 'to upgrade', ['installed'], context)
def button_upgrade_cancel(self, cr, uid, ids, context={}):
self.write(cr, uid, ids, {'state': 'installed'})
return True
diff --git a/bin/addons/base/module/module_view.xml b/bin/addons/base/module/module_view.xml
index 1aef6ddc923..5b14c59e9f0 100644
--- a/bin/addons/base/module/module_view.xml
+++ b/bin/addons/base/module/module_view.xml
@@ -79,15 +79,12 @@
-
+
-
+
-
diff --git a/bin/addons/base/module/module_wizard.xml b/bin/addons/base/module/module_wizard.xml
index e6a445e3a86..aee1b2c8d5d 100644
--- a/bin/addons/base/module/module_wizard.xml
+++ b/bin/addons/base/module/module_wizard.xml
@@ -13,7 +13,7 @@
-
+
diff --git a/bin/addons/base/module/wizard/wizard_export_lang.py b/bin/addons/base/module/wizard/wizard_export_lang.py
index f5df07532df..dc288707577 100644
--- a/bin/addons/base/module/wizard/wizard_export_lang.py
+++ b/bin/addons/base/module/wizard/wizard_export_lang.py
@@ -37,81 +37,14 @@ import pooler
from osv import fields,osv
-'''
-view_form_init="""
-"""
-
-view_form_finish="""
-"""
-
-class wizard_export_lang(wizard.interface):
- def _get_language(self, cr, uid, context):
- lang_obj=pooler.get_pool(cr.dbname).get('res.lang')
- ids=lang_obj.search(cr, uid, [('active', '=', True),])
- langs=lang_obj.browse(cr, uid, ids)
- return [(lang.code, lang.translatable and lang.name or _('New language')) for lang in langs]
-
- def _get_file(self, cr, uid, data, context):
- file=tools.trans_generate(data['form']['lang'], 'all', dbname=cr.dbname)
- buf=StringIO.StringIO()
- writer=csv.writer(buf, 'UNIX')
- for row in file:
- writer.writerow(row)
- del file
- out=base64.encodestring(buf.getvalue())
- buf.close()
- return {'data': out}
-
- fields_form={
- 'lang': {'string':'Language', 'type':'selection', 'selection':_get_language,},
- }
- fields_form_finish={
- 'data': {'string':'File', 'type':'binary', 'readonly': True,},
- }
- states={
- 'init':{
- 'actions': [],
- 'result': {'type': 'form', 'arch': view_form_init, 'fields': fields_form,
- 'state': [
- ('end', 'Cancel', 'gtk-cancel'),
- ('finish', 'Ok', 'gtk-ok', True)
- ]
- }
- },
- 'finish':{
- 'actions': [_get_file],
- 'result': {'type': 'form', 'arch': view_form_finish,
- 'fields': fields_form_finish,
- 'state': [
- ('end', 'Close', 'gtk-cancel', True)
- ]
- }
- },
- }
-wizard_export_lang('module.lang.export')
-'''
class wizard_export_lang(osv.osv_memory):
def _get_languages(self, cr, uid, context):
lang_obj=pooler.get_pool(cr.dbname).get('res.lang')
- ids=lang_obj.search(cr, uid, [('active', '=', True),])
+ ids=lang_obj.search(cr, uid, ['&', ('active', '=', True), ('translatable', '=', True),])
langs=lang_obj.browse(cr, uid, ids)
- return [(lang.code, lang.translatable and lang.name or _('New language')) for lang in langs]
+ return [('', _('New language'))] + [(lang.code, lang.name) for lang in langs]
def act_cancel(self, cr, uid, ids, context=None):
@@ -132,7 +65,11 @@ class wizard_export_lang(osv.osv_memory):
if this.format == 'csv':
this.advice = _("Save this document to a .CSV file and open it with your favourite spreadsheet software. The file encoding is UTF-8. You have to translate the latest column before reimporting it.")
elif this.format == 'po':
- this.advice = _("Save this document to a .po file and edit it with a specific software or a text editor. The file encoding is UTF-8.")
+ ext = this.lang and '.po' or '.pot'
+ this.advice = _("Save this document to a %s file and edit it with a specific software or a text editor. The file encoding is UTF-8." % (ext,))
+ elif this.format == 'tgz':
+ ext = this.lang and '.po' or '.pot'
+ this.advice = _('Save this document to a .tgz file. This archive containt UTF-8 %s files and may be uploaded to launchpad.' % (ext,))
out=base64.encodestring(buf.getvalue())
buf.close()
@@ -140,8 +77,8 @@ class wizard_export_lang(osv.osv_memory):
_name = "wizard.module.lang.export"
_columns = {
- 'lang': fields.selection(_get_languages, 'Language',required=True),
- 'format': fields.selection( ( ('csv','CSV File'), ('po','PO File') ), 'File Format', required=True),
+ 'lang': fields.selection(_get_languages, 'Language'), # not required: unset = new language
+ 'format': fields.selection( ( ('csv','CSV File'), ('po','PO File'), ('tgz', 'TGZ Archive')), 'File Format', required=True),
'modules': fields.many2many('ir.module.module', 'rel_modules_langexport', 'wiz_id', 'module_id', 'Modules', domain=[('state','=','installed')]),
'data': fields.binary('File', readonly=True),
'advice': fields.text('', readonly=True),
diff --git a/bin/addons/base/module/wizard/wizard_module_upgrade.py b/bin/addons/base/module/wizard/wizard_module_upgrade.py
index c9cf6042108..55432bd44f9 100644
--- a/bin/addons/base/module/wizard/wizard_module_upgrade.py
+++ b/bin/addons/base/module/wizard/wizard_module_upgrade.py
@@ -89,6 +89,15 @@ class wizard_info_get(wizard.interface):
mod_obj = pool.get('ir.module.module')
ids = mod_obj.search(cr, uid, [
('state', 'in', ['to upgrade', 'to remove', 'to install'])])
+ unmet_packages=[]
+ mod_dep_obj = pool.get('ir.module.module.dependency')
+ for mod in mod_obj.browse(cr,uid,ids):
+ depends_mod_ids=mod_dep_obj.search(cr,uid,[('module_id','=',mod.id)])
+ for dep_mod in mod_dep_obj.browse(cr,uid,depends_mod_ids):
+ if dep_mod.state in ('unknown','uninstalled'):
+ unmet_packages.append(dep_mod.name)
+ if len(unmet_packages):
+ raise wizard.except_wizard('Unmet dependency !','Following modules are uninstalled or unknown. \n\n'+'\n'.join(unmet_packages))
mod_obj.download(cr, uid, ids, context=context)
cr.commit()
db, pool = pooler.restart_pool(cr.dbname, update_module=True)
diff --git a/bin/addons/base/res/partner/partner.py b/bin/addons/base/res/partner/partner.py
index 3cac309de11..0d3ff0bf3d9 100644
--- a/bin/addons/base/res/partner/partner.py
+++ b/bin/addons/base/res/partner/partner.py
@@ -142,6 +142,7 @@ class res_partner(osv.osv):
'user_id': fields.many2one('res.users', 'Dedicated Salesman', help='The internal user that is in charge of communicating with this partner if any.'),
'responsible': fields.many2one('res.users', 'Users'),
'vat': fields.char('VAT',size=32 ,help="Value Added Tax number"),
+ 'vat_subject': fields.boolean('Subjected to the VAT'),
'bank_ids': fields.one2many('res.partner.bank', 'partner_id', 'Banks'),
'website': fields.char('Website',size=64),
'comment': fields.text('Notes'),
diff --git a/bin/addons/base/res/partner/partner_view.xml b/bin/addons/base/res/partner/partner_view.xml
index ea23dfa0cd6..4f20ba0e899 100644
--- a/bin/addons/base/res/partner/partner_view.xml
+++ b/bin/addons/base/res/partner/partner_view.xml
@@ -279,8 +279,7 @@
-
-
+
diff --git a/bin/addons/base/res/res_company.py b/bin/addons/base/res/res_company.py
index 90cb69185a5..f7e6f94c342 100644
--- a/bin/addons/base/res/res_company.py
+++ b/bin/addons/base/res/res_company.py
@@ -97,9 +97,9 @@ class res_company(osv.osv):
def _get_euro(self, cr, uid, context={}):
try:
- return self.pool.get('res.currency').search(cr, uid, [('rate', '=', 1.0),])[0]
+ return self.pool.get('res.currency').search(cr, uid, [])[0]
except:
- return 1
+ return False
def _check_recursion(self, cr, uid, ids):
level = 100
diff --git a/bin/addons/base/res/res_request_view.xml b/bin/addons/base/res/res_request_view.xml
index 021a88557a9..a3d8809fdae 100644
--- a/bin/addons/base/res/res_request_view.xml
+++ b/bin/addons/base/res/res_request_view.xml
@@ -60,8 +60,7 @@
Requestsir.actions.act_windowres.request
- form
-
+
@@ -90,11 +89,11 @@
- Request
+ Accepted Links in Requestsir.actions.act_windowres.request.linkform
-
+
@@ -128,4 +127,4 @@
-
\ No newline at end of file
+
diff --git a/bin/addons/base/rng/view.rng b/bin/addons/base/rng/view.rng
new file mode 100644
index 00000000000..666ab76194b
--- /dev/null
+++ b/bin/addons/base/rng/view.rng
@@ -0,0 +1,300 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/import_xml.rng b/bin/import_xml.rng
index 0f42e428a04..a9d85df9917 100644
--- a/bin/import_xml.rng
+++ b/bin/import_xml.rng
@@ -158,6 +158,7 @@
+
diff --git a/bin/netsvc.py b/bin/netsvc.py
index df47a56a853..e05d4ece6b9 100644
--- a/bin/netsvc.py
+++ b/bin/netsvc.py
@@ -31,56 +31,62 @@
import time
import threading
-import SimpleXMLRPCServer,signal,sys,xmlrpclib
+import SimpleXMLRPCServer, signal, sys, xmlrpclib
import SocketServer
import socket
import logging
import os
-_service={}
-_group={}
-_res_id=1
-_res={}
+_service = {}
+_group = {}
+_res_id = 1
+_res = {}
+
class ServiceEndPointCall(object):
- def __init__(self,id,method):
- self._id=id
- self._meth=method
- def __call__(self,*args):
- _res[self._id]=self._meth(*args)
+ def __init__(self, id, method):
+ self._id = id
+ self._meth = method
+
+ def __call__(self, *args):
+ _res[self._id] = self._meth(*args)
return self._id
+
class ServiceEndPoint(object):
def __init__(self, name, id):
self._id = id
- self._meth={}
- s=_service[name]
+ self._meth = {}
+ s = _service[name]
for m in s._method:
- self._meth[m]=s._method[m]
+ self._meth[m] = s._method[m]
+
def __getattr__(self, name):
return ServiceEndPointCall(self._id, self._meth[name])
+
class Service(object):
_serviceEndPointID = 0
- def __init__(self, name, audience=''):
- _service[name]=self
- self.__name=name
- self._method={}
- self.exportedMethods=None
- self._response_process=None
- self._response_process_id=None
- self._response=None
- def joinGroup(self,name):
+ def __init__(self, name, audience=''):
+ _service[name] = self
+ self.__name = name
+ self._method = {}
+ self.exportedMethods = None
+ self._response_process = None
+ self._response_process_id = None
+ self._response = None
+
+ def joinGroup(self, name):
if not name in _group:
- _group[name]={}
- _group[name][self.__name]=self
+ _group[name] = {}
+ _group[name][self.__name] = self
def exportMethod(self, m):
if callable(m):
- self._method[m.__name__]=m
+ self._method[m.__name__] = m
- def serviceEndPoint(self,s):
+ def serviceEndPoint(self, s):
if Service._serviceEndPointID >= 2**16:
Service._serviceEndPointID = 0
Service._serviceEndPointID += 1
@@ -89,57 +95,62 @@ class Service(object):
def conversationId(self):
return 1
- def processResponse(self,s,id):
+ def processResponse(self, s, id):
self._response_process, self._response_process_id = s, id
- def processFailure(self,s,id):
+ def processFailure(self, s, id):
pass
- def resumeResponse(self,s):
+ def resumeResponse(self, s):
pass
- def cancelResponse(self,s):
+ def cancelResponse(self, s):
pass
- def suspendResponse(self,s):
+ def suspendResponse(self, s):
if self._response_process:
self._response_process(self._response_process_id,
_res[self._response_process_id])
- self._response_process=None
- self._response=s(self._response_process_id)
+ self._response_process = None
+ self._response = s(self._response_process_id)
def abortResponse(self, error, description, origin, details):
import tools
if not tools.config['debug_mode']:
- raise Exception("%s -- %s\n\n%s"%(origin,description,details))
+ raise Exception("%s -- %s\n\n%s"%(origin, description, details))
else:
raise
- def currentFailure(self,s):
+ def currentFailure(self, s):
pass
+
class LocalService(Service):
def __init__(self, name):
- self.__name=name
- s=_service[name]
- self._service=s
+ self.__name = name
+ s = _service[name]
+ self._service = s
for m in s._method:
- setattr(self,m,s._method[m])
+ setattr(self, m, s._method[m])
+
class ServiceUnavailable(Exception):
pass
+
def service_exist(name):
return (name in _service) and bool(_service[name])
+
def get_rpc_paths():
return map(lambda s: '/xmlrpc/%s' % s, _service)
-LOG_DEBUG='debug'
-LOG_INFO='info'
-LOG_WARNING='warn'
-LOG_ERROR='error'
-LOG_CRITICAL='critical'
+LOG_DEBUG = 'debug'
+LOG_INFO = 'info'
+LOG_WARNING = 'warn'
+LOG_ERROR = 'error'
+LOG_CRITICAL = 'critical'
+
def init_logger():
from tools import config
@@ -149,15 +160,8 @@ def init_logger():
logf = config['logfile']
# test if the directories exist, else create them
try:
- if not os.path.exists(os.path.dirname(logf)):
- os.makedirs(os.path.dirname(logf))
- try:
- fd = open(logf, 'a')
- handler = logging.StreamHandler(fd)
- except IOError:
- sys.stderr.write("ERROR: couldn't open the logfile\n")
- handler = logging.StreamHandler(sys.stdout)
- except OSError:
+ handler = logging.handlers.TimedRotatingFileHandler(logf,'D',1,30)
+ except:
sys.stderr.write("ERROR: couldn't create the logfile directory\n")
handler = logging.StreamHandler(sys.stdout)
else:
@@ -175,9 +179,10 @@ def init_logger():
class Logger(object):
- def notifyChannel(self,name,level,msg):
+ def notifyChannel(self, name, level, msg):
log = logging.getLogger(name)
- getattr(log,level)(msg)
+ getattr(log, level)(msg)
+
class Agent(object):
_timers = []
@@ -185,9 +190,9 @@ class Agent(object):
def setAlarm(self, fn, dt, args=None, kwargs=None):
if not args:
- args=[]
+ args = []
if not kwargs:
- kwargs={}
+ kwargs = {}
wait = dt - time.time()
if wait > 0:
self._logger.notifyChannel('timers', LOG_DEBUG, "Job scheduled in %s seconds for %s.%s" % (wait, fn.im_class.__name__, fn.func_name))
@@ -201,42 +206,49 @@ class Agent(object):
def quit(cls):
for timer in cls._timers:
timer.cancel()
- quit=classmethod(quit)
+ quit = classmethod(quit)
+
class RpcGateway(object):
def __init__(self, name):
- self.name=name
+ self.name = name
+
class Dispatcher(object):
def __init__(self):
pass
- def monitor(self,signal):
+
+ def monitor(self, signal):
pass
+
def run(self):
pass
+
class xmlrpc(object):
class RpcGateway(object):
def __init__(self, name):
- self.name=name
+ self.name = name
+
class GenericXMLRPCRequestHandler:
def _dispatch(self, method, params):
+ #print 'TERP-CALL : ',method, params
import traceback
try:
- n=self.path.split("/")[-1]
- s=LocalService(n)
- m=getattr(s,method)
- s._service._response=None
- r=m(*params)
- res=s._service._response
- if res!=None:
- r=res
+ n = self.path.split("/")[-1]
+ s = LocalService(n)
+ m = getattr(s, method)
+ s._service._response = None
+ r = m(*params)
+ res = s._service._response
+ if res != None:
+ r = res
return r
except Exception, e:
tb_s = reduce(lambda x, y: x+y, traceback.format_exception(
sys.exc_type, sys.exc_value, sys.exc_traceback))
- s=str(e)
+ s = str(e)
import tools
if tools.config['debug_mode']:
import pdb
@@ -244,10 +256,12 @@ class GenericXMLRPCRequestHandler:
pdb.post_mortem(tb)
raise xmlrpclib.Fault(s, tb_s)
+
class SimpleXMLRPCRequestHandler(GenericXMLRPCRequestHandler,
SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.rpc_paths = get_rpc_paths()
+
class SimpleThreadedXMLRPCServer(SocketServer.ThreadingMixIn,
SimpleXMLRPCServer.SimpleXMLRPCServer):
@@ -256,12 +270,13 @@ class SimpleThreadedXMLRPCServer(SocketServer.ThreadingMixIn,
socket.SO_REUSEADDR, 1)
SimpleXMLRPCServer.SimpleXMLRPCServer.server_bind(self)
+
class HttpDaemon(threading.Thread):
- def __init__(self, interface,port, secure=False):
+ def __init__(self, interface, port, secure=False):
threading.Thread.__init__(self)
- self.__port=port
- self.__interface=interface
+ self.__port = port
+ self.__interface = interface
self.secure = secure
if secure:
from ssl import SecureXMLRPCServer
@@ -277,17 +292,17 @@ class HttpDaemon(threading.Thread):
SecureXMLRPCServer.SecureXMLRPCServer.server_bind(self)
self.server = SecureThreadedXMLRPCServer((interface, port),
- SecureXMLRPCRequestHandler,0)
+ SecureXMLRPCRequestHandler, 0)
else:
self.server = SimpleThreadedXMLRPCServer((interface, port),
- SimpleXMLRPCRequestHandler,0)
+ SimpleXMLRPCRequestHandler, 0)
- def attach(self,path,gw):
+ def attach(self, path, gw):
pass
def stop(self):
self.running = False
- if os.name <> 'nt':
+ if os.name != 'nt':
if hasattr(socket, 'SHUT_RDWR'):
if self.secure:
self.server.socket.sock_shutdown(socket.SHUT_RDWR)
@@ -342,17 +357,17 @@ class TinySocketClientThread(threading.Thread):
self.threads.remove(self)
return False
try:
- s=LocalService(msg[0])
- m=getattr(s,msg[1])
- s._service._response=None
- r=m(*msg[2:])
- res=s._service._response
- if res!=None:
- r=res
+ s = LocalService(msg[0])
+ m = getattr(s, msg[1])
+ s._service._response = None
+ r = m(*msg[2:])
+ res = s._service._response
+ if res != None:
+ r = res
ts.mysend(r)
except Exception, e:
tb_s = reduce(lambda x, y: x+y, traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))
- s=str(e)
+ s = str(e)
import tools
if tools.config['debug_mode']:
import pdb
@@ -368,11 +383,12 @@ class TinySocketClientThread(threading.Thread):
def stop(self):
self.running = False
+
class TinySocketServerThread(threading.Thread):
def __init__(self, interface, port, secure=False):
threading.Thread.__init__(self)
- self.__port=port
- self.__interface=interface
+ self.__port = port
+ self.__interface = interface
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind((self.__interface, self.__port))
@@ -394,7 +410,7 @@ class TinySocketServerThread(threading.Thread):
return False
def stop(self):
- self.running=False
+ self.running = False
for t in self.threads:
t.stop()
try:
diff --git a/bin/osv/expression.py b/bin/osv/expression.py
index 63c6918e04c..9855a4a5d91 100644
--- a/bin/osv/expression.py
+++ b/bin/osv/expression.py
@@ -1,64 +1,28 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
-from tools import flatten
+from tools import flatten, reverse_enumerate
+
class expression(object):
"""
parse a domain expression
- examples:
-
- >>> e = [('foo', '=', 'bar')]
- >>> expression(e).parse().to_sql()
- 'foo = bar'
- >>> e = [('id', 'in', [1,2,3])]
- >>> expression(e).parse().to_sql()
- 'id in (1, 2, 3)'
- >>> e = [('field', '=', 'value'), ('field', '<>', 'value')]
- >>> expression(e).parse().to_sql()
- '( field = value AND field <> value )'
- >>> e = [('&', ('field', '<', 'value'), ('field', '>', 'value'))]
- >>> expression(e).parse().to_sql()
- '( field < value AND field > value )'
- >>> e = [('|', ('field', '=', 'value'), ('field', '=', 'value'))]
- >>> expression(e).parse().to_sql()
- '( field = value OR field = value )'
- >>> e = [('&', ('field1', '=', 'value'), ('field2', '=', 'value'), ('|', ('field3', '<>', 'value'), ('field4', '=', 'value')))]
- >>> expression(e).parse().to_sql()
- '( field1 = value AND field2 = value AND ( field3 <> value OR field4 = value ) )'
- >>> e = [('&', ('|', ('a', '=', '1'), ('b', '=', '2')), ('|', ('c', '=', '3'), ('d', '=', '4')))]
- >>> expression(e).parse().to_sql()
- '( ( a = 1 OR b = 2 ) AND ( c = 3 OR d = 4 ) )'
- >>> e = [('|', (('a', '=', '1'), ('b', '=', '2')), (('c', '=', '3'), ('d', '=', '4')))]
- >>> expression(e).parse().to_sql()
- '( ( a = 1 AND b = 2 ) OR ( c = 3 AND d = 4 ) )'
- >>> expression(e).parse().get_tables()
- []
- >>> expression('fail').parse().to_sql()
- Traceback (most recent call last):
- ...
- ValueError: Bad expression: 'fail'
- >>> e = [('fail', 'is', 'True')]
- >>> expression(e).parse().to_sql()
- Traceback (most recent call last):
- ...
- ValueError: Bad expression: ('&', ('fail', 'is', 'True'))
+ use a real polish notation
+ leafs are still in a ('foo', '=', 'bar') format
+ For more info: http://christophe-simonis-at-tiny.blogspot.com/2008/08/new-new-domain-notation.html
"""
def _is_operator(self, element):
return isinstance(element, str) \
- and element in ['&','|']
+ and element in ['&', '|', '!']
- def _is_leaf(self, element):
- return isinstance(element, tuple) \
+ def _is_leaf(self, element, internal=False):
+ OPS = ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of')
+ INTERNAL_OPS = OPS + ('inselect',)
+ return (isinstance(element, tuple) or isinstance(element, list)) \
and len(element) == 3 \
- and element[1] in ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of')
-
- def _is_expression(self, element):
- return isinstance(element, tuple) \
- and len(element) > 2 \
- and self._is_operator(element[0])
-
+ and (((not internal) and element[1] in OPS) \
+ or (internal and element[1] in INTERNAL_OPS))
def __execute_recursive_in(self, cr, s, f, w, ids):
res = []
@@ -73,271 +37,264 @@ class expression(object):
def __init__(self, exp):
- if exp and isinstance(exp, tuple):
- if not self._is_leaf(exp) and not self._is_operator(exp[0]):
- exp = list(exp)
- if exp and isinstance(exp, list):
- if len(exp) == 1 and self._is_leaf(exp[0]):
- exp = exp[0]
- else:
- if not self._is_operator(exp[0][0]):
- if isinstance(exp[0],list):
- exp=tuple(exp[0])
- else:
- exp.insert(0, '&')
- exp = tuple(exp)
- else:
- exp = exp[0]
-
+ # check if the expression is valid
+ if not reduce(lambda acc, val: acc and (self._is_operator(val) or self._is_leaf(val)), exp, True):
+ raise ValueError('Bad domain expression: %r' % (exp,))
self.__exp = exp
- self.__operator = '&'
- self.__children = []
-
- self.__tables = []
+ self.__tables = {} # used to store the table to use for the sql generation. key = index of the leaf
self.__joins = []
- self.__table = None
+ self.__main_table = None # 'root' table. set by parse()
+ self.__DUMMY_LEAF = (1, '=', 1) # a dummy leaf that must not be parsed or sql generated
- self.__left, self.__right = None, None
- if self._is_leaf(self.__exp):
- self.__left, self.__operator, self.__right = self.__exp
- if isinstance(self.__right, list):
- self.__right = tuple(self.__right)
- elif exp and not self._is_expression(self.__exp):
- raise ValueError, 'Bad expression: %r' % (self.__exp,)
def parse(self, cr, uid, table, context):
-
- def _rec_get(ids, table, parent):
- if not ids:
- return []
- ids2 = table.search(cr, uid, [(parent, 'in', ids)], context=context)
- return ids + _rec_get(ids2, table, parent)
-
+ """ transform the leafs of the expression """
if not self.__exp:
return self
- if self._is_leaf(self.__exp):
- self.__table = table
- self.__tables.append(self.__table._table)
- if self.__left in table._inherit_fields:
- self.__table = table.pool.get(table._inherit_fields[self.__left][0])
- if self.__table._table not in self.__tables:
- self.__tables.append(self.__table._table)
- self.__joins.append('%s.%s' % (table._table, table._inherits[self.__table._name]))
- fargs = self.__left.split('.', 1)
- field = self.__table._columns.get(fargs[0], False)
+ def _rec_get(ids, table, parent, left='id', prefix=''):
+ if table._parent_store and (not table.pool._init):
+# TODO: Improve where joins are implemented for many with '.', replace by:
+# doms += ['&',(prefix+'.parent_left','<',o.parent_right),(prefix+'.parent_left','>=',o.parent_left)]
+ doms = []
+ for o in table.browse(cr, uid, ids, context=context):
+ if doms:
+ doms.insert(0,'|')
+ doms += ['&',('parent_left','<',o.parent_right),('parent_left','>=',o.parent_left)]
+ if prefix:
+ return [(left, 'in', table.search(cr, uid, doms, context=context))]
+ return doms
+ else:
+ def rg(ids, table, parent):
+ if not ids:
+ return []
+ ids2 = table.search(cr, uid, [(parent, 'in', ids)], context=context)
+ return ids+rg(ids2, table, parent)
+ return [(prefix+left, 'in', rg(ids2, table, parent))]
+
+ self.__main_table = table
+
+ i = -1
+ while i+1 1:
- if field._type == 'many2one':
- self.__left = fargs[0]
- self.__right = table.pool.get(field._obj).search(cr, uid, [(fargs[1], self.__operator, self.__right)], context=context)
- self.__operator = 'in'
- return self
+ if left == 'id' and operator == 'child_of':
+ dom = _rec_get(right, working_table, working_table._parent_name)
+ self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]
+ continue
field_obj = table.pool.get(field._obj)
+ if len(fargs) > 1:
+ if field._type == 'many2one':
+ right = field_obj.search(cr, uid, [(fargs[1], operator, right)], context=context)
+ self.__exp[i] = (fargs[0], 'in', right)
+ continue
+
if field._properties:
# this is a function field
- if not field._fnct_search and not field.store:
- # the function field doesn't provide a search function and doesn't store values in the database, so we must ignore it : we generate a dummy leaf
- self.__left, self.__operator, self.__right = 1, '=', 1
- self.__exp = '' # force to generate an empty sql expression
- else:
- # we need to replace this leaf to a '&' expression
- # we clone ourself...
- import copy
- newexp = copy.copy(self)
- self.__table = None
- self.__tables, self.__joins = [], []
- self.__children = []
+ if not field.store:
+ if not field._fnct_search:
+ # the function field doesn't provide a search function and doesn't store
+ # values in the database, so we must ignore it : we generate a dummy leaf
+ self.__exp[i] = self.__DUMMY_LEAF
+ else:
+ subexp = field.search(cr, uid, table, left, [self.__exp[i]])
+ # we assume that the expression is valid
+ # we create a dummy leaf for forcing the parsing of the resulting expression
+ self.__exp[i] = '&'
+ self.__exp.insert(i + 1, self.__DUMMY_LEAF)
+ for j, se in enumerate(subexp):
+ self.__exp.insert(i + 2 + j, se)
+
+ # else, the value of the field is store in the database, so we search on it
- if field._fnct_search:
- subexp = field.search(cr, uid, table, self.__left, [self.__exp])
- self.__children.append(expression(subexp).parse(cr, uid, table, context))
- if field.store:
- self.__children.append(newexp)
-
- self.__left, self.__right = None, None
- self.__operator = '&'
- self.__exp = ('&',) + tuple( [tuple(e.__exp) for e in self.__children] )
elif field._type == 'one2many':
- if isinstance(self.__right, basestring):
- ids2 = [x[0] for x in field_obj.name_search(cr, uid, self.__right, [], self.__operator)]
+ if isinstance(right, basestring):
+ ids2 = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator)]
else:
- ids2 = self.__right
+ ids2 = list(right)
if not ids2:
- self.__left, self.__operator, self.__right = 'id', '=', '0'
+ self.__exp[i] = ('id', '=', '0')
else:
- self.__left, self.__operator, self.__right = 'id', 'in', self.__execute_recursive_in(cr, field._fields_id, field_obj._table, 'id', ids2)
+ self.__exp[i] = ('id', 'in', self.__execute_recursive_in(cr, field._fields_id, field_obj._table, 'id', ids2))
elif field._type == 'many2many':
#FIXME
- if self.__operator == 'child_of':
- if isinstance(self.__right, basestring):
- ids2 = [x[0] for x in field_obj.name_search(cr, uid, self.__right, [], 'like')]
+ if operator == 'child_of':
+ if isinstance(right, basestring):
+ ids2 = [x[0] for x in field_obj.name_search(cr, uid, right, [], 'like')]
else:
- ids2 = self.__right
+ ids2 = list(right)
def _rec_convert(ids):
if field_obj == table:
return ids
return self.__execute_recursive_in(cr, field._id1, field._rel, field._id2, ids)
-
- self.__left, self.__operator, self.__right = 'id', 'in', _rec_convert(ids2 + _rec_get(ids2, field_obj, self.__table._parent_name))
+
+ dom = _rec_get(ids2, field_obj, working_table._parent_name)
+ ids2 = field_obj.search(cr, uid, doms, context=context)
+ self.__exp[i] = ('id', 'in', _rec_convert(ids2))
else:
- if isinstance(self.__right, basestring):
- res_ids = [x[0] for x in field_obj.name_search(cr, uid, self.__right, [], self.__operator)]
+ if isinstance(right, basestring):
+ res_ids = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator)]
else:
- res_ids = self.__right
- self.__left, self.__operator, self.__right = 'id', 'in', self.__execute_recursive_in(cr, field._id1, field._rel, field._id2, res_ids) or [0]
+ res_ids = list(right)
+ self.__exp[i] = ('id', 'in', self.__execute_recursive_in(cr, field._id1, field._rel, field._id2, res_ids) or [0])
elif field._type == 'many2one':
- if self.__operator == 'child_of':
- if isinstance(self.__right, basestring):
- ids2 = [x[0] for x in field_obj.search_name(cr, uid, self.__right, [], 'like')]
+ if operator == 'child_of':
+ if isinstance(right, basestring):
+ ids2 = [x[0] for x in field_obj.search_name(cr, uid, right, [], 'like')]
else:
- ids2 = list(self.__right)
+ ids2 = list(right)
self.__operator = 'in'
- if field._obj <> self.__table._name:
- self.__right = ids2 + _rec_get(ids2, field_obj, self.__table._parent_name)
+ if field._obj != working_table._name:
+ dom = _rec_get(ids2, field_obj, working_table._parent_name, left=left)
else:
- self.__right = ids2 + _rec_get(ids2, self.__table, self.__left)
- self.__left = 'id'
+ dom = _rec_get(ids2, working_table, left)
+ self.__exp = self.__exp[:i] + dom + self.__exp[i+1:]
else:
- if isinstance(self.__right, basestring):
- res_ids = field_obj.name_search(cr, uid, self.__right, [], self.__operator)
- self.__operator = 'in'
- self.__right = map(lambda x: x[0], res_ids)
+ if isinstance(right, basestring):
+ res_ids = field_obj.name_search(cr, uid, right, [], operator)
+ right = map(lambda x: x[0], res_ids)
+ self.__exp[i] = (left, 'in', right)
else:
# other field type
if field.translate:
- if self.__operator in ('like', 'ilike', 'not like', 'not ilike'):
- self.__right = '%%%s%%' % self.__right
+ if operator in ('like', 'ilike', 'not like', 'not ilike'):
+ right = '%%%s%%' % right
query1 = '( SELECT res_id' \
' FROM ir_translation' \
' WHERE name = %s' \
' AND lang = %s' \
' AND type = %s' \
- ' AND value ' + self.__operator + ' %s' \
+ ' AND value ' + operator + ' %s' \
') UNION (' \
' SELECT id' \
- ' FROM "' + self.__table._table + '"' \
- ' WHERE "' + self.__left + '" ' + self.__operator + ' %s' \
+ ' FROM "' + working_table._table + '"' \
+ ' WHERE "' + left + '" ' + operator + ' %s' \
')'
- query2 = [self.__table._name + ',' + self.__left,
+ query2 = [working_table._name + ',' + left,
context.get('lang', False) or 'en_US',
'model',
- self.__right,
- self.__right,
+ right,
+ right,
]
- self.__left = 'id'
- self.__operator = 'inselect'
- self.__right = (query1, query2,)
+ self.__exp[i] = ('id', 'inselect', (query1, query2))
-
- elif self._is_expression(self.__exp):
- self.__operator = self.__exp[0]
-
- for element in self.__exp[1:]:
- if not self._is_operator(element):
- self.__children.append(expression(element).parse(cr, uid, table, context))
return self
- def to_sql(self):
- if not self.__exp:
- return ('', [])
- elif self._is_leaf(self.__exp):
- if self.__operator == 'inselect':
- query = '(%s.%s in (%s))' % (self.__table._table, self.__left, self.__right[0])
- params = self.__right[1]
- elif self.__operator in ['in', 'not in']:
- params = self.__right[:]
- len_before = len(params)
- for i in range(len_before)[::-1]:
- if params[i] == False:
- del params[i]
+ def __leaf_to_sql(self, leaf, table):
+ left, operator, right = leaf
- len_after = len(params)
- check_nulls = len_after <> len_before
- query = '(1=0)'
+ if operator == 'inselect':
+ query = '(%s.%s in (%s))' % (table._table, left, right[0])
+ params = right[1]
+ elif operator in ['in', 'not in']:
+ params = right[:]
+ len_before = len(params)
+ for i in range(len_before)[::-1]:
+ if params[i] == False:
+ del params[i]
- if len_after:
- if self.__left == 'id':
- instr = ','.join(['%d'] * len_after)
- else:
- instr = ','.join([self.__table._columns[self.__left]._symbol_set[0]] * len_after)
+ len_after = len(params)
+ check_nulls = len_after != len_before
+ query = '(1=0)'
- query = '(%s.%s %s (%s))' % (self.__table._table, self.__left, self.__operator, instr)
-
- if check_nulls:
- query = '(%s OR %s IS NULL)' % (query, self.__left)
- else:
- params = []
- if self.__right is False and self.__operator == '=':
- query = '%s IS NULL' % self.__left
- elif self.__right is False and self.__operator == '<>':
- query = '%s IS NOT NULL' % self.__left
+ if len_after:
+ if left == 'id':
+ instr = ','.join(['%d'] * len_after)
else:
- if self.__left == 'id':
- query = '%s.id %s %%s' % (self.__table._table, self.__operator)
- params = self.__right
- else:
- like = self.__operator in ('like', 'ilike', 'not like', 'not ilike')
-
- op = self.__operator == '=like' and 'like' or self.__operator
- if self.__left in self.__table._columns:
- format = like and '%s' or self.__table._columns[self.__left]._symbol_set[0]
- query = '(%s.%s %s %s)' % (self.__table._table, self.__left, op, format)
- else:
- query = "(%s.%s %s '%s')" % (self.__table._table, self.__left, op, self.__right)
-
- add_null = False
- if like:
- if isinstance(self.__right, str):
- str_utf8 = self.__right
- elif isinstance(self.__right, unicode):
- str_utf8 = self.__right.encode('utf-8')
- else:
- str_utf8 = str(self.__right)
- params = '%%%s%%' % str_utf8
- add_null = not str_utf8
- elif self.__left in self.__table._columns:
- params = self.__table._columns[self.__left]._symbol_set[1](self.__right)
-
- if add_null:
- query = '(%s OR %s IS NULL)' % (query, self.__left)
-
- joins = ' AND '.join(map(lambda j: '%s.id = %s' % (self.__table._table, j), self.__joins))
- if joins:
- query = '(%s AND (%s))' % (joins, query)
- if isinstance(params, basestring):
- params = [params]
- return (query, params)
+ instr = ','.join([table._columns[left]._symbol_set[0]] * len_after)
+ query = '(%s.%s %s (%s))' % (table._table, left, operator, instr)
+ if check_nulls:
+ query = '(%s OR %s IS NULL)' % (query, left)
else:
- children = [child.to_sql() for child in self.__children]
- params = flatten([child[1] for child in children])
- query = "( %s )" % (" %s " % {'&' : 'AND', '|' : 'OR' }[self.__operator]).join([child[0] for child in children if child[0]])
- return (query, params)
+ params = []
+ if right is False and operator == '=':
+ query = '%s IS NULL' % left
+ elif right is False and operator in ['<>', '!=']:
+ query = '%s IS NOT NULL' % left
+ else:
+ if left == 'id':
+ query = '%s.id %s %%s' % (table._table, operator)
+ params = right
+ else:
+ like = operator in ('like', 'ilike', 'not like', 'not ilike')
- def __get_tables(self):
- return self.__tables + [child.__get_tables() for child in self.__children]
+ op = operator == '=like' and 'like' or operator
+ if left in table._columns:
+ format = like and '%s' or table._columns[left]._symbol_set[0]
+ query = '(%s.%s %s %s)' % (table._table, left, op, format)
+ else:
+ query = "(%s.%s %s '%s')" % (table._table, left, op, right)
+
+ add_null = False
+ if like:
+ if isinstance(right, str):
+ str_utf8 = right
+ elif isinstance(right, unicode):
+ str_utf8 = right.encode('utf-8')
+ else:
+ str_utf8 = str(right)
+ params = '%%%s%%' % str_utf8
+ add_null = not str_utf8
+ elif left in table._columns:
+ params = table._columns[left]._symbol_set[1](right)
+
+ if add_null:
+ query = '(%s OR %s IS NULL)' % (query, left)
+
+ if isinstance(params, basestring):
+ params = [params]
+ return (query, params)
+
+
+ def to_sql(self):
+ stack = []
+ params = []
+ for i, e in reverse_enumerate(self.__exp):
+ if self._is_leaf(e, internal=True):
+ table = self.__tables.get(i, self.__main_table)
+ q, p = self.__leaf_to_sql(e, table)
+ params.insert(0, p)
+ stack.append(q)
+ else:
+ if e == '!':
+ stack.append('(NOT (%s))' % (stack.pop(),))
+ else:
+ ops = {'&': ' AND ', '|': ' OR '}
+ q1 = stack.pop()
+ q2 = stack.pop()
+ stack.append('(%s %s %s)' % (q1, ops[e], q2,))
+
+ query = ' AND '.join(reversed(stack))
+ joins = ' AND '.join(map(lambda j: j[0], self.__joins))
+ if joins:
+ query = '(%s) AND (%s)' % (joins, query)
+ return (query, flatten(params))
def get_tables(self):
- return [ '"%s"' % t for t in set(flatten(self.__get_tables()))]
-
- #def
-
-if __name__ == '__main__':
- pass
- #import doctest
- #doctest.testmod()
+ return ['"%s"' % t._table for t in set(self.__tables.values()+[self.__main_table])]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/bin/osv/fields.py b/bin/osv/fields.py
index 6983fa2292b..959e9f109f4 100644
--- a/bin/osv/fields.py
+++ b/bin/osv/fields.py
@@ -1,5 +1,4 @@
# -*- encoding: utf-8 -*-
-# -*- encoding: iso-8859-1 -*-
##############################################################################
#
# Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved.
@@ -49,19 +48,22 @@ import warnings
import tools
+
def _symbol_set(symb):
- if symb==None or symb==False:
+ if symb == None or symb == False:
return None
elif isinstance(symb, unicode):
return symb.encode('utf-8')
return str(symb)
+
class _column(object):
_classic_read = True
_classic_write = True
_properties = False
_type = 'unknown'
_obj = None
+ _multi = False
_symbol_c = '%s'
_symbol_f = _symbol_set
_symbol_set = (_symbol_c, _symbol_f)
@@ -79,13 +81,12 @@ class _column(object):
self.ondelete = ondelete
self.translate = translate
self._domain = domain or []
- self.relate =False
+ self.relate = False
self._context = context
- self.group_name = False
self.write = False
self.read = False
self.view_load = 0
- self.select=select
+ self.select = select
for a in args:
if args[a]:
setattr(self, a, args[a])
@@ -96,21 +97,25 @@ class _column(object):
pass
def set(self, cr, obj, id, name, value, user=None, context=None):
- cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%d', (self._symbol_set[1](value),id) )
+ cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%d', (self._symbol_set[1](value), id))
+
def set_memory(self, cr, obj, id, name, value, user=None, context=None):
- raise Exception, _('Not implemented set_memory method !')
+ raise Exception(_('Not implemented set_memory method !'))
def get_memory(self, cr, obj, ids, name, context=None, values=None):
- raise Exception, _('Not implemented get_memory method !')
+ raise Exception(_('Not implemented get_memory method !'))
+
def get(self, cr, obj, ids, name, context=None, values=None):
- raise Exception, _('undefined get method !')
+ raise Exception(_('undefined get method !'))
def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None):
- ids = obj.search(cr, uid, args+self._domain+[(name,'ilike',value)], offset, limit)
+ ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit)
res = obj.read(cr, uid, ids, [name])
return [x[name] for x in res]
+
def search_memory(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None):
- raise Exception, _('Not implemented search_memory method !')
+ raise Exception(_('Not implemented search_memory method !'))
+
# ---------------------------------------------------------
# Simple fields
@@ -121,34 +126,38 @@ class boolean(_column):
_symbol_f = lambda x: x and 'True' or 'False'
_symbol_set = (_symbol_c, _symbol_f)
+
class integer(_column):
_type = 'integer'
_symbol_c = '%d'
_symbol_f = lambda x: int(x or 0)
_symbol_set = (_symbol_c, _symbol_f)
+
class reference(_column):
_type = 'reference'
+
def __init__(self, string, selection, size, **args):
_column.__init__(self, string=string, size=size, selection=selection, **args)
+
class char(_column):
_type = 'char'
-
+
def __init__(self, string, size, **args):
_column.__init__(self, string=string, size=size, **args)
self._symbol_set = (self._symbol_c, self._symbol_set_char)
-
+
# takes a string (encoded in utf8) and returns a string (encoded in utf8)
def _symbol_set_char(self, symb):
- #TODO:
- # * we need to remove the "symb==False" from the next line BUT
+ #TODO:
+ # * we need to remove the "symb==False" from the next line BUT
# for now too many things rely on this broken behavior
# * the symb==None test should be common to all data types
- if symb==None or symb==False:
+ if symb == None or symb == False:
return None
- # we need to convert the string to a unicode object to be able
+ # we need to convert the string to a unicode object to be able
# to evaluate its length (and possibly truncate it) reliably
if isinstance(symb, str):
u_symb = unicode(symb, 'utf8')
@@ -158,38 +167,46 @@ class char(_column):
u_symb = unicode(symb)
return u_symb.encode('utf8')[:self.size]
+
class text(_column):
_type = 'text'
import __builtin__
+
class float(_column):
_type = 'float'
_symbol_c = '%f'
_symbol_f = lambda x: __builtin__.float(x or 0.0)
_symbol_set = (_symbol_c, _symbol_f)
+
def __init__(self, string='unknown', digits=None, **args):
_column.__init__(self, string=string, **args)
self.digits = digits
+
class date(_column):
_type = 'date'
+
class datetime(_column):
_type = 'datetime'
+
class time(_column):
_type = 'time'
+
class binary(_column):
_type = 'binary'
_symbol_c = '%s'
_symbol_f = lambda symb: symb and psycopg.Binary(symb) or None
_symbol_set = (_symbol_c, _symbol_f)
+
class selection(_column):
_type = 'selection'
-
+
def __init__(self, selection, string='unknown', **args):
_column.__init__(self, string=string, **args)
self.selection = selection
@@ -211,7 +228,7 @@ class one2one(_column):
_classic_read = False
_classic_write = True
_type = 'one2one'
-
+
def __init__(self, obj, string='unknown', **args):
warnings.warn("The one2one field doesn't work anymore", DeprecationWarning)
_column.__init__(self, string=string, **args)
@@ -219,25 +236,26 @@ class one2one(_column):
def set(self, cr, obj_src, id, field, act, user=None, context=None):
if not context:
- context={}
+ context = {}
obj = obj_src.pool.get(self._obj)
self._table = obj_src.pool.get(self._obj)._table
- if act[0]==0:
+ if act[0] == 0:
id_new = obj.create(cr, user, act[1])
- cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (id_new,id))
+ cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (id_new, id))
else:
cr.execute('select '+field+' from '+obj_src._table+' where id=%d', (act[0],))
- id =cr.fetchone()[0]
- obj.write(cr, user, [id] , act[1], context=context)
+ id = cr.fetchone()[0]
+ obj.write(cr, user, [id], act[1], context=context)
def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None):
- return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name','like',value)], offset, limit)
+ return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit)
+
class many2one(_column):
_classic_read = False
_classic_write = True
_type = 'many2one'
-
+
def __init__(self, obj, string='unknown', **args):
_column.__init__(self, string=string, **args)
self._obj = obj
@@ -254,9 +272,9 @@ class many2one(_column):
def get(self, cr, obj, ids, name, user=None, context=None, values=None):
if not context:
- context={}
+ context = {}
if not values:
- values={}
+ values = {}
res = {}
for r in values:
res[r['id']] = r[name]
@@ -268,10 +286,10 @@ class many2one(_column):
try:
names = dict(obj.name_get(cr, user, filter(None, res.values()), context))
except except_orm:
- names={}
+ names = {}
iids = filter(None, res.values())
- cr.execute('select id,'+obj._rec_name+' from '+obj._table+' where id in ('+','.join(map(str,iids))+')')
+ cr.execute('select id,'+obj._rec_name+' from '+obj._table+' where id in ('+','.join(map(str, iids))+')')
for res22 in cr.fetchall():
names[res22[0]] = res22[1]
@@ -284,36 +302,37 @@ class many2one(_column):
def set(self, cr, obj_src, id, field, values, user=None, context=None):
if not context:
- context={}
+ context = {}
obj = obj_src.pool.get(self._obj)
self._table = obj_src.pool.get(self._obj)._table
if type(values)==type([]):
for act in values:
- if act[0]==0:
+ if act[0] == 0:
id_new = obj.create(cr, act[2])
- cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (id_new,id))
- elif act[0]==1:
+ cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (id_new, id))
+ elif act[0] == 1:
obj.write(cr, [act[1]], act[2], context=context)
- elif act[0]==2:
+ elif act[0] == 2:
cr.execute('delete from '+self._table+' where id=%d', (act[1],))
- elif act[0]==3 or act[0]==5:
+ elif act[0] == 3 or act[0] == 5:
cr.execute('update '+obj_src._table+' set '+field+'=null where id=%d', (id,))
- elif act[0]==4:
- cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (act[1],id))
+ elif act[0] == 4:
+ cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (act[1], id))
else:
if values:
- cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (values,id))
+ cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (values, id))
else:
cr.execute('update '+obj_src._table+' set '+field+'=null where id=%d', (id,))
def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None):
- return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name','like',value)], offset, limit)
+ return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit)
+
class one2many(_column):
_classic_read = False
_classic_write = False
_type = 'one2many'
-
+
def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
_column.__init__(self, string=string, **args)
self._obj = obj
@@ -330,42 +349,41 @@ class one2many(_column):
res = {}
for id in ids:
res[id] = []
- ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id,'in',ids)], limit=self._limit)
+ ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id, 'in', ids)], limit=self._limit)
for r in obj.pool.get(self._obj).read(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
if r[self._fields_id] in res:
- res[r[self._fields_id]].append( r['id'] )
+ res[r[self._fields_id]].append(r['id'])
return res
def set_memory(self, cr, obj, id, field, values, user=None, context=None):
if not context:
- context={}
+ context = {}
if not values:
return
obj = obj.pool.get(self._obj)
for act in values:
- if act[0]==0:
+ if act[0] == 0:
act[2][self._fields_id] = id
obj.create(cr, user, act[2], context=context)
- elif act[0]==1:
- obj.write(cr, user, [act[1]] , act[2], context=context)
- elif act[0]==2:
+ elif act[0] == 1:
+ obj.write(cr, user, [act[1]], act[2], context=context)
+ elif act[0] == 2:
obj.unlink(cr, user, [act[1]], context=context)
- elif act[0]==3:
+ elif act[0] == 3:
obj.datas[act[1]][self._fields_id] = False
- elif act[0]==4:
+ elif act[0] == 4:
obj.datas[act[1]] = id
- elif act[0]==5:
+ elif act[0] == 5:
for o in obj.datas.values():
- if o[self._fields_id]==id:
+ if o[self._fields_id] == id:
o[self._fields_id] = False
- elif act[0]==6:
+ elif act[0] == 6:
for id2 in (act[2] or []):
obj.datas[id2][self._fields_id] = id
def search_memory(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
raise _('Not Implemented')
-
def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
if not context:
context = {}
@@ -374,33 +392,33 @@ class one2many(_column):
res = {}
for id in ids:
res[id] = []
- ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id,'in',ids)], limit=self._limit)
+ ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id, 'in', ids)], limit=self._limit)
for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
- res[r[self._fields_id]].append( r['id'] )
+ res[r[self._fields_id]].append(r['id'])
return res
def set(self, cr, obj, id, field, values, user=None, context=None):
if not context:
- context={}
+ context = {}
if not values:
return
_table = obj.pool.get(self._obj)._table
obj = obj.pool.get(self._obj)
for act in values:
- if act[0]==0:
+ if act[0] == 0:
act[2][self._fields_id] = id
obj.create(cr, user, act[2], context=context)
- elif act[0]==1:
- obj.write(cr, user, [act[1]] , act[2], context=context)
- elif act[0]==2:
+ elif act[0] == 1:
+ obj.write(cr, user, [act[1]], act[2], context=context)
+ elif act[0] == 2:
obj.unlink(cr, user, [act[1]], context=context)
- elif act[0]==3:
+ elif act[0] == 3:
cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%d', (act[1],))
- elif act[0]==4:
- cr.execute('update '+_table+' set '+self._fields_id+'=%d where id=%d', (id,act[1]))
- elif act[0]==5:
+ elif act[0] == 4:
+ cr.execute('update '+_table+' set '+self._fields_id+'=%d where id=%d', (id, act[1]))
+ elif act[0] == 5:
cr.execute('update '+_table+' set '+self._fields_id+'=null where '+self._fields_id+'=%d', (id,))
- elif act[0]==6:
+ elif act[0] == 6:
if not act[2]:
ids2 = [0]
else:
@@ -408,9 +426,11 @@ class one2many(_column):
cr.execute('update '+_table+' set '+self._fields_id+'=NULL where '+self._fields_id+'=%d and id not in ('+','.join(map(str, ids2))+')', (id,))
if act[2]:
cr.execute('update '+_table+' set '+self._fields_id+'=%d where id in ('+','.join(map(str, act[2]))+')', (id,))
+
def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like'):
return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, offset, limit)
+
#
# Values: (0, 0, { fields }) create
# (1, ID, { fields }) modification
@@ -424,7 +444,7 @@ class many2many(_column):
_classic_read = False
_classic_write = False
_type = 'many2many'
-
+
def __init__(self, obj, rel, id1, id2, string='unknown', limit=None, **args):
_column.__init__(self, string=string, **args)
self._obj = obj
@@ -435,15 +455,15 @@ class many2many(_column):
def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
if not context:
- context={}
+ context = {}
if not values:
- values={}
+ values = {}
res = {}
if not ids:
return res
for id in ids:
res[id] = []
- ids_s = ','.join(map(str,ids))
+ ids_s = ','.join(map(str, ids))
limit_str = self._limit is not None and ' limit %d' % self._limit or ''
obj = obj.pool.get(self._obj)
@@ -463,39 +483,39 @@ class many2many(_column):
def set(self, cr, obj, id, name, values, user=None, context=None):
if not context:
- context={}
+ context = {}
if not values:
return
obj = obj.pool.get(self._obj)
for act in values:
- if act[0]==0:
+ if act[0] == 0:
idnew = obj.create(cr, user, act[2])
- cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%d,%d)', (id,idnew))
- elif act[0]==1:
- obj.write(cr, user, [act[1]] , act[2], context=context)
- elif act[0]==2:
+ cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%d,%d)', (id, idnew))
+ elif act[0] == 1:
+ obj.write(cr, user, [act[1]], act[2], context=context)
+ elif act[0] == 2:
obj.unlink(cr, user, [act[1]], context=context)
- elif act[0]==3:
- cr.execute('delete from '+self._rel+' where ' + self._id1 + '=%d and '+ self._id2 + '=%d', (id,act[1]))
- elif act[0]==4:
- cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%d,%d)', (id,act[1]))
- elif act[0]==5:
+ elif act[0] == 3:
+ cr.execute('delete from '+self._rel+' where ' + self._id1 + '=%d and '+ self._id2 + '=%d', (id, act[1]))
+ elif act[0] == 4:
+ cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%d,%d)', (id, act[1]))
+ elif act[0] == 5:
cr.execute('update '+self._rel+' set '+self._id2+'=null where '+self._id2+'=%d', (id,))
- elif act[0]==6:
+ elif act[0] == 6:
- d1, d2 = obj.pool.get('ir.rule').domain_get(cr, user, obj._name)
+ d1, d2 = obj.pool.get('ir.rule').domain_get(cr, user, obj._name)
if d1:
d1 = ' and '+d1
- cr.execute('delete from '+self._rel+' where '+self._id1+'=%d AND '+self._id2+' IN (SELECT '+self._rel+'.'+self._id2+' FROM '+self._rel+', '+obj._table+' WHERE '+self._rel+'.'+self._id1+'=%d AND '+self._rel+'.'+self._id2+' = '+obj._table+'.id '+ d1 +')', [id, id]+d2 )
+ cr.execute('delete from '+self._rel+' where '+self._id1+'=%d AND '+self._id2+' IN (SELECT '+self._rel+'.'+self._id2+' FROM '+self._rel+', '+obj._table+' WHERE '+self._rel+'.'+self._id1+'=%d AND '+self._rel+'.'+self._id2+' = '+obj._table+'.id '+ d1 +')', [id, id]+d2)
- for act_nbr in act[2]:
+ for act_nbr in act[2]:
cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%d, %d)', (id, act_nbr))
#
# TODO: use a name_search
#
def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like'):
- return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name',operator,value)], offset, limit)
+ return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit)
def get_memory(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
result = {}
@@ -508,39 +528,42 @@ class many2many(_column):
return
for act in values:
# TODO: use constants instead of these magic numbers
- if act[0]==0:
+ if act[0] == 0:
raise _('Not Implemented')
- elif act[0]==1:
+ elif act[0] == 1:
raise _('Not Implemented')
- elif act[0]==2:
+ elif act[0] == 2:
raise _('Not Implemented')
- elif act[0]==3:
+ elif act[0] == 3:
raise _('Not Implemented')
- elif act[0]==4:
+ elif act[0] == 4:
raise _('Not Implemented')
- elif act[0]==5:
+ elif act[0] == 5:
raise _('Not Implemented')
- elif act[0]==6:
+ elif act[0] == 6:
obj.datas[id][name] = act[2]
# ---------------------------------------------------------
# Function fields
# ---------------------------------------------------------
-
class function(_column):
_classic_read = False
_classic_write = False
_type = 'function'
_properties = True
-
- def __init__(self, fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float', fnct_search=None, obj=None, method=False, store=False, **args):
+
+#
+# multi: compute several fields in one call
+#
+ def __init__(self, fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float', fnct_search=None, obj=None, method=False, store=False, multi=False, **args):
_column.__init__(self, **args)
self._obj = obj
self._method = method
self._fnct = fnct
self._fnct_inv = fnct_inv
self._arg = arg
+ self._multi = multi
if 'relation' in args:
self._obj = args['relation']
self._fnct_inv_arg = fnct_inv_arg
@@ -553,7 +576,7 @@ class function(_column):
self._symbol_c = '%f'
self._symbol_f = lambda x: __builtin__.float(x or 0.0)
self._symbol_set = (self._symbol_c, self._symbol_f)
-
+
def search(self, cr, uid, obj, name, args):
if not self._fnct_search:
#CHECKME: should raise an exception
@@ -562,23 +585,22 @@ class function(_column):
def get(self, cr, obj, ids, name, user=None, context=None, values=None):
if not context:
- context={}
+ context = {}
if not values:
- values={}
+ values = {}
res = {}
- table = obj._table
if self._method:
- # TODO get HAS to receive uid for permissions !
return self._fnct(obj, cr, user, ids, name, self._arg, context)
else:
- return self._fnct(cr, table, ids, name, self._arg, context)
+ return self._fnct(cr, obj._table, ids, name, self._arg, context)
def set(self, cr, obj, id, name, value, user=None, context=None):
if not context:
- context={}
+ context = {}
if self._fnct_inv:
self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
+
# ---------------------------------------------------------
# Serialized fields
# ---------------------------------------------------------
@@ -596,7 +618,7 @@ class property(function):
def _fnct_write(self, obj, cr, uid, id, prop, id_val, val, context=None):
if not context:
- context={}
+ context = {}
(obj_dest,) = val
definition_id = self._field_get(cr, uid, obj._name, prop)
@@ -629,7 +651,7 @@ class property(function):
def _fnct_read(self, obj, cr, uid, ids, prop, val, context=None):
if not context:
- context={}
+ context = {}
property = obj.pool.get('ir.property')
definition_id = self._field_get(cr, uid, obj._name, prop)
@@ -646,7 +668,7 @@ class property(function):
res = {}
for id in ids:
- res[id]= default_val
+ res[id] = default_val
for prop in property.browse(cr, uid, nids):
res[int(prop.res_id.split(',')[1])] = (prop.value and \
int(prop.value.split(',')[1])) or False
diff --git a/bin/osv/orm.py b/bin/osv/orm.py
index d064078b9c7..5f4ac7a097f 100644
--- a/bin/osv/orm.py
+++ b/bin/osv/orm.py
@@ -50,21 +50,20 @@
import time
import types
from xml import dom, xpath
-from xml.parsers import expat
import string
-import pooler
-import psycopg
import netsvc
import re
+import pickle
+
import fields
-import ir
import tools
from tools.config import config
regex_order = re.compile('^([a-zA-Z0-9_]+( desc)?( asc)?,?)+$', re.I)
+
def intersect(la, lb):
return filter(lambda x: x in lb, la)
@@ -75,11 +74,12 @@ class except_orm(Exception):
self.value = value
self.args = (name, value)
+
# Readonly python database object browser
class browse_null(object):
def __init__(self):
- self.id=False
+ self.id = False
def __getitem__(self, name):
return False
@@ -93,6 +93,7 @@ class browse_null(object):
def __nonzero__(self):
return False
+
#
# TODO: execute an object method on browse_record_list
#
@@ -126,13 +127,13 @@ class browse_record(object):
cache.setdefault(table._name, {})
self._data = cache[table._name]
if not id in self._data:
- self._data[id] = {'id':id}
+ self._data[id] = {'id': id}
self._cache = cache
def __getitem__(self, name):
if name == 'id':
return self._id
- if not self._data[self._id].has_key(name):
+ if not name in self._data[self._id]:
# build the list of fields we will fetch
# fetch the definition of the field which was asked for
@@ -141,7 +142,7 @@ class browse_record(object):
elif name in self._table._inherit_fields:
col = self._table._inherit_fields[name][2]
elif hasattr(self._table, name):
- if isinstance( getattr(self._table, name), (types.MethodType,types.LambdaType,types.FunctionType)):
+ if isinstance(getattr(self._table, name), (types.MethodType, types.LambdaType, types.FunctionType)):
return lambda *args, **argv: getattr(self._table, name)(self._cr, self._uid, [self._id], *args, **argv)
else:
return getattr(self._table, name)
@@ -160,13 +161,13 @@ class browse_record(object):
ffields += filter(lambda x: x[1]._classic_write, inherits)
# otherwise we fetch only that field
else:
- ffields = [(name,col)]
- ids = filter(lambda id: not self._data[id].has_key(name), self._data.keys())
+ ffields = [(name, col)]
+ ids = filter(lambda id: not name in self._data[id], self._data.keys())
# read the data
fffields = map(lambda x: x[0], ffields)
datas = self._table.read(self._cr, self._uid, ids, fffields, context=self._context, load="_classic_write")
if self._fields_process:
- for n,f in ffields:
+ for n, f in ffields:
if f._type in self._fields_process:
for d in datas:
d[n] = self._fields_process[f._type](d[n])
@@ -175,11 +176,11 @@ class browse_record(object):
# create browse records for 'remote' objects
for data in datas:
- for n,f in ffields:
+ for n, f in ffields:
if f._type in ('many2one', 'one2one'):
if data[n]:
obj = self._table.pool.get(f._obj)
- compids=False
+ compids = False
if not f._classic_write:
ids2 = data[n][0]
else:
@@ -191,7 +192,7 @@ class browse_record(object):
else:
data[n] = browse_null()
elif f._type in ('one2many', 'many2many') and len(data[n]):
- data[n] = self._list_class([browse_record(self._cr,self._uid,id,self._table.pool.get(f._obj),self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in data[n]], self._context)
+ data[n] = self._list_class([browse_record(self._cr, self._uid, id, self._table.pool.get(f._obj), self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in data[n]], self._context)
self._data[data['id']].update(data)
return self._data[self._id][name]
@@ -235,38 +236,38 @@ def get_pg_type(f):
'''
type_dict = {
- fields.boolean:'bool',
- fields.integer:'int4',
- fields.text:'text',
- fields.date:'date',
- fields.time:'time',
- fields.datetime:'timestamp',
- fields.binary:'bytea',
- fields.many2one:'int4',
+ fields.boolean: 'bool',
+ fields.integer: 'int4',
+ fields.text: 'text',
+ fields.date: 'date',
+ fields.time: 'time',
+ fields.datetime: 'timestamp',
+ fields.binary: 'bytea',
+ fields.many2one: 'int4',
}
- if type_dict.has_key(type(f)):
+ if type(f) in type_dict:
f_type = (type_dict[type(f)], type_dict[type(f)])
elif isinstance(f, fields.float):
if f.digits:
- f_type = ('numeric', 'NUMERIC(%d,%d)' % (f.digits[0],f.digits[1]))
+ f_type = ('numeric', 'NUMERIC(%d,%d)' % (f.digits[0], f.digits[1]))
else:
f_type = ('float8', 'DOUBLE PRECISION')
elif isinstance(f, (fields.char, fields.reference)):
f_type = ('varchar', 'VARCHAR(%d)' % (f.size,))
elif isinstance(f, fields.selection):
if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)):
- f_size = reduce(lambda x,y: max(x,len(y[0])), f.selection, f.size or 16)
+ f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16)
elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int):
f_size = -1
else:
- f_size = (hasattr(f,'size') and f.size) or 16
+ f_size = (hasattr(f, 'size') and f.size) or 16
if f_size == -1:
f_type = ('int4', 'INTEGER')
else:
f_type = ('varchar', 'VARCHAR(%d)' % f_size)
- elif isinstance(f, fields.function) and type_dict.has_key(eval('fields.'+(f._type))):
- t=eval('fields.'+(f._type))
+ elif isinstance(f, fields.function) and eval('fields.'+(f._type)) in type_dict:
+ t = eval('fields.'+(f._type))
f_type = (type_dict[t], type_dict[t])
elif isinstance(f, fields.function) and f._type == 'float':
f_type = ('float8', 'DOUBLE PRECISION')
@@ -278,6 +279,7 @@ def get_pg_type(f):
f_type = None
return f_type
+
class orm_template(object):
_name = None
_columns = {}
@@ -285,6 +287,7 @@ class orm_template(object):
_defaults = {}
_rec_name = 'name'
_parent_name = 'parent_id'
+ _parent_store = False
_date_name = 'date'
_order = 'id'
_sequence = None
@@ -298,10 +301,10 @@ class orm_template(object):
# reference model in order to have a description of its fonctionnality in custom_report
cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',))
id = cr.fetchone()[0]
- cr.execute("INSERT INTO ir_model (id,model, name, info) VALUES (%s, %s, %s, %s)", (id,self._name, self._description, self.__doc__))
+ cr.execute("INSERT INTO ir_model (id,model, name, info) VALUES (%s, %s, %s, %s)", (id, self._name, self._description, self.__doc__))
if 'module' in context:
cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
- ( 'model_'+self._table, context['module'], 'ir.model', id)
+ ('model_'+self._table, context['module'], 'ir.model', id)
)
cr.commit()
@@ -313,50 +316,48 @@ class orm_template(object):
cr.execute("SELECT id FROM ir_model WHERE model='%s'" % self._name)
model_id = cr.fetchone()[0]
- for (k,f) in self._columns.items():
+ for (k, f) in self._columns.items():
vals = {
- 'model_id':model_id,
- 'model':self._name,
- 'name':k,
- 'field_description':f.string.replace("'", " "),
- 'ttype':f._type,
- 'relate':(f.relate and 1) or 0,
- 'relation':f._obj or 'NULL',
- 'group_name':f.group_name or '',
- 'view_load':(f.view_load and 1) or 0,
- 'select_level':str(f.select or 0)
+ 'model_id': model_id,
+ 'model': self._name,
+ 'name': k,
+ 'field_description': f.string.replace("'", " "),
+ 'ttype': f._type,
+ 'relate': (f.relate and 1) or 0,
+ 'relation': f._obj or 'NULL',
+ 'view_load': (f.view_load and 1) or 0,
+ 'select_level': str(f.select or 0)
}
if k not in cols:
cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',))
id = cr.fetchone()[0]
vals['id'] = id
cr.execute("""INSERT INTO ir_model_fields (
- id, model_id, model, name, field_description, ttype,
- relate,relation,group_name,view_load,state,select_level
+ id, model_id, model, name, field_description, ttype,
+ relate,relation,view_load,state,select_level
) VALUES (
- %d,%s,%s,%s,%s,%s, %s,%s,%s,%s,%s, %s
+ %d,%s,%s,%s,%s,%s,%s,%s,%s,%s, %s
)""", (
id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
- bool(vals['relate']), vals['relation'], vals['group_name'], bool(vals['view_load']), 'base',
+ bool(vals['relate']), vals['relation'], bool(vals['view_load']), 'base',
vals['select_level']
))
if 'module' in context:
cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
- ( ('field_'+self._table+'_'+k)[:64], context['module'], 'ir.model.fields', id)
+ (('field_'+self._table+'_'+k)[:64], context['module'], 'ir.model.fields', id)
)
else:
- for key,val in vals.items():
- if cols[k][key]<>vals[key]:
- print 'Different', cols[k][key], vals[key], k, key, self._name
- cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'],vals['model'],vals['name']))
+ for key, val in vals.items():
+ if cols[k][key] != vals[key]:
+ cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name']))
cr.commit()
cr.execute("""UPDATE ir_model_fields SET
model_id=%s, field_description=%s, ttype=%s, relate=%s, relation=%s,
- group_name=%s, view_load=%s, select_level=%s
+ view_load=%s, select_level=%s
WHERE
model=%s AND name=%s""", (
- vals['model_id'],vals['field_description'],vals['ttype'],bool(vals['relate']),
- vals['relation'], vals['group_name'], bool(vals['view_load']),
+ vals['model_id'], vals['field_description'], vals['ttype'], bool(vals['relate']),
+ vals['relation'], bool(vals['view_load']),
vals['select_level'], vals['model'], vals['name']
))
continue
@@ -369,19 +370,19 @@ class orm_template(object):
if not self._description:
self._description = self._name
if not self._table:
- self._table=self._name.replace('.','_')
+ self._table = self._name.replace('.', '_')
def browse(self, cr, uid, select, context=None, list_class=None, fields_process={}):
if not context:
- context={}
+ context = {}
self._list_class = list_class or browse_record_list
cache = {}
# need to accepts ints and longs because ids coming from a method
# launched by button in the interface have a type long...
if isinstance(select, (int, long)):
- return browse_record(cr,uid,select,self,cache, context=context, list_class=self._list_class, fields_process=fields_process)
- elif isinstance(select,list):
- return self._list_class([browse_record(cr,uid,id,self,cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select], context)
+ return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
+ elif isinstance(select, list):
+ return self._list_class([browse_record(cr, uid, id, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select], context)
else:
return browse_null()
@@ -394,7 +395,7 @@ class orm_template(object):
if f:
r = row
i = 0
- while i=len(line):
- raise Exception, _('Please check that all your lines have %d columns.') % (len(fields),)
+ if i >= len(line):
+ raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),))
field = fields[i]
if field == ["id"]:
- data_id= line[i]
+ data_id = line[i]
continue
if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'):
res_id = False
@@ -472,7 +474,7 @@ class orm_template(object):
if res_id2:
res_id.append(res_id2)
if len(res_id):
- res_id=[(6,0,res_id)]
+ res_id = [(6, 0, res_id)]
else:
if '.' in line[i]:
module, xml_id = line[i].rsplit('.', 1)
@@ -492,9 +494,9 @@ class orm_template(object):
if (len(field) == len(prefix)+1) and \
(prefix == field[0:len(prefix)]):
if fields_def[field[len(prefix)]]['type'] == 'integer':
- res =line[i] and int(line[i])
+ res = line[i] and int(line[i])
elif fields_def[field[len(prefix)]]['type'] == 'float':
- res =line[i] and float(line[i])
+ res = line[i] and float(line[i])
elif fields_def[field[len(prefix)]]['type'] == 'selection':
res = False
if isinstance(fields_def[field[len(prefix)]]['selection'],
@@ -541,7 +543,7 @@ class orm_template(object):
else:
res.append(res3)
if len(res):
- res= [(6,0,res)]
+ res = [(6, 0, res)]
else:
res = line[i] or False
row[field[len(prefix)]] = res
@@ -559,9 +561,9 @@ class orm_template(object):
(newrow, max2, w2, translate2, data_id2) = res
nbrmax = max(nbrmax, max2)
warning = warning + w2
- reduce(lambda x,y: x and y, newrow)
+ reduce(lambda x, y: x and y, newrow)
row[field] = (reduce(lambda x, y: x or y, newrow.values()) and \
- [(0,0,newrow)]) or []
+ [(0, 0, newrow)]) or []
i = max2
while (position+i)'cascade':
+ logger.notifyChannel('init', netsvc.LOG_ERROR, "the columns %s on object must be set as ondelete='cascasde'" % (self._name, self._parent_name))
+ cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" INTEGER" % (self._table, 'parent_left'))
+ cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" INTEGER" % (self._table, 'parent_right'))
+ cr.commit()
+ store_compute = True
+
if self._log_access:
logs = {
'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
@@ -1297,7 +1334,7 @@ class orm(orm_template):
# iterate on the "object columns"
for k in self._columns:
- if k in ('id','write_uid','write_date','create_uid','create_date'):
+ if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
continue
#raise _('Can not define a column %s. Reserved keyword !') % (k,)
f = self._columns[k]
@@ -1305,7 +1342,7 @@ class orm(orm_template):
if isinstance(f, fields.one2many):
cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
if cr.fetchone():
- cr.execute("SELECT count(*) as c FROM pg_class c,pg_attribute a WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid", (f._obj,f._fields_id))
+ cr.execute("SELECT count(*) as c FROM pg_class c,pg_attribute a WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid", (f._obj, f._fields_id))
res = cr.fetchone()[0]
if not res:
cr.execute("ALTER TABLE \"%s\" ADD FOREIGN KEY (%s) REFERENCES \"%s\" ON DELETE SET NULL" % (self._obj, f._fields_id, f._table))
@@ -1316,16 +1353,16 @@ class orm(orm_template):
try:
ref = self.pool.get(f._obj)._table
except AttributeError:
- ref = f._obj.replace('.','_')
- cr.execute("CREATE TABLE \"%s\" (\"%s\" INTEGER NOT NULL REFERENCES \"%s\" ON DELETE CASCADE, \"%s\" INTEGER NOT NULL REFERENCES \"%s\" ON DELETE CASCADE) WITH OIDS"%(f._rel,f._id1,self._table,f._id2,ref))
- cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (f._rel,f._id1,f._rel,f._id1))
- cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (f._rel,f._id2,f._rel,f._id2))
+ ref = f._obj.replace('.', '_')
+ cr.execute("CREATE TABLE \"%s\" (\"%s\" INTEGER NOT NULL REFERENCES \"%s\" ON DELETE CASCADE, \"%s\" INTEGER NOT NULL REFERENCES \"%s\" ON DELETE CASCADE) WITH OIDS"%(f._rel, f._id1, self._table, f._id2, ref))
+ cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (f._rel, f._id1, f._rel, f._id1))
+ cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (f._rel, f._id2, f._rel, f._id2))
cr.commit()
else:
cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size FROM pg_class c,pg_attribute a,pg_type t WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid AND a.atttypid=t.oid", (self._table, k))
res = cr.dictfetchall()
if not res:
- if not isinstance(f,fields.function) or f.store:
+ if not isinstance(f, fields.function) or f.store:
# add the missing field
cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" %s" % (self._table, k, get_pg_type(f)[1]))
@@ -1337,7 +1374,7 @@ class orm(orm_template):
cr.execute("UPDATE \"%s\" SET \"%s\"=NULL" % (self._table, k))
else:
cr.execute("UPDATE \"%s\" SET \"%s\"='%s'" % (self._table, k, default))
- if isinstance(f,fields.function):
+ if isinstance(f, fields.function):
cr.execute('select id from '+self._table)
ids_lst = map(lambda x: x[0], cr.fetchall())
while ids_lst:
@@ -1345,7 +1382,7 @@ class orm(orm_template):
ids_lst = ids_lst[40:]
res = f.get(cr, self, iids, k, 1, {})
for r in res.items():
- cr.execute("UPDATE \"%s\" SET \"%s\"='%s' where id=%d"% (self._table, k, r[1],r[0]))
+ cr.execute("UPDATE \"%s\" SET \"%s\"='%s' where id=%d"% (self._table, k, r[1], r[0]))
# and add constraints if needed
if isinstance(f, fields.many2one):
@@ -1353,9 +1390,9 @@ class orm(orm_template):
try:
ref = self.pool.get(f._obj)._table
except AttributeError:
- ref = f._obj.replace('.','_')
+ ref = f._obj.replace('.', '_')
# ir_actions is inherited so foreign key doesn't work on it
- if ref <> 'ir_actions':
+ if ref != 'ir_actions':
cr.execute("ALTER TABLE \"%s\" ADD FOREIGN KEY (\"%s\") REFERENCES \"%s\" ON DELETE %s" % (self._table, k, ref, f.ondelete))
if f.select:
cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (self._table, k, self._table, k))
@@ -1389,15 +1426,15 @@ class orm(orm_template):
# We update varchar size, otherwise, we keep DB size
# to avoid truncated string...
if f_pg_size < f.size:
- cr.execute("ALTER TABLE \"%s\" RENAME COLUMN \"%s\" TO temp_change_size" % (self._table,k))
- cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" VARCHAR(%d)" % (self._table,k,f.size))
- cr.execute("UPDATE \"%s\" SET \"%s\"=temp_change_size::VARCHAR(%d)" % (self._table,k,f.size))
+ cr.execute("ALTER TABLE \"%s\" RENAME COLUMN \"%s\" TO temp_change_size" % (self._table, k))
+ cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" VARCHAR(%d)" % (self._table, k, f.size))
+ cr.execute("UPDATE \"%s\" SET \"%s\"=temp_change_size::VARCHAR(%d)" % (self._table, k, f.size))
cr.execute("ALTER TABLE \"%s\" DROP COLUMN temp_change_size" % (self._table,))
cr.commit()
# if the field is required and hasn't got a NOT NULL constraint
if f.required and f_pg_notnull == 0:
# set the field to the default value if any
- if self._defaults.has_key(k):
+ if k in self._defaults:
default = self._defaults[k](self, cr, 1, {})
if not (default is False):
cr.execute("UPDATE \"%s\" SET \"%s\"='%s' WHERE %s is NULL" % (self._table, k, default, k))
@@ -1410,7 +1447,7 @@ class orm(orm_template):
logger.notifyChannel('init', netsvc.LOG_WARNING, 'unable to set a NOT NULL constraint on column %s of the %s table !\nIf you want to have it, you should update the records and execute manually:\nALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % (k, self._table, self._table, k))
cr.commit()
elif not f.required and f_pg_notnull == 1:
- cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" DROP NOT NULL" % (self._table,k))
+ cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" DROP NOT NULL" % (self._table, k))
cr.commit()
cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = '%s_%s_index' and tablename = '%s'" % (self._table, k, self._table))
res = cr.dictfetchall()
@@ -1457,26 +1494,28 @@ class orm(orm_template):
cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table)
create = not bool(cr.fetchone())
- for (key,con,_) in self._sql_constraints:
+ for (key, con, _) in self._sql_constraints:
cr.execute("SELECT conname FROM pg_constraint where conname='%s_%s'" % (self._table, key))
if not cr.dictfetchall():
try:
- cr.execute('alter table \"%s\" add constraint \"%s_%s\" %s' % (self._table,self._table,key, con,))
+ cr.execute('alter table \"%s\" add constraint \"%s_%s\" %s' % (self._table, self._table, key, con,))
cr.commit()
except:
- logger.notifyChannel('init', netsvc.LOG_WARNING, 'unable to add \'%s\' constraint on table %s !\n If you want to have it, you should update the records and execute manually:\nALTER table %s ADD CONSTRAINT %s_%s %s' % (con, self._table, self._table,self._table,key, con,))
+ logger.notifyChannel('init', netsvc.LOG_WARNING, 'unable to add \'%s\' constraint on table %s !\n If you want to have it, you should update the records and execute manually:\nALTER table %s ADD CONSTRAINT %s_%s %s' % (con, self._table, self._table, self._table, key, con,))
if create:
- if hasattr(self,"_sql"):
+ if hasattr(self, "_sql"):
for line in self._sql.split(';'):
- line2 = line.replace('\n','').strip()
+ line2 = line.replace('\n', '').strip()
if line2:
cr.execute(line2)
cr.commit()
+ if store_compute:
+ self._parent_store_compute(cr)
def __init__(self, cr):
super(orm, self).__init__(cr)
- f=filter(lambda a: isinstance(self._columns[a], fields.function) and self._columns[a].store, self._columns)
+ f = filter(lambda a: isinstance(self._columns[a], fields.function) and self._columns[a].store, self._columns)
if f:
list_store = []
tuple_store = ()
@@ -1485,29 +1524,29 @@ class orm(orm_template):
if not self._columns[store_field].store == True:
dict_store = self._columns[store_field].store
key = dict_store.keys()
- list_data=[]
+ list_data = []
for i in key:
- tuple_store=self._name,store_field,self._columns[store_field]._fnct.__name__,tuple(dict_store[i][0]),dict_store[i][1],i
+ tuple_store = self._name, store_field, self._columns[store_field]._fnct.__name__, tuple(dict_store[i][0]), dict_store[i][1], i
list_data.append(tuple_store)
#tuple_store=self._name,store_field,self._columns[store_field]._fnct.__name__,tuple(dict_store[key[0]][0]),dict_store[key[0]][1]
for l in list_data:
- list_store=[]
+ list_store = []
if l[5] in self.pool._store_function.keys():
self.pool._store_function[l[5]].append(l)
temp_list = list(set(self.pool._store_function[l[5]]))
- self.pool._store_function[l[5]]=temp_list
+ self.pool._store_function[l[5]] = temp_list
else:
list_store.append(l)
- self.pool._store_function[l[5]]=list_store
+ self.pool._store_function[l[5]] = list_store
- for (key,_,msg) in self._sql_constraints:
+ for (key, _, msg) in self._sql_constraints:
self.pool._sql_error[self._table+'_'+key] = msg
# Load manual fields
- cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state','ir.model.fields'))
+ cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
if cr.fetchone():
- cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name,'manual'))
+ cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
for field in cr.dictfetchall():
if field['name'] in self._columns:
continue
@@ -1540,7 +1579,7 @@ class orm(orm_template):
def default_get(self, cr, uid, fields_list, context=None):
if not context:
- context={}
+ context = {}
value = {}
# get the default values for the inherited fields
for t in self._inherits.keys():
@@ -1552,7 +1591,7 @@ class orm(orm_template):
if f in self._defaults:
value[f] = self._defaults[f](self, cr, uid, context)
fld_def = ((f in self._columns) and self._columns[f]) \
- or ((f in self._inherit_fields ) and self._inherit_fields[f][2]) \
+ or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \
or False
if isinstance(fld_def, fields.property):
property_obj = self.pool.get('ir.property')
@@ -1616,51 +1655,51 @@ class orm(orm_template):
for table in self._inherits:
res.update(self.pool.get(table)._inherit_fields)
for col in self.pool.get(table)._columns.keys():
- res[col]=(table, self._inherits[table], self.pool.get(table)._columns[col])
+ res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
for col in self.pool.get(table)._inherit_fields.keys():
- res[col]=(table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
- self._inherit_fields=res
+ res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
+ self._inherit_fields = res
self._inherits_reload_src()
def fields_get(self, cr, user, fields=None, context=None):
- read_access= self.pool.get('ir.model.access').check(cr, user, self._name, 'write', raise_exception=False)
+ read_access = self.pool.get('ir.model.access').check(cr, user, self._name, 'write', raise_exception=False)
return super(orm, self).fields_get(cr, user, fields, context, read_access)
def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
if not context:
- context={}
+ context = {}
self.pool.get('ir.model.access').check(cr, user, self._name, 'read')
if not fields:
fields = self._columns.keys() + self._inherit_fields.keys()
select = ids
if isinstance(ids, (int, long)):
select = [ids]
- result = self._read_flat(cr, user, select, fields, context, load)
+ result = self._read_flat(cr, user, select, fields, context, load)
for r in result:
- for key,v in r.items():
+ for key, v in r.items():
if v == None:
- r[key]=False
+ r[key] = False
if isinstance(ids, (int, long)):
return result[0]
return result
def _read_flat(self, cr, user, ids, fields, context=None, load='_classic_read'):
if not context:
- context={}
+ context = {}
if not ids:
return []
- if fields==None:
+ if fields == None:
fields = self._columns.keys()
# construct a clause for the rules :
d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name)
# all inherited fields + all non inherited fields for which the attribute whose name is in load is True
- fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x],'_classic_write'), fields) + self._inherits.values()
+ fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x], '_classic_write'), fields) + self._inherits.values()
res = []
- if len(fields_pre) :
+ if len(fields_pre):
fields_pre2 = map(lambda x: (x in ('create_date', 'write_date')) and ('date_trunc(\'second\', '+x+') as '+x) or '"'+x+'"', fields_pre)
for i in range(0, len(ids), cr.IN_MAX):
sub_ids = ids[i:i+cr.IN_MAX]
@@ -1668,7 +1707,7 @@ class orm(orm_template):
cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) AND %s ORDER BY %s' % \
(','.join(fields_pre2 + ['id']), self._table,
','.join([str(x) for x in sub_ids]), d1,
- self._order),d2)
+ self._order), d2)
if not cr.rowcount == len({}.fromkeys(sub_ids)):
raise except_orm(_('AccessError'),
_('You try to bypass an access rule (Document type: %s).') % self._description)
@@ -1717,11 +1756,29 @@ class orm(orm_template):
# all non inherited fields for which the attribute whose name is in load is False
fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields)
+
+ # Compute POST fields
+ todo = {}
for f in fields_post:
- # get the value of that field for all records/ids
- res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
- for record in res:
- record[f] = res2[record['id']]
+ todo.setdefault(self._columns[f]._multi, [])
+ todo[self._columns[f]._multi].append(f)
+ for key,val in todo.items():
+ if key:
+ res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res)
+ for pos in range(len(val)):
+ for record in res:
+ record[val[pos]] = res2[record['id']][pos]
+ else:
+ for f in val:
+ res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
+ for record in res:
+ record[f] = res2[record['id']]
+
+#for f in fields_post:
+# # get the value of that field for all records/ids
+# res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res)
+# for record in res:
+# record[f] = res2[record['id']]
readonly = None
for vals in res:
@@ -1762,22 +1819,22 @@ class orm(orm_template):
def perm_read(self, cr, user, ids, context=None, details=True):
if not context:
- context={}
+ context = {}
if not ids:
return []
fields = ''
if self._log_access:
- fields =', u.create_uid, u.create_date, u.write_uid, u.write_date'
+ fields = ', u.create_uid, u.create_date, u.write_uid, u.write_date'
if isinstance(ids, (int, long)):
ids_str = str(ids)
else:
- ids_str = string.join(map(lambda x:str(x), ids),',')
+ ids_str = string.join(map(lambda x: str(x), ids), ',')
cr.execute('select u.id'+fields+' from "'+self._table+'" u where u.id in ('+ids_str+')')
res = cr.dictfetchall()
for r in res:
for key in r:
r[key] = r[key] or False
- if key in ('write_uid','create_uid','uid') and details:
+ if key in ('write_uid', 'create_uid', 'uid') and details:
if r[key]:
r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0]
if isinstance(ids, (int, long)):
@@ -1786,7 +1843,7 @@ class orm(orm_template):
def unlink(self, cr, uid, ids, context=None):
if not context:
- context={}
+ context = {}
if not ids:
return True
if isinstance(ids, (int, long)):
@@ -1799,18 +1856,18 @@ class orm(orm_template):
id_change = []
for tuple_fn in list_store:
for id in ids:
- id_change.append(self._store_get_ids(cr, uid, id,tuple_fn, context)[0])
- fn_data = id_change,tuple_fn
+ id_change.append(self._store_get_ids(cr, uid, id, tuple_fn, context)[0])
+ fn_data = id_change, tuple_fn
fn_list.append(fn_data)
- delta= context.get('read_delta',False)
+ delta = context.get('read_delta', False)
if delta and self._log_access:
for i in range(0, len(ids), cr.IN_MAX):
sub_ids = ids[i:i+cr.IN_MAX]
cr.execute("select (now() - min(write_date)) <= '%s'::interval " \
"from \"%s\" where id in (%s)" %
- (delta, self._table, ",".join(map(str, sub_ids))) )
- res= cr.fetchone()
+ (delta, self._table, ",".join(map(str, sub_ids))))
+ res = cr.fetchone()
if res and res[0]:
raise except_orm(_('ConcurrencyException'),
_('This record was modified in the meanwhile'))
@@ -1833,7 +1890,7 @@ class orm(orm_template):
for i in range(0, len(ids), cr.IN_MAX):
sub_ids = ids[i:i+cr.IN_MAX]
- str_d = string.join(('%d',)*len(sub_ids),',')
+ str_d = string.join(('%d',)*len(sub_ids), ',')
if d1:
cr.execute('SELECT id FROM "'+self._table+'" ' \
'WHERE id IN ('+str_d+')'+d1, sub_ids+d2)
@@ -1849,8 +1906,8 @@ class orm(orm_template):
cr.execute('delete from "'+self._table+'" ' \
'where id in ('+str_d+')', sub_ids)
if fn_list:
- for ids,tuple_fn in fn_list:
- self._store_set_values(cr, uid, ids,tuple_fn,id_change, context)
+ for ids, tuple_fn in fn_list:
+ self._store_set_values(cr, uid, ids, tuple_fn, id_change, context)
return True
@@ -1889,19 +1946,19 @@ class orm(orm_template):
vals.pop(field)
if not context:
- context={}
+ context = {}
if not ids:
return True
if isinstance(ids, (int, long)):
ids = [ids]
- delta= context.get('read_delta',False)
+ delta = context.get('read_delta', False)
if delta and self._log_access:
for i in range(0, len(ids), cr.IN_MAX):
sub_ids = ids[i:i+cr.IN_MAX]
cr.execute("select (now() - min(write_date)) <= '%s'::interval " \
"from %s where id in (%s)" %
- (delta,self._table, ",".join(map(str, sub_ids))))
- res= cr.fetchone()
+ (delta, self._table, ",".join(map(str, sub_ids))))
+ res = cr.fetchone()
if res and res[0]:
for field in vals:
if field in self._columns and self._columns[field]._classic_write:
@@ -1912,10 +1969,10 @@ class orm(orm_template):
#for v in self._inherits.values():
# assert v not in vals, (v, vals)
- upd0=[]
- upd1=[]
- upd_todo=[]
- updend=[]
+ upd0 = []
+ upd1 = []
+ upd_todo = []
+ updend = []
direct = []
totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
for field in vals:
@@ -1961,7 +2018,7 @@ class orm(orm_template):
for i in range(0, len(ids), cr.IN_MAX):
sub_ids = ids[i:i+cr.IN_MAX]
- ids_str = string.join(map(str, sub_ids),',')
+ ids_str = string.join(map(str, sub_ids), ',')
if d1:
cr.execute('SELECT id FROM "'+self._table+'" ' \
'WHERE id IN ('+ids_str+')'+d1, d2)
@@ -1976,19 +2033,19 @@ class orm(orm_template):
_('You try to write on an record that doesn\'t exist ' \
'(Document type: %s).') % self._description)
if d1:
- cr.execute('update "'+self._table+'" set '+string.join(upd0,',')+' ' \
+ cr.execute('update "'+self._table+'" set '+string.join(upd0, ',')+' ' \
'where id in ('+ids_str+')'+d1, upd1+ d2)
else:
- cr.execute('update "'+self._table+'" set '+string.join(upd0,',')+' ' \
+ cr.execute('update "'+self._table+'" set '+string.join(upd0, ',')+' ' \
'where id in ('+ids_str+')', upd1)
if totranslate:
for f in direct:
if self._columns[f].translate:
- self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f,'model', context['lang'], ids,vals[f])
+ self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f])
# call the 'set' method of fields which are not classic_write
- upd_todo.sort(lambda x,y: self._columns[x].priority-self._columns[y].priority)
+ upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
for field in upd_todo:
for id in ids:
self._columns[field].set(cr, self, id, field, vals[field], user, context=context)
@@ -1998,20 +2055,65 @@ class orm(orm_template):
nids = []
for i in range(0, len(ids), cr.IN_MAX):
sub_ids = ids[i:i+cr.IN_MAX]
- ids_str = string.join(map(str, sub_ids),',')
+ ids_str = string.join(map(str, sub_ids), ',')
cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \
'where id in ('+ids_str+')', upd1)
nids.extend([x[0] for x in cr.fetchall()])
v = {}
for val in updend:
- if self._inherit_fields[val][0]==table:
- v[val]=vals[val]
+ if self._inherit_fields[val][0] == table:
+ v[val] = vals[val]
self.pool.get(table).write(cr, user, nids, v, context)
self._validate(cr, user, ids, context)
+# TODO: use _order to set dest at the right position and not first node of parent
+ if self._parent_store and (self._parent_name in vals):
+ if self.pool._init:
+ self.pool._init_parent[self._name]=True
+ else:
+ cr.execute('select parent_left,parent_right from '+self._table+' where id=%d', (vals[self._parent_name],))
+ res = cr.fetchone()
+ if res:
+ pleft,pright = res
+ else:
+ cr.execute('select max(parent_right),max(parent_right)+1 from '+self._table)
+ pleft,pright = cr.fetchone()
+ cr.execute('select parent_left,parent_right,id from '+self._table+' where id in ('+','.join(map(lambda x:'%d',ids))+')', ids)
+ dest = pleft + 1
+ for cleft,cright,cid in cr.fetchall():
+ if cleft > pleft:
+ treeshift = pleft - cleft + 1
+ leftbound = pleft+1
+ rightbound = cleft-1
+ cwidth = cright-cleft+1
+ leftrange = cright
+ rightrange = pleft
+ else:
+ treeshift = pleft - cright
+ leftbound = cright + 1
+ rightbound = pleft
+ cwidth = cleft-cright-1
+ leftrange = pleft+1
+ rightrange = cleft
+ cr.execute('UPDATE '+self._table+'''
+ SET
+ parent_left = CASE
+ WHEN parent_left BETWEEN %d AND %d THEN parent_left + %d
+ WHEN parent_left BETWEEN %d AND %d THEN parent_left + %d
+ ELSE parent_left
+ END,
+ parent_right = CASE
+ WHEN parent_right BETWEEN %d AND %d THEN parent_right + %d
+ WHEN parent_right BETWEEN %d AND %d THEN parent_right + %d
+ ELSE parent_right
+ END
+ WHERE
+ parent_left<%d OR parent_right>%d;
+ ''', (leftbound,rightbound,cwidth,cleft,cright,treeshift,leftbound,rightbound,
+ cwidth,cleft,cright,treeshift,leftrange,rightrange))
- if context.has_key('read_delta'):
+ if 'read_delta' in context:
del context['read_delta']
wf_service = netsvc.LocalService("workflow")
@@ -2030,8 +2132,8 @@ class orm(orm_template):
flag = True
break
if flag:
- id_change = self._store_get_ids(cr, user, ids[0],tuple_fn, context)
- self._store_set_values(cr, user, ids[0],tuple_fn,id_change, context)
+ id_change = self._store_get_ids(cr, user, ids[0], tuple_fn, context)
+ self._store_set_values(cr, user, ids[0], tuple_fn, id_change, context)
return True
@@ -2045,13 +2147,13 @@ class orm(orm_template):
vals = dictionary of the form {'field_name':field_value, ...}
"""
if not context:
- context={}
+ context = {}
self.pool.get('ir.model.access').check(cr, user, self._name, 'create')
default = []
avoid_table = []
- for (t,c) in self._inherits.items():
+ for (t, c) in self._inherits.items():
if c in vals:
avoid_table.append(t)
for f in self._columns.keys(): # + self._inherit_fields.keys():
@@ -2074,7 +2176,7 @@ class orm(orm_template):
for v in vals.keys():
if v in self._inherit_fields:
- (table,col,col_detail) = self._inherit_fields[v]
+ (table, col, col_detail) = self._inherit_fields[v]
tocreate[table][v] = vals[v]
del vals[v]
@@ -2088,8 +2190,8 @@ class orm(orm_template):
for field in vals:
if self._columns[field]._classic_write:
- upd0=upd0+',"'+field+'"'
- upd1=upd1+','+self._columns[field]._symbol_set[0]
+ upd0 = upd0 + ',"' + field + '"'
+ upd1 = upd1 + ',' + self._columns[field]._symbol_set[0]
upd2.append(self._columns[field]._symbol_set[1](vals[field]))
else:
upd_todo.append(field)
@@ -2116,36 +2218,51 @@ class orm(orm_template):
upd1 += ',%d,now()'
upd2.append(user)
cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
- upd_todo.sort(lambda x,y: self._columns[x].priority-self._columns[y].priority)
+ upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
for field in upd_todo:
self._columns[field].set(cr, self, id_new, field, vals[field], user, context)
self._validate(cr, user, [id_new], context)
+ if self._parent_store:
+ if self.pool._init:
+ self.pool._init_parent[self._name]=True
+ else:
+ parent = vals.get(self._parent_name, False)
+ if parent:
+ cr.execute('select parent_left from '+self._table+' where id=%d', (parent,))
+ pleft = cr.fetchone()[0]
+ else:
+ cr.execute('select max(parent_right) from '+self._table)
+ pleft = cr.fetchone()[0] or 0
+ cr.execute('update '+self._table+' set parent_left=%d,parent_right=%d', (pleft+1,pleft+2))
+ cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%d', (pleft,))
+ cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%d', (pleft,))
+
wf_service = netsvc.LocalService("workflow")
wf_service.trg_create(user, self._name, id_new, cr)
self._update_function_stored(cr, user, [id_new], context=context)
if self._name in self.pool._store_function.keys():
list_store = self.pool._store_function[self._name]
for tuple_fn in list_store:
- id_change = self._store_get_ids(cr, user, id_new,tuple_fn, context)
- self._store_set_values(cr, user, id_new,tuple_fn,id_change, context)
+ id_change = self._store_get_ids(cr, user, id_new, tuple_fn, context)
+ self._store_set_values(cr, user, id_new, tuple_fn, id_change, context)
return id_new
- def _store_get_ids(self,cr, uid, ids,tuple_fn, context):
- parent_id=getattr(self.pool.get(tuple_fn[0]),tuple_fn[4].func_name)(cr,uid,[ids])
+ def _store_get_ids(self, cr, uid, ids, tuple_fn, context):
+ parent_id = getattr(self.pool.get(tuple_fn[0]), tuple_fn[4].func_name)(cr, uid, [ids])
return parent_id
- def _store_set_values(self,cr,uid,ids,tuple_fn,parent_id,context):
+ def _store_set_values(self, cr, uid, ids, tuple_fn, parent_id, context):
name = tuple_fn[1]
table = tuple_fn[0]
- args={}
- vals_tot=getattr(self.pool.get(table),tuple_fn[2])(cr, uid, parent_id ,name, args, context)
+ args = {}
+ vals_tot = getattr(self.pool.get(table), tuple_fn[2])(cr, uid, parent_id, name, args, context)
write_dict = {}
for id in vals_tot.keys():
- write_dict[name]=vals_tot[id]
- self.pool.get(table).write(cr,uid,[id],write_dict)
+ write_dict[name] = vals_tot[id]
+ self.pool.get(table).write(cr, uid, [id], write_dict)
return True
def _update_function_stored(self, cr, user, ids, context=None):
@@ -2154,10 +2271,10 @@ class orm(orm_template):
f = filter(lambda a: isinstance(self._columns[a], fields.function) \
and self._columns[a].store, self._columns)
if f:
- result=self.read(cr, user, ids, fields=f, context=context)
+ result = self.read(cr, user, ids, fields=f, context=context)
for res in result:
- upd0=[]
- upd1=[]
+ upd0 = []
+ upd1 = []
for field in res:
if field not in f:
continue
@@ -2180,12 +2297,15 @@ class orm(orm_template):
# TODO: ameliorer avec NULL
def _where_calc(self, cr, user, args, active_test=True, context=None):
if not context:
- context={}
+ context = {}
args = args[:]
# if the object has a field named 'active', filter out all inactive
# records unless they were explicitely asked for
if 'active' in self._columns and (active_test and context.get('active_test', True)):
- args = [('&', ('active', '=', 1), tuple(args))]
+ if args:
+ args.insert(0, ('active', '=', 1))
+ else:
+ args = [('active', '=', 1)]
if args:
import expression
@@ -2197,7 +2317,7 @@ class orm(orm_template):
else:
qu1, qu2, tables = [], [], ['"%s"' % self._table]
- return (qu1,qu2,tables)
+ return (qu1, qu2, tables)
def _check_qorder(self, word):
if not regex_order.match(word):
@@ -2209,10 +2329,10 @@ class orm(orm_template):
if not context:
context = {}
# compute the where, order by, limit and offset clauses
- (qu1,qu2,tables) = self._where_calc(cr, user, args, context=context)
+ (qu1, qu2, tables) = self._where_calc(cr, user, args, context=context)
if len(qu1):
- qu1 = ' where '+string.join(qu1,' and ')
+ qu1 = ' where '+string.join(qu1, ' and ')
else:
qu1 = ''
@@ -2245,15 +2365,15 @@ class orm(orm_template):
# a char field
def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None):
if not args:
- args=[]
+ args = []
if field in self._inherit_fields:
- return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid,field,value,args,offset,limit)
+ return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit)
else:
return self._columns[field].search(cr, self, args, field, value, offset, limit, uid)
def name_get(self, cr, user, ids, context=None):
if not context:
- context={}
+ context = {}
if not ids:
return []
if isinstance(ids, (int, long)):
@@ -2263,19 +2383,19 @@ class orm(orm_template):
def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=80):
if not args:
- args=[]
+ args = []
if not context:
- context={}
- args=args[:]
+ context = {}
+ args = args[:]
if name:
- args += [(self._rec_name,operator,name)]
+ args += [(self._rec_name, operator, name)]
ids = self.search(cr, user, args, limit=limit, context=context)
res = self.name_get(cr, user, ids, context)
return res
def copy(self, cr, uid, id, default=None, context=None):
if not context:
- context={}
+ context = {}
if not default:
default = {}
if 'state' not in default:
@@ -2316,7 +2436,7 @@ class orm(orm_template):
def read_string(self, cr, uid, id, langs, fields=None, context=None):
if not context:
- context={}
+ context = {}
res = {}
res2 = {}
self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read')
@@ -2328,22 +2448,22 @@ class orm(orm_template):
if f in self._columns:
res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang)
if res_trans:
- res[lang][f]=res_trans
+ res[lang][f] = res_trans
else:
- res[lang][f]=self._columns[f].string
+ res[lang][f] = self._columns[f].string
for table in self._inherits:
cols = intersect(self._inherit_fields.keys(), fields)
res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context)
for lang in res2:
if lang in res:
- res[lang]={'code': lang}
+ res[lang] = {'code': lang}
for f in res2[lang]:
- res[lang][f]=res2[lang][f]
+ res[lang][f] = res2[lang][f]
return res
def write_string(self, cr, uid, id, langs, vals, context=None):
if not context:
- context={}
+ context = {}
self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write')
for lang in langs:
for field in vals:
diff --git a/bin/osv/osv.py b/bin/osv/osv.py
index 369f006c9df..6f883444fef 100644
--- a/bin/osv/osv.py
+++ b/bin/osv/osv.py
@@ -60,6 +60,8 @@ class osv_pool(netsvc.Service):
self.created = []
self._sql_error = {}
self._store_function = {}
+ self._init = True
+ self._init_parent = {}
netsvc.Service.__init__(self, 'object_proxy', audience='')
self.joinGroup('web-services')
self.exportMethod(self.exportedMethods)
@@ -68,6 +70,17 @@ class osv_pool(netsvc.Service):
self.exportMethod(self.execute)
self.exportMethod(self.execute_cr)
+ def init_set(self, cr, mode):
+ if mode<>self._init:
+ if mode:
+ self._init_parent={}
+ if not mode:
+ for o in self._init_parent:
+ self.get(o)._parent_store_compute(cr)
+ self._init = mode
+ return True
+ return False
+
def execute_cr(self, cr, uid, obj, method, *args, **kw):
try:
object = pooler.get_pool(cr.dbname).get(obj)
@@ -152,19 +165,11 @@ class osv_pool(netsvc.Service):
#TODO: pass a list of modules to load
def instanciate(self, module, cr):
-# print "module list:", module_list
-# for module in module_list:
res = []
class_list = module_class_list.get(module, [])
-# if module not in self.module_object_list:
-# print "%s class_list:" % module, class_list
for klass in class_list:
res.append(klass.createInstance(self, module, cr))
return res
-# else:
-# print "skipping module", module
-
-#pooler.get_pool(cr.dbname) = osv_pool()
class osv_memory(orm.orm_memory):
#__metaclass__ = inheritor
@@ -223,7 +228,7 @@ class osv(orm.orm):
parent_name = hasattr(cls, '_inherit') and cls._inherit
if parent_name:
parent_class = pool.get(parent_name).__class__
- assert parent_class, "parent class %s does not exist !" % parent_name
+ assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module)
nattr = {}
for s in ('_columns', '_defaults', '_inherits', '_constraints', '_sql_constraints'):
new = copy.copy(getattr(pool.get(parent_name), s))
diff --git a/bin/pooler.py b/bin/pooler.py
index 4f47a1f158b..fd23a9edaae 100644
--- a/bin/pooler.py
+++ b/bin/pooler.py
@@ -36,6 +36,7 @@ import netsvc
db_dic = {}
pool_dic = {}
+
def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False):
if not status:
status={}
@@ -46,13 +47,17 @@ def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False)
logger.notifyChannel('pooler', netsvc.LOG_INFO, 'Connecting to %s' % (db_name))
db = sql_db.db_connect(db_name)
db_dic[db_name] = db
-
+
if db_name in pool_dic:
pool = pool_dic[db_name]
else:
pool = osv.osv.osv_pool()
pool_dic[db_name] = pool
addons.load_modules(db, force_demo, status, update_module)
+ cr = db.cursor()
+ pool.init_set(cr, False)
+ cr.commit()
+ cr.close()
if not update_module:
import report
@@ -60,11 +65,13 @@ def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False)
pool.get('ir.cron')._poolJobs(db.dbname)
return db, pool
+
def restart_pool(db_name, force_demo=False, update_module=False):
# del db_dic[db_name]
del pool_dic[db_name]
return get_db_and_pool(db_name, force_demo, update_module=update_module)
+
def close_db(db_name):
if db_name in db_dic:
db_dic[db_name].truedb.close()
@@ -72,6 +79,7 @@ def close_db(db_name):
if db_name in pool_dic:
del pool_dic[db_name]
+
def get_db_only(db_name):
if db_name in db_dic:
db = db_dic[db_name]
@@ -80,10 +88,12 @@ def get_db_only(db_name):
db_dic[db_name] = db
return db
+
def get_db(db_name):
# print "get_db", db_name
return get_db_and_pool(db_name)[0]
+
def get_pool(db_name, force_demo=False, status=None, update_module=False):
# print "get_pool", db_name
pool = get_db_and_pool(db_name, force_demo, status, update_module)[1]
@@ -94,6 +104,7 @@ def get_pool(db_name, force_demo=False, status=None, update_module=False):
return pool
# return get_db_and_pool(db_name)[1]
+
def init():
global db
# db = get_db_only(tools.config['db_name'])
diff --git a/bin/service/web_services.py b/bin/service/web_services.py
index 405fea724ac..20473e6388a 100644
--- a/bin/service/web_services.py
+++ b/bin/service/web_services.py
@@ -56,6 +56,7 @@ class db(netsvc.Service):
self.exportMethod(self.list)
self.exportMethod(self.list_lang)
self.exportMethod(self.change_admin_password)
+ self.exportMethod(self.server_version)
self.actions = {}
self.id = 0
self.id_protect = threading.Semaphore()
@@ -281,6 +282,12 @@ class db(netsvc.Service):
lang = os.path.basename(fname).split(".")[0]
return (lang, lang_dict.get(lang, lang))
return [lang_tuple(fname) for fname in file_list]
+
+ def server_version(self):
+ """ Return the version of the server
+ Used by the client to verify the compatibility with its own version
+ """
+ return tinyerp_version
db()
class common(netsvc.Service):
diff --git a/bin/tiny_socket.py b/bin/tiny_socket.py
index 076155748d3..8f89fe97297 100644
--- a/bin/tiny_socket.py
+++ b/bin/tiny_socket.py
@@ -28,6 +28,7 @@
###############################################################################
import socket
import cPickle
+import cStringIO
import marshal
class Myexception(Exception):
@@ -82,7 +83,11 @@ class mysocket:
if chunk == '':
raise RuntimeError, "socket connection broken"
msg = msg + chunk
- res = cPickle.loads(msg)
+ msgio = cStringIO.StringIO(msg)
+ unpickler = cPickle.Unpickler(msgio)
+ unpickler.find_global = None
+ res = unpickler.load()
+
if isinstance(res[0],Exception):
if exception:
raise Myexception(str(res[0]), str(res[1]))
diff --git a/bin/tinyerp-server.py b/bin/tinyerp-server.py
index 3bef58fb0c9..e5d9f2626b3 100755
--- a/bin/tinyerp-server.py
+++ b/bin/tinyerp-server.py
@@ -50,7 +50,7 @@ __builtin__.__dict__['tinyerp_version_string'] = "Tiny ERP Server " + __version_
#----------------------------------------------------------
# python imports
#----------------------------------------------------------
-import sys,os,signal
+import sys, os, signal
#----------------------------------------------------------
# ubuntu 8.04 has obsoleted `pyxml` package and installs here.
# the path needs to be updated before any `import xml`
@@ -75,9 +75,9 @@ logger = netsvc.Logger()
import tools
import time
-if sys.platform=='win32':
+if sys.platform == 'win32':
import mx.DateTime
- mx.DateTime.strptime = lambda x,y: mx.DateTime.mktime(time.strptime(x, y))
+ mx.DateTime.strptime = lambda x, y: mx.DateTime.mktime(time.strptime(x, y))
#os.chdir(tools.file_path_root)
@@ -104,9 +104,9 @@ try:
except psycopg.OperationalError, err:
logger.notifyChannel("init", netsvc.LOG_ERROR, "could not connect to database '%s'!" % (tools.config["db_name"],))
- msg = str(err).replace("FATAL:","").strip()
+ msg = str(err).replace("FATAL:", "").strip()
db_msg = "database \"%s\" does not exist" % (tools.config["db_name"],)
-
+
# Note: this is ugly but since psycopg only uses one exception for all errors
# I don't think it's possible to do differently
if msg == db_msg:
@@ -165,7 +165,7 @@ if tools.config['upgrade']:
tools.config['update'][m] = 1
#----------------------------------------------------------
-# import basic modules
+# import basic modules
#----------------------------------------------------------
import osv, workflow, report, service
@@ -184,13 +184,17 @@ if tools.config['init'] or tools.config['update']:
if tools.config["translate_out"]:
import csv
- logger.notifyChannel("init", netsvc.LOG_INFO, 'writing translation file for language %s to %s' % (tools.config["language"], tools.config["translate_out"]))
+ if tools.config["language"]:
+ 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"]))
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 succesfully')
sys.exit(0)
@@ -217,12 +221,12 @@ if tools.config['xmlrpc']:
sys.exit(1)
interface = tools.config["interface"]
secure = tools.config["secure"]
-
- httpd = netsvc.HttpDaemon(interface,port, secure)
-
+
+ httpd = netsvc.HttpDaemon(interface, port, secure)
+
if tools.config["xmlrpc"]:
xml_gw = netsvc.xmlrpc.RpcGateway('web-services')
- httpd.attach("/xmlrpc", xml_gw )
+ httpd.attach("/xmlrpc", xml_gw)
logger.notifyChannel("web-services", netsvc.LOG_INFO,
"starting XML-RPC" + \
(tools.config['secure'] and ' Secure' or '') + \
@@ -242,10 +246,11 @@ if tools.config['netrpc']:
logger.notifyChannel("init", netsvc.LOG_ERROR, "invalid port '%s'!" % (tools.config["netport"],))
sys.exit(1)
netinterface = tools.config["netinterface"]
-
+
tinySocket = netsvc.TinySocketServerThread(netinterface, netport, False)
logger.notifyChannel("web-services", netsvc.LOG_INFO, "starting netrpc service, port "+str(netport))
+
def handler(signum, frame):
from tools import config
if tools.config['netrpc']:
@@ -259,8 +264,8 @@ def handler(signum, frame):
from tools import config
if config['pidfile']:
- fd=open(config['pidfile'], 'w')
- pidtext="%d" % (os.getpid())
+ fd = open(config['pidfile'], 'w')
+ pidtext = "%d" % (os.getpid())
fd.write(pidtext)
fd.close()
diff --git a/bin/tools/config.py b/bin/tools/config.py
index f4a3674e67e..df01bf50e3e 100644
--- a/bin/tools/config.py
+++ b/bin/tools/config.py
@@ -58,12 +58,13 @@ class configmanager(object):
'addons_path': None,
'root_path': None,
'debug_mode': False,
- 'commit_mode': False,
+ 'import_partial': "",
'pidfile': None,
'logfile': None,
'secure': False,
'smtp_server': 'localhost',
'smtp_user': False,
+ 'smtp_port':25,
'smtp_password': False,
'stop_after_init': False, # this will stop the server after initialization
'price_accuracy': 2,
@@ -96,8 +97,10 @@ class configmanager(object):
parser.add_option('--debug', dest='debug_mode', action='store_true', default=False, help='enable debug mode')
parser.add_option("--assert-exit-level", dest='assert_exit_level', help="specify the level at which a failed assertion will stop the server " + str(assert_exit_levels))
parser.add_option("-S", "--secure", dest="secure", action="store_true", help="launch server over https instead of http", default=False)
+
parser.add_option('--email-from', dest='email_from', default='', help='specify the SMTP email address for sending email')
parser.add_option('--smtp', dest='smtp_server', default='', help='specify the SMTP server for sending email')
+ parser.add_option('--smtp-port', dest='smtp_port', default='25', help='specify the SMTP port')
parser.add_option('--smtp-ssl', dest='smtp_ssl', default='', help='specify the SMTP server support SSL or not')
parser.add_option('--smtp-user', dest='smtp_user', default='', help='specify the SMTP username for sending email')
parser.add_option('--smtp-password', dest='smtp_password', default='', help='specify the SMTP password for sending email')
@@ -114,23 +117,26 @@ class configmanager(object):
group.add_option("--db_host", dest="db_host", help="specify the database host")
group.add_option("--db_port", dest="db_port", help="specify the database port")
group.add_option("--db_maxconn", dest="db_maxconn", default='64', help="specify the the maximum number of physical connections to posgresql")
- group.add_option("-C", "--commit-mode", dest="commit_mode", action="store_true", help="Several commit during one file importation. Use this for big data importation.", default=False)
+ group.add_option("-P", "--import-partial", dest="import_partial", help="Use this for big data importation, if it crashes you will be able to continue at the current state. Provide a filename to store intermediate importation states.", default=False)
parser.add_option_group(group)
group = optparse.OptionGroup(parser, "Internationalisation options",
"Use these options to translate Tiny ERP to another language."
- "See i18n section of the user manual. Options '-l' and '-d' are mandatory.")
+ "See i18n section of the user manual. Option '-d' is mandatory."
+ "Option '-l' is mandatory in case of importation"
+ )
- group.add_option('-l', "--language", dest="language", help="specify the language of the translation file. Use it with --i18n-export and --i18n-import")
- group.add_option("--i18n-export", dest="translate_out", help="export all sentences to be translated to a CSV or a PO file and exit")
- group.add_option("--i18n-import", dest="translate_in", help="import a CSV or a PO file with translations and exit")
+ group.add_option('-l', "--language", dest="language", help="specify the language of the translation file. Use it with --i18n-export or --i18n-import")
+ group.add_option("--i18n-export", dest="translate_out", help="export all sentences to be translated to a CSV file, a PO file or a TGZ archive and exit")
+ group.add_option("--i18n-import", dest="translate_in", help="import a CSV or a PO file with translations and exit. The '-l' option is required.")
group.add_option("--modules", dest="translate_modules", help="specify modules to export. Use in combination with --i18n-export")
group.add_option("--addons-path", dest="addons_path", help="specify an alternative addons path.")
parser.add_option_group(group)
(opt, args) = parser.parse_args()
- assert not ((opt.translate_in or opt.translate_out) and (not opt.language or not opt.db_name)), "the i18n-import and i18n-export options cannot be used without the language (-l) and database (-d) options"
+ assert not (opt.translate_in and (not opt.language or not opt.db_name)), "the i18n-import option cannot be used without the language (-l) and the database (-d) options"
+ assert not (opt.translate_out and (not opt.db_name)), "the i18n-export option cannot be used without the database (-d) option"
# place/search the config file on Win32 near the server installation
# (../etc from the server)
@@ -153,7 +159,7 @@ class configmanager(object):
self.options['pidfile'] = False
for arg in ('interface', 'port', 'db_name', 'db_user', 'db_password', 'db_host',
- 'db_port', 'logfile', 'pidfile', 'secure', 'smtp_ssl', 'email_from', 'smtp_server', 'smtp_user', 'smtp_password', 'price_accuracy', 'netinterface', 'netport', 'db_maxconn', 'commit_mode', 'addons_path'):
+ 'db_port', 'logfile', 'pidfile', 'secure', 'smtp_ssl', 'smtp_port', 'email_from', 'smtp_server', 'smtp_user', 'smtp_password', 'price_accuracy', 'netinterface', 'netport', 'db_maxconn', 'import_partial', 'addons_path'):
if getattr(opt, arg):
self.options[arg] = getattr(opt, arg)
diff --git a/bin/tools/convert.py b/bin/tools/convert.py
index bca44cb349f..7588ca2f1cc 100644
--- a/bin/tools/convert.py
+++ b/bin/tools/convert.py
@@ -42,6 +42,7 @@ from config import config
import logging
from lxml import etree
+import pickle
class ConvertError(Exception):
@@ -657,7 +658,7 @@ form: module.record_id""" % (xml_id,)
id = self.pool.get('ir.model.data')._update(cr, self.uid, rec_model, self.module, res, rec_id or False, not self.isnoupdate(data_node), noupdate=self.isnoupdate(data_node), mode=self.mode )
if rec_id:
self.idref[rec_id] = int(id)
- if config.get('commit_mode', False):
+ if config.get('import_partial', False):
cr.commit()
return rec_model, id
@@ -728,6 +729,20 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
reader = csv.reader(input, quotechar='"', delimiter=',')
fields = reader.next()
+ fname_partial = ""
+ if config.get('import_partial'):
+ fname_partial = module + '/'+ fname
+ if not os.path.isfile(config.get('import_partial')):
+ pickle.dump({}, file(config.get('import_partial'),'w+'))
+ else:
+ data = pickle.load(file(config.get('import_partial')))
+ if fname_partial in data:
+ if not data[fname_partial]:
+ return
+ else:
+ for i in range(data[fname_partial]):
+ reader.next()
+
if not (mode == 'init' or 'id' in fields):
return
@@ -737,7 +752,12 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
if (not line) or not reduce(lambda x,y: x or y, line) :
continue
datas.append( map(lambda x:x.decode('utf8').encode('utf8'), line))
- pool.get(model).import_data(cr, uid, fields, datas,mode, module,noupdate)
+ pool.get(model).import_data(cr, uid, fields, datas,mode, module,noupdate,filename=fname_partial)
+
+ if config.get('import_partial'):
+ data = pickle.load(file(config.get('import_partial')))
+ data[fname_partial] = 0
+ pickle.dump(data, file(config.get('import_partial'),'wb'))
#
# xml import/export
diff --git a/bin/tools/misc.py b/bin/tools/misc.py
index 3c6d726a84e..e02785efa18 100644
--- a/bin/tools/misc.py
+++ b/bin/tools/misc.py
@@ -48,6 +48,8 @@ if sys.version_info[:2] < (2, 4):
else:
from threading import local
+from itertools import izip
+
# initialize a database with base/base.sql
def init_db(cr):
import addons
@@ -288,7 +290,22 @@ def flatten(list):
r.append(e)
return r
-
+def reverse_enumerate(l):
+ """Like enumerate but in the other sens
+ >>> a = ['a', 'b', 'c']
+ >>> it = reverse_enumerate(a)
+ >>> it.next()
+ (2, 'c')
+ >>> it.next()
+ (1, 'b')
+ >>> it.next()
+ (0, 'a')
+ >>> it.next()
+ Traceback (most recent call last):
+ File "", line 1, in
+ StopIteration
+ """
+ return izip(xrange(len(l)-1, -1, -1), reversed(l))
#----------------------------------------------------------
# Emails
@@ -321,7 +338,15 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
msg['Message-Id'] = '<'+str(time.time())+'-tinycrm-'+str(tinycrm)+'@'+socket.gethostname()+'>'
try:
s = smtplib.SMTP()
- s.connect(config['smtp_server'])
+
+ if debug:
+ s.debuglevel = 5
+ if ssl:
+ s.ehlo()
+ s.starttls()
+ s.ehlo()
+
+ s.connect(config['smtp_server'], config['smtp_port'])
if config['smtp_user'] or config['smtp_password']:
s.login(config['smtp_user'], config['smtp_password'])
s.sendmail(email_from, flatten([email_to, email_cc, email_bcc]), msg.as_string())
@@ -386,7 +411,7 @@ def email_send_attach(email_from, email_to, subject, body, email_cc=None, email_
s.starttls()
s.ehlo()
- s.connect(config['smtp_server'])
+ s.connect(config['smtp_server'], config['smtp_port'])
if config['smtp_user'] or config['smtp_password']:
s.login(config['smtp_user'], config['smtp_password'])
s.sendmail(email_from, flatten([email_to, email_cc, email_bcc]), msg.as_string())
diff --git a/bin/tools/translate.py b/bin/tools/translate.py
index ac31af343d8..c585367b40f 100644
--- a/bin/tools/translate.py
+++ b/bin/tools/translate.py
@@ -37,6 +37,8 @@ import netsvc
from tools.misc import UpdateableStr
import inspect
import mx.DateTime as mxdt
+import tempfile
+import tarfile
class UNIX_LINE_TERMINATOR(csv.excel):
lineterminator = '\n'
@@ -78,6 +80,7 @@ class TinyPoFile(object):
self.buffer.seek(0)
self.lines = self.buffer.readlines()
self.first = True
+ self.tnrs= []
return self
def next(self):
@@ -86,41 +89,51 @@ class TinyPoFile(object):
.replace('\\"', "\"")
type = name = res_id = source = trad = None
-
- line = None
- while not line:
- if 0 == len(self.lines):
- raise StopIteration()
- line = self.lines.pop(0).strip()
- while line.startswith('#'):
- if line.startswith('#:'):
- type, name, res_id = line[2:].strip().split(':')
- line = self.lines.pop(0).strip()
- if not line.startswith('msgid'):
- raise Exception("malformed file")
- source = line[7:-1]
- line = self.lines.pop(0).strip()
- if not source and self.first:
- # if the source is "" and it's the first msgid, it's the special
- # msgstr with the informations about the traduction and the
- # traductor; we skip it
- while line:
+ if self.tnrs:
+ type, name, res_id, source, trad = self.tnrs.pop(0)
+ else:
+ tmp_tnrs = []
+ line = None
+ while not line:
+ if 0 == len(self.lines):
+ raise StopIteration()
line = self.lines.pop(0).strip()
- return next()
- while not line.startswith('msgstr'):
- if not line:
- raise Exception('malformed file')
- source += unquote(line)
+ while line.startswith('#'):
+ if line.startswith('#:'):
+ tmp_tnrs.append( line[2:].strip().split(':') )
+ line = self.lines.pop(0).strip()
+ if not line.startswith('msgid'):
+ raise Exception("malformed file")
+ source = line[7:-1]
line = self.lines.pop(0).strip()
+ if not source and self.first:
+ # if the source is "" and it's the first msgid, it's the special
+ # msgstr with the informations about the traduction and the
+ # traductor; we skip it
+ self.tnrs = []
+ while line:
+ line = self.lines.pop(0).strip()
+ return next()
+
+ while not line.startswith('msgstr'):
+ if not line:
+ raise Exception('malformed file')
+ source += unquote(line)
+ line = self.lines.pop(0).strip()
- trad = line[8:-1]
- line = self.lines.pop(0).strip()
- while line:
- trad += unquote(line)
+ trad = line[8:-1]
line = self.lines.pop(0).strip()
-
+ while line:
+ trad += unquote(line)
+ line = self.lines.pop(0).strip()
+
+ if tmp_tnrs:
+ type, name, res_id = tmp_tnrs.pop(0)
+ for t, n, r in tmp_tnrs:
+ self.tnrs.append((t, n, r, source, trad))
+
self.first = False
return type, name, res_id, source, trad
@@ -152,37 +165,79 @@ class TinyPoFile(object):
}
)
- def write(self, module, type, name, res_id, source, trad):
+ def write(self, modules, tnrs, source, trad):
def quote(str):
return '"%s"' % str.replace('"','\\"') \
.replace('\n', '\\n"\n"')
- self.buffer.write("#. module: %s\n" \
+ plurial = len(modules) > 1 and 's' or ''
+ self.buffer.write("#. module%s: %s\n" \
"#, python-format\n" \
- "#: %s:%s:%s\n" \
- "msgid %s\n" \
+ % (plurial, ', '.join(modules)))
+
+ for type, name, res_id in tnrs:
+ self.buffer.write("#: %s:%s:%s\n" % (type, name, str(res_id)))
+
+ self.buffer.write("msgid %s\n" \
"msgstr %s\n\n" \
- % (module, type, name, str(res_id), quote(source), quote(trad))
- )
+ % (quote(source), quote(trad))
+ )
# Methods to export the translation file
def trans_export(lang, modules, buffer, format, dbname=None):
+
+ def _process(format, modules, rows, buffer, lang, newlang):
+ if format == 'csv':
+ writer=csv.writer(buffer, 'UNIX')
+ for row in rows:
+ writer.writerow(row)
+ elif format == 'po':
+ rows.pop(0)
+ writer = tools.TinyPoFile(buffer)
+ writer.write_infos(modules)
+
+ # we now group the translations by source. That means one translation per source.
+ grouped_rows = {}
+ for module, type, name, res_id, src, trad in rows:
+ row = grouped_rows.setdefault(src, {})
+ row.setdefault('modules', set()).add(module)
+ row.setdefault('translation', trad)
+ row.setdefault('tnrs', []).append((type, name, res_id))
+
+ for src, row in grouped_rows.items():
+ writer.write(row['modules'], row['tnrs'], src, row['translation'])
+
+ elif format == 'tgz':
+ rows.pop(0)
+ rows_by_module = {}
+ for row in rows:
+ module = row[0]
+ rows_by_module.setdefault(module, []).append(row)
+
+ tmpdir = tempfile.mkdtemp()
+ for mod, modrows in rows_by_module.items():
+ tmpmoddir = join(tmpdir, mod, 'i18n')
+ os.makedirs(tmpmoddir)
+ pofilename = (newlang and mod or lang) + ".po" + (newlang and 't' or '')
+ buf = open(join(tmpmoddir, pofilename), 'w')
+ _process('po', [mod], modrows, buf, lang, newlang)
+
+ tar = tarfile.open(fileobj=buffer, mode='w|gz')
+ tar.add(tmpdir, '/')
+ tar.close()
+
+ else:
+ raise Exception(_('Bad file format'))
+
+ newlang = not bool(lang)
+ if newlang:
+ lang = 'en_US'
trans = trans_generate(lang, modules, dbname)
- if format == 'csv':
- writer=csv.writer(buffer, 'UNIX')
- for row in trans:
- writer.writerow(row)
- elif format == 'po':
- trans.pop(0)
- writer = tools.TinyPoFile(buffer)
- writer.write_infos(modules)
- for module, type, name, res_id, src, trad in trans:
- writer.write(module, type, name, res_id, src, trad)
- else:
- raise Exception(_('Bad file format'))
+ modules = set([t[0] for t in trans[1:]])
+ _process(format, modules, trans, buffer, lang, newlang)
del trans
diff --git a/doc/COPYING b/doc/COPYING
index e69de29bb2d..960fe7469fe 100644
--- a/doc/COPYING
+++ b/doc/COPYING
@@ -0,0 +1,280 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
diff --git a/doc/README.pychart b/doc/README.pychart
index e69de29bb2d..88ae2156810 100644
--- a/doc/README.pychart
+++ b/doc/README.pychart
@@ -0,0 +1,68 @@
+* What is this package?
+
+PyChart is a Python library for creating professional quality PS, PDF,
+PNG, or SVG charts. It supports line plots, bar plots, range-fill
+plots, and pie charts. Because it is based on Python, you can make
+the full use of Python's scripting power.
+
+The PyChart home page is at
+http://www.hpl.hp.com/personal/Yasushi_Saito/pychart
+
+* What you need
+
+You need Python (http://www.python.org) version 2.2 or later (on
+Debian GNU/Linux, you need, python-xml package as well). You also need
+Ghostscript (http://www.cs.wisc.edu/~ghost/) to produce PNG
+files.
+
+ Pychart used to require Ghostscript during PS and PDF
+ generation to obtain font metric information, but that's no
+ longer the case.
+
+* Platforms supported
+
+In theory, PyChart works on any platform with Python. In practice,
+I've run PyChart on Linux and Win2000.
+
+* Installation
+
+Become a root and do:
+
+ # python setup.py install
+
+Or, you can set PYTHONPATH environment variable before you start
+Python. For example (in bash):
+
+% PYTHONPATH=~/PyChart-1.33/pychart python mumbo.py
+
+* Documentation
+
+A detailed documentation is found in doc/pychart.
+
+* Examples
+
+All the *.py files in the demos/ directory can be run directly. For
+example,
+
+ setenv PYTHONPATH ..
+ python linetest.py >foo.eps
+ gs foo.eps
+
+or
+
+ setenv PYTHONPATH ..
+ setenv PYCHART_OPTIONS="format=pdf"
+ python linetest.py >foo.pdf
+ acroread foo.pdf
+
+* About the author
+
+Yasushi Saito (ysaito@hpl.hp.com), a full-time researcher and
+part-time hacker. This program is created mainly to serve my personal
+needs to write pretty charts for research papers. As such, it is
+updated only when I'm writing a paper, which happens about once every
+half year or so.
+
+Anyway, if you have comments, requests, or (even better)
+fixes/enhancements, feel free to email me.
+