bzr revid: jean-baptiste.aubort@camptocamp.com-20080822144626-kblv6krdoo4pema2
This commit is contained in:
Jean-Baptiste Aubort
2008-08-22 16:46:26 +02:00
43 changed files with 2033 additions and 1000 deletions
+21 -17
View File
@@ -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')")
-1
View File
@@ -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,
+4
View File
@@ -32,6 +32,10 @@
<record model="res.groups" id="group_extended">
<field name="name">Extended View</field>
</record>
<record model="res.groups" id="group_no_one">
<field name="name">No One</field>
</record>
<!--
Users
+1 -1
View File
@@ -31,9 +31,9 @@
import ir_sequence
import ir_ui_menu
import ir_ui_view
import ir_actions
import ir_default
import ir_model
import ir_actions
import ir_report_custom
import ir_attachment
import ir_cron
Regular → Executable
+85 -9
View File
@@ -46,6 +46,7 @@
<field name="res_model">ir.values</field>
<field name="view_type">form</field>
<field name="view_id" ref="values_view_tree"/>
<field name="context">{'read':'default'}</field>
</record>
<menuitem id="next_id_4" name="Low Level" parent="menu_custom"/>
@@ -130,6 +131,7 @@
<field name="name" select="1"/>
<field name="type" select="1"/>
<field name="usage"/>
<field name="parent_id"/>
</form>
</field>
</record>
@@ -391,6 +393,44 @@
</record>
<menuitem action="action_res_groups" id="menu_action_res_groups" parent="base.menu_users"/>
<record id="view_roles_form" model="ir.ui.view">
<field name="name">res.roles.form</field>
<field name="model">res.roles</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Role">
<field colspan="4" name="name" select="1"/>
<field colspan="4" name="parent_id"/>
</form>
</field>
</record>
<record id="view_roles_tree" model="ir.ui.view">
<field name="name">res.roles.tree</field>
<field name="model">res.roles</field>
<field name="type">tree</field>
<field name="field_parent">child_id</field>
<field name="arch" type="xml">
<tree string="Roles">
<field name="name"/>
</tree>
</field>
</record>
<record id="action_res_roles" model="ir.actions.act_window">
<field name="name">Roles Structure</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.roles</field>
<field name="view_type">tree</field>
<field eval="[('parent_id','=',False)]" name="domain"/>
</record>
<menuitem action="action_res_roles" id="menu_action_res_roles" parent="base.menu_users"/>
<record id="action_res_roles_form" model="ir.actions.act_window">
<field name="name">Roles</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.roles</field>
<field name="view_type">form</field>
</record>
<menuitem action="action_res_roles_form" id="menu_action_res_roles_form" parent="menu_action_res_roles"/>
<!-- View -->
<record id="view_view_form" model="ir.ui.view">
@@ -799,7 +839,8 @@ Menu Edition
<field name="sequence"/>
<field colspan="4" name="parent_id" select="1"/>
<field name="action"/>
<field name="icon"/>
<field name="icon" on_change="onchange_icon(icon)"/>
<field name="icon_pict" widget="picture" nolabel="1"/>
</page>
<page string="Groups">
<field colspan="4" name="groups_id"/>
@@ -1129,22 +1170,42 @@ Cron Jobs
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Server Action">
<field name="name" select="1"/>
<field name="name" select="1"/>
<field name="state" select="1"/>
<field name="model_id" select="1"/>
<field name="sequence" select="2"/>
<field name="code" colspan="4"/>
<field name="trigger_name" select="2"/>
<field name="trigger_object" select="2"/>
<field name="trigger_object_id"/>
<field name="message" select="2" colspan="4"/>
<field name="address" select="2"/>
<field name="child_ids" colspan="4"/>
<notebook colspan="4">
<page string="Python Code" attrs="{'invisible':[('state','!=','python')]}">
<separator colspan="4" string="Python code"/>
<field name="code" colspan="4" nolabel="1" />
</page>
<page string="Trigger" attrs="{'invisible':[('state','!=','trigger')]}">
<separator colspan="4" string="Trigger Configuration"/>
<field name="trigger_obj_id" select="2" colspan="4"/>
<field name="trigger_name" select="2"/>
</page>
<page string="Email" attrs="{'invisible':[('state','=','python'),('state','=','dummy'),('state','=','trigger'), ('state','=','object_create'), ('state','=','object_write'), ('state','=','client_action'), ('state','=','other')]}">
<separator colspan="4" string="Email Configuration"/>
<field name="address" domain="[('model_id','=',model_id)]"/>
<field name="message" select="2" colspan="4"/>
<newline/>
<label colspan="4" string="Use o to access the current object, you can use the any attributes which is belongs to current selected obect with o, i.e. o.partner_id.name for Invoice Object"/>
</page>
<page string="Other Actions" attrs="{'invisible':[('state','!=','other')]}">
<separator colspan="4" string="Other Actions Configuration"/>
<field name="child_ids" nolabel="1" colspan="4" />
</page>
</notebook>
<field name="usage"/>
<field name="type"/>
</form>
</field>
</record>
<record id="view_server_action_tree" model="ir.ui.view">
<field name="name">Server Actions</field>
@@ -1164,8 +1225,23 @@ Cron Jobs
<field name="res_model">ir.actions.server</field>
<field name="view_type">form</field>
<field name="view_id" ref="view_server_action_tree"/>
<field name="context">{'key':'server_action'}</field>
</record>
<menuitem action="action_server_action" id="menu_server_action" parent="base.next_id_6"/>
<record id="view_model_fields_tree" model="ir.ui.view">
<field name="name">ir.model.fields.tree</field>
<field name="model">ir.model.fields</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Fields">
<field name="complete_name"/>
<field name="model_id"/>
<field name="name"/>
<field name="field_description"/>
</tree>
</field>
</record>
</data>
</terp>
Regular → Executable
+132 -13
View File
@@ -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()
+1 -1
View File
@@ -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)
}
+1 -1
View File
@@ -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')
+2 -2
View File
@@ -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),
+10 -10
View File
@@ -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:
+1 -1
View File
@@ -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),
+1 -1
View File
@@ -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'),
+1 -1
View File
@@ -34,7 +34,7 @@ import tools
TRANSLATION_TYPE = [
('field', 'Field'),
('model', 'Model'),
('model', 'Object'),
('rml', 'RML'),
('selection', 'Selection'),
('view', 'View'),
+5 -5
View File
@@ -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):
+1 -1
View File
@@ -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'),
+1 -1
View File
@@ -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'),
+3 -3
View File
@@ -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"),
}
+11 -25
View File
@@ -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
+2 -5
View File
@@ -79,15 +79,12 @@
<newline/>
<field name="state" readonly="1" select="1"/>
<group col="6" colspan="2">
<button name="button_install" states="uninstalled" string="Install" type="object"/>
<button name="button_install" states="uninstalled" string="Schedule for Installation" type="object"/>
<button name="button_install_cancel" states="to install" string="Cancel Install" type="object"/>
<button name="button_uninstall" states="installed" string="Uninstall (beta)" type="object"/>
<button name="button_uninstall_cancel" states="to remove" string="Cancel Uninstall" type="object"/>
<button name="button_upgrade" states="installed" string="Upgrade" type="object"/>
<button name="button_upgrade" states="installed" string="Schedule Upgrade" type="object"/>
<button name="button_upgrade_cancel" states="to upgrade" string="Cancel Upgrade" type="object"/>
<!--
<button string="Update translations" name="button_update_translations" type="object" states="installed"/>
-->
</group>
</page>
<page string="Dependencies">
+1 -1
View File
@@ -13,7 +13,7 @@
</record>
<menuitem action="wizard_update" icon="STOCK_CONVERT" id="menu_module_update" parent="base.menu_management" type="wizard"/>
<wizard id="wizard_upgrade" model="ir.module.module" name="module.upgrade" string="Apply upgrades"/>
<wizard id="wizard_upgrade" model="ir.module.module" name="module.upgrade" string="Apply Scheduled Upgrades"/>
<menuitem action="wizard_upgrade" id="menu_wizard_upgrade" parent="base.menu_management" type="wizard"/>
<record id="wizard_lang_install" model="ir.actions.wizard">
@@ -37,81 +37,14 @@ import pooler
from osv import fields,osv
'''
view_form_init="""<?xml version="1.0"?>
<form string="Export language">
<image name="gtk-dialog-info" colspan="2"/>
<group colspan="2" col="4">
<separator string="Export translation file" colspan="4"/>
<label align="0.0" string="Choose a language to export:" colspan="4"/>
<field name="lang" colspan="4"/>
</group>
</form>"""
view_form_finish="""<?xml version="1.0"?>
<form string="Export language">
<image name="gtk-dialog-info" colspan="2"/>
<group colspan="2" col="4">
<separator string="Export done" colspan="4"/>
<field name="data" readonly="1" colspan="3"/>
<label align="0.0" string="Save this document to a .CSV file and open it with\n your favourite spreadsheet software. The file\n encoding is UTF-8. You have to translate the latest\n column before reimporting it." colspan="4"/>
</group>
</form>"""
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),
@@ -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)
+1
View File
@@ -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'),
+1 -2
View File
@@ -279,8 +279,7 @@
<page string="Event History">
<field colspan="4" name="events" nolabel="1" widget="one2many_list"/>
</page>
<page groups="base.group_extended" string="Properties">
<properties/>
<page string="Properties">
</page>
</notebook>
</form>
+2 -2
View File
@@ -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
+4 -5
View File
@@ -60,8 +60,7 @@
<field name="name">Requests</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.request</field>
<field name="view_type">form</field>
<field name="view_id" ref="res_request_tree-view"/>
<field name="view_id" eval="False"/>
</record>
<menuitem id="next_id_12" name="Requests" parent="base.next_id_4"/><menuitem action="res_request-act" id="menu_res_request_act" parent="next_id_12"/>
@@ -90,11 +89,11 @@
</record>
<record id="res_request_link-act" model="ir.actions.act_window">
<field name="name">Request</field>
<field name="name">Accepted Links in Requests</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.request.link</field>
<field name="view_type">form</field>
<field name="view_id" ref="res_request_link-view"/>
<field name="view_id" eval="False"/>
</record>
<menuitem action="res_request_link-act" id="menu_res_request_link_act" parent="base.next_id_12"/>
@@ -128,4 +127,4 @@
</field>
</record>
</data>
</terp>
</terp>
+300
View File
@@ -0,0 +1,300 @@
<?xml version="1.0" encoding="UTF-8"?>
<rng:grammar xmlns:rng="http://relaxng.org/ns/structure/1.0">
<rng:define name="form">
<rng:element name="form">
<rng:optional><rng:attribute name="string"/></rng:optional>
<rng:optional><rng:attribute name="col"/></rng:optional>
<rng:optional><rng:attribute name="editable"/></rng:optional>
<rng:optional><rng:attribute name="type"/></rng:optional>
<rng:optional><rng:attribute name="position"/></rng:optional>
<rng:zeroOrMore>
<rng:choice>
<rng:ref name="notebook"/>
<rng:ref name="field"/>
<rng:ref name="group"/>
<rng:ref name="button"/>
<rng:ref name="hpaned" />
<rng:ref name="label" />
<rng:ref name="separator"/>
<rng:ref name="image"/>
<rng:ref name="form"/>
<rng:element name="newline"><rng:empty/></rng:element>
<rng:element name="properties"><rng:empty/></rng:element>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:define name="tree">
<rng:element name="tree">
<rng:optional><rng:attribute name="string"/></rng:optional>
<rng:optional><rng:attribute name="colors"/></rng:optional>
<rng:optional><rng:attribute name="color"/></rng:optional>
<rng:optional><rng:attribute name="editable"/></rng:optional>
<rng:optional><rng:attribute name="toolbar"/></rng:optional>
<rng:optional><rng:attribute name="position"/></rng:optional>
<rng:zeroOrMore>
<rng:choice>
<rng:ref name="field"/>
<rng:ref name="separator"/>
<rng:ref name="tree"/>
<rng:ref name="group"/>
<rng:element name="newline"><rng:empty/></rng:element>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:define name="image">
<rng:element name="image">
<rng:attribute name="name"/>
</rng:element>
</rng:define>
<rng:define name="label">
<rng:element name="label">
<rng:optional><rng:attribute name="align"/></rng:optional>
<rng:optional><rng:attribute name="nolabel"/></rng:optional>
<rng:optional><rng:attribute name="colspan"/></rng:optional>
<rng:optional><rng:attribute name="string"/></rng:optional>
</rng:element>
</rng:define>
<rng:define name="hpaned">
<rng:element name="hpaned">
<rng:optional><rng:attribute name="position"/></rng:optional>
<rng:zeroOrMore>
<rng:ref name="any" />
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:define name="any">
<rng:element>
<rng:anyName/>
<rng:zeroOrMore>
<rng:choice>
<rng:attribute>
<rng:anyName/>
</rng:attribute>
<rng:text/>
<rng:ref name="any"/>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:define name="page">
<rng:element name="page">
<rng:optional><rng:attribute name="string"/></rng:optional>
<rng:optional><rng:attribute name="groups"/></rng:optional>
<rng:optional><rng:attribute name="attrs"/></rng:optional>
<rng:optional><rng:attribute name="position"/></rng:optional>
<rng:zeroOrMore>
<rng:choice>
<rng:element name="newline"><rng:empty/></rng:element>
<rng:element name="properties"><rng:empty/></rng:element>
<rng:ref name="label" />
<rng:ref name="field"/>
<rng:ref name="group"/>
<rng:ref name="separator"/>
<rng:ref name="button"/>
<rng:ref name="form" />
<rng:ref name="page" />
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:define name="notebook">
<rng:element name="notebook">
<rng:optional><rng:attribute name="position"/></rng:optional>
<rng:optional><rng:attribute name="colspan"/></rng:optional>
<rng:oneOrMore>
<rng:ref name="page"/>
</rng:oneOrMore>
</rng:element>
</rng:define>
<rng:define name="separator">
<rng:element name="separator">
<rng:optional><rng:attribute name="colspan"/></rng:optional>
<rng:optional><rng:attribute name="string"/></rng:optional>
<rng:optional><rng:attribute name="col"/></rng:optional>
<rng:optional><rng:attribute name="select"/></rng:optional>
<rng:optional><rng:attribute name="position"/></rng:optional>
</rng:element>
</rng:define>
<rng:define name="xpath">
<rng:element name="xpath">
<rng:optional><rng:attribute name="expr"/></rng:optional>
<rng:optional><rng:attribute name="position"/></rng:optional>
<rng:zeroOrMore>
<rng:choice>
<rng:ref name="notebook" />
<rng:ref name="graph" />
<rng:ref name="calendar" />
<rng:ref name="form"/>
<rng:ref name="tree"/>
<rng:ref name="field"/>
<rng:ref name="separator"/>
<rng:ref name="page"/>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:define name="field">
<rng:element name="field">
<rng:attribute name="name" />
<rng:optional><rng:attribute name="editable"/></rng:optional>
<rng:optional><rng:attribute name="attrs"/></rng:optional>
<rng:optional><rng:attribute name="string"/></rng:optional>
<rng:optional><rng:attribute name="completion"/></rng:optional>
<rng:optional><rng:attribute name="width"/></rng:optional>
<rng:optional><rng:attribute name="type"/></rng:optional>
<rng:optional><rng:attribute name="ref"/></rng:optional>
<rng:optional><rng:attribute name="eval"/></rng:optional>
<rng:optional><rng:attribute name="search"/></rng:optional>
<rng:optional><rng:attribute name="model"/></rng:optional>
<rng:optional><rng:attribute name="use"/></rng:optional>
<rng:optional><rng:attribute name="on_change"/></rng:optional>
<rng:optional><rng:attribute name="domain"/></rng:optional>
<rng:optional><rng:attribute name="invisible"/></rng:optional>
<rng:optional><rng:attribute name="visible"/></rng:optional>
<rng:optional><rng:attribute name="sum"/></rng:optional>
<rng:optional><rng:attribute name="select"/></rng:optional>
<rng:optional><rng:attribute name="group"/></rng:optional>
<rng:optional><rng:attribute name="operator"/></rng:optional>
<rng:optional><rng:attribute name="colspan"/></rng:optional>
<rng:optional><rng:attribute name="nolabel"/></rng:optional>
<rng:optional><rng:attribute name="default_get"/></rng:optional>
<rng:optional><rng:attribute name="required"/></rng:optional>
<rng:optional><rng:attribute name="readonly"/></rng:optional>
<rng:optional><rng:attribute name="view_mode"/></rng:optional>
<rng:optional><rng:attribute name="widget"/></rng:optional>
<rng:optional><rng:attribute name="context"/></rng:optional>
<rng:optional><rng:attribute name="groups"/></rng:optional>
<rng:optional><rng:attribute name="states"/></rng:optional>
<rng:optional><rng:attribute name="position"/></rng:optional>
<rng:optional><rng:attribute name="digits"/></rng:optional>
<rng:optional><rng:attribute name="icon"/></rng:optional>
<rng:optional><rng:attribute name="mode"/></rng:optional>
<rng:optional><rng:attribute name="size"/></rng:optional>
<rng:optional><rng:attribute name="height"/></rng:optional>
<rng:zeroOrMore>
<rng:choice>
<rng:ref name="form"/>
<rng:ref name="tree"/>
<rng:ref name="field"/>
<rng:ref name="separator"/>
<rng:ref name="xpath"/>
<rng:ref name="button"/>
<rng:ref name="group"/>
<rng:element name="newline"><rng:empty/></rng:element>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:define name="group">
<rng:element name="group">
<rng:optional><rng:attribute name="attrs"/></rng:optional>
<rng:optional><rng:attribute name="col"/></rng:optional>
<rng:optional><rng:attribute name="colspan"/></rng:optional>
<rng:optional><rng:attribute name="expand"/></rng:optional>
<rng:optional><rng:attribute name="states"/></rng:optional>
<rng:optional><rng:attribute name="groups"/></rng:optional>
<rng:optional><rng:attribute name="string"/></rng:optional>
<rng:optional><rng:attribute name="fill"/></rng:optional>
<rng:optional><rng:attribute name="height"/></rng:optional>
<rng:zeroOrMore>
<rng:choice>
<rng:ref name="separator"/>
<rng:ref name="button"/>
<rng:ref name="field"/>
<rng:ref name="label" />
<rng:ref name="group" />
<rng:element name="properties"><rng:empty/></rng:element>
<rng:element name="newline"><rng:empty/></rng:element>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:define name="calendar">
<rng:element name="calendar">
<rng:optional><rng:attribute name="string" /></rng:optional>
<rng:optional><rng:attribute name="color" /></rng:optional>
<rng:optional><rng:attribute name="date_start" /></rng:optional>
<rng:optional><rng:attribute name="date_stop" /></rng:optional>
<rng:optional><rng:attribute name="day_length" /></rng:optional>
<rng:optional><rng:attribute name="date_delay" /></rng:optional>
<rng:oneOrMore>
<rng:ref name="field"/>
</rng:oneOrMore>
</rng:element>
</rng:define>
<rng:define name="graph">
<rng:element name="graph">
<rng:optional><rng:attribute name="string" /></rng:optional>
<rng:optional><rng:attribute name="orientation" /></rng:optional>
<rng:optional><rng:attribute name="type" /></rng:optional>
<rng:oneOrMore>
<rng:ref name="field"/>
</rng:oneOrMore>
</rng:element>
</rng:define>
<rng:define name="button">
<rng:element name="button">
<rng:optional><rng:attribute name="attrs"/></rng:optional>
<rng:optional><rng:attribute name="name" /></rng:optional>
<rng:optional><rng:attribute name="icon" /></rng:optional>
<rng:optional><rng:attribute name="string" /></rng:optional>
<rng:optional><rng:attribute name="states" /></rng:optional>
<rng:optional><rng:attribute name="type" /></rng:optional>
<rng:optional><rng:attribute name="special" /></rng:optional>
<rng:optional><rng:attribute name="align" /></rng:optional>
<rng:optional><rng:attribute name="colspan"/></rng:optional>
<rng:optional><rng:attribute name="groups"/></rng:optional>
<rng:optional><rng:attribute name="target"/></rng:optional>
<rng:optional><rng:attribute name="readonly"/></rng:optional>
<rng:optional><rng:attribute name="position"/></rng:optional>
<rng:zeroOrMore>
<rng:choice>
<rng:ref name="form" />
<rng:ref name="field" />
<rng:ref name="tree" />
<rng:ref name="notebook" />
<rng:ref name="graph" />
<rng:ref name="calendar" />
<rng:ref name="xpath" />
<rng:ref name="page" />
<rng:ref name="separator"/>
<rng:ref name="button"/>
<rng:element name="properties"><rng:empty/></rng:element>
<rng:element name="newline"><rng:empty/></rng:element>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:start>
<rng:choice>
<rng:ref name="form" />
<rng:ref name="field" />
<rng:ref name="tree" />
<rng:ref name="notebook" />
<rng:ref name="graph" />
<rng:ref name="calendar" />
<rng:ref name="xpath" />
<rng:ref name="page" />
<rng:ref name="separator"/>
<rng:ref name="button"/>
</rng:choice>
</rng:start>
</rng:grammar>
+1
View File
@@ -158,6 +158,7 @@
<rng:optional> <rng:attribute name="sequence"/> </rng:optional>
<rng:optional> <rng:attribute name="groups"/> </rng:optional>
<rng:optional> <rng:attribute name="type"/> </rng:optional>
<rng:optional> <rng:attribute name="menu"/> </rng:optional>
<rng:empty />
</rng:element>
</rng:define>
+104 -88
View File
@@ -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:
+206 -249
View File
@@ -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<len(self.__exp):
i+=1
e = self.__exp[i]
if self._is_operator(e) or e == self.__DUMMY_LEAF:
continue
left, operator, right = e
working_table = table
if left in table._inherit_fields:
working_table = table.pool.get(table._inherit_fields[left][0])
if working_table not in self.__tables.values():
self.__joins.append(('%s.%s=%s.%s' % (working_table._table, 'id', table._table, table._inherits[working_table._name]), working_table._table))
self.__tables[i] = working_table
fargs = left.split('.', 1)
field = working_table._columns.get(fargs[0], False)
if not field:
if self.__left == 'id' and self.__operator == 'child_of':
self.__right += _rec_get(self.__right, self.__table, self.__table._parent_name)
self.__operator = 'in'
return self
if len(fargs) > 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:
+128 -106
View File
@@ -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
+411 -291
View File
File diff suppressed because it is too large Load Diff
+14 -9
View File
@@ -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))
+12 -1
View File
@@ -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'])
+7
View File
@@ -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):
+6 -1
View File
@@ -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]))
+20 -15
View File
@@ -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()
+14 -8
View File
@@ -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)
+22 -2
View File
@@ -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
+28 -3
View File
@@ -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 "<stdin>", line 1, in <module>
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())
+102 -47
View File
@@ -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
+280
View File
@@ -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
+68
View File
@@ -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.