[MERGE]merge with trunk
bzr revid: sgo@tinyerp.com-20121129052848-97zzqhddp1tgenvp bzr revid: sgo@tinyerp.com-20121204050625-5zu9xzsqtg5p56h9 bzr revid: sgo@tinyerp.com-20121205052033-01i94a3tun5umjyy bzr revid: sgo@tinyerp.com-20121206044540-gici94fhwkx1nghh
This commit is contained in:
@@ -149,7 +149,6 @@ CREATE TABLE res_users (
|
||||
active boolean default True,
|
||||
login varchar(64) NOT NULL UNIQUE,
|
||||
password varchar(64) default null,
|
||||
tz varchar(64) default null,
|
||||
lang varchar(64) default '',
|
||||
-- No FK references below, will be added later by ORM
|
||||
-- (when the destination rows exist)
|
||||
|
||||
@@ -88,6 +88,7 @@ gk//2Q==</field>
|
||||
|
||||
<record model="res.partner" id="base.partner_root">
|
||||
<field name="email">admin@example.com</field>
|
||||
<field name="tz">Europe/Brussels</field>
|
||||
<field name="image">/9j/4AAQSkZJRgABAQEBLAEsAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEP
|
||||
ERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4e
|
||||
Hh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCACmAKYDASIA
|
||||
|
||||
+279
-298
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+310
-313
File diff suppressed because it is too large
Load Diff
+258
-268
File diff suppressed because it is too large
Load Diff
+288
-302
File diff suppressed because it is too large
Load Diff
+280
-299
File diff suppressed because it is too large
Load Diff
+316
-314
File diff suppressed because it is too large
Load Diff
+324
-316
File diff suppressed because it is too large
Load Diff
+280
-299
File diff suppressed because it is too large
Load Diff
+468
-357
File diff suppressed because it is too large
Load Diff
+317
-315
File diff suppressed because it is too large
Load Diff
+323
-315
File diff suppressed because it is too large
Load Diff
+349
-327
File diff suppressed because it is too large
Load Diff
+288
-302
File diff suppressed because it is too large
Load Diff
+316
-314
File diff suppressed because it is too large
Load Diff
+336
-319
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+335
-318
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+287
-301
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+287
-301
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+337
-320
File diff suppressed because it is too large
Load Diff
+880
-387
File diff suppressed because it is too large
Load Diff
+317
-315
File diff suppressed because it is too large
Load Diff
+282
-298
File diff suppressed because it is too large
Load Diff
+287
-301
File diff suppressed because it is too large
Load Diff
+321
-313
File diff suppressed because it is too large
Load Diff
+1459
-450
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+359
-338
File diff suppressed because it is too large
Load Diff
+333
-316
File diff suppressed because it is too large
Load Diff
+338
-321
File diff suppressed because it is too large
Load Diff
+281
-300
File diff suppressed because it is too large
Load Diff
+314
-312
File diff suppressed because it is too large
Load Diff
+285
-299
File diff suppressed because it is too large
Load Diff
+287
-301
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+464
-334
File diff suppressed because it is too large
Load Diff
+288
-302
File diff suppressed because it is too large
Load Diff
+336
-319
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+331
-317
File diff suppressed because it is too large
Load Diff
+537
-519
File diff suppressed because it is too large
Load Diff
+765
-460
File diff suppressed because it is too large
Load Diff
+337
-320
File diff suppressed because it is too large
Load Diff
+554
-496
File diff suppressed because it is too large
Load Diff
+316
-314
File diff suppressed because it is too large
Load Diff
+320
-315
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+317
-315
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+313
-311
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+366
-344
File diff suppressed because it is too large
Load Diff
+287
-301
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+281
-300
File diff suppressed because it is too large
Load Diff
+892
-629
File diff suppressed because it is too large
Load Diff
+279
-298
File diff suppressed because it is too large
Load Diff
+287
-301
File diff suppressed because it is too large
Load Diff
@@ -502,6 +502,19 @@
|
||||
<menuitem id="menu_ir_actions_todo" name="Configuration Wizards" parent="menu_custom" sequence="20" groups="base.group_no_one"/>
|
||||
<menuitem id="menu_ir_actions_todo_form" action="act_ir_actions_todo_form" parent="menu_ir_actions_todo"/>
|
||||
|
||||
<record id="action_run_ir_action_todo" model="ir.actions.server">
|
||||
<field name="name">Run Remaining Action Todo</field>
|
||||
<field name="condition">True</field>
|
||||
<field name="type">ir.actions.server</field>
|
||||
<field name="model_id" ref="model_res_config"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">
|
||||
config = self.next(cr, uid, [], context=context) or {}
|
||||
if config.get('type') not in ('ir.actions.act_window_close',):
|
||||
action = config
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -64,10 +64,18 @@ class ir_attachment(osv.osv):
|
||||
return 0
|
||||
return []
|
||||
|
||||
# Work with a set, as list.remove() is prohibitive for large lists of documents
|
||||
# (takes 20+ seconds on a db with 100k docs during search_count()!)
|
||||
orig_ids = ids
|
||||
ids = set(ids)
|
||||
|
||||
# For attachments, the permissions of the document they are attached to
|
||||
# apply, so we must remove attachments for which the user cannot access
|
||||
# the linked document.
|
||||
targets = super(ir_attachment,self).read(cr, uid, ids, ['id', 'res_model', 'res_id'])
|
||||
# Use pure SQL rather than read() as it is about 50% faster for large dbs (100k+ docs),
|
||||
# and the permissions are checked in super() and below anyway.
|
||||
cr.execute("""SELECT id, res_model, res_id FROM ir_attachment WHERE id = ANY(%s)""", (list(ids),))
|
||||
targets = cr.dictfetchall()
|
||||
model_attachments = {}
|
||||
for target_dict in targets:
|
||||
if not (target_dict['res_id'] and target_dict['res_model']):
|
||||
@@ -92,9 +100,10 @@ class ir_attachment(osv.osv):
|
||||
for res_id in disallowed_ids:
|
||||
for attach_id in targets[res_id]:
|
||||
ids.remove(attach_id)
|
||||
if count:
|
||||
return len(ids)
|
||||
return ids
|
||||
|
||||
# sort result according to the original sort ordering
|
||||
result = [id for id in orig_ids if id in ids]
|
||||
return len(result) if count else list(result)
|
||||
|
||||
def read(self, cr, uid, ids, fields_to_read=None, context=None, load='_classic_read'):
|
||||
self.check(cr, uid, ids, 'read', context=context)
|
||||
|
||||
@@ -70,7 +70,10 @@ class ir_model(osv.osv):
|
||||
models = self.browse(cr, uid, ids, context=context)
|
||||
res = dict.fromkeys(ids)
|
||||
for model in models:
|
||||
res[model.id] = self.pool.get(model.model).is_transient()
|
||||
if self.pool.get(model.model):
|
||||
res[model.id] = self.pool.get(model.model).is_transient()
|
||||
else:
|
||||
_logger.error('Missing model %s' % (model.model, ))
|
||||
return res
|
||||
|
||||
def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
|
||||
@@ -206,6 +209,7 @@ class ir_model(osv.osv):
|
||||
else:
|
||||
x_name = a._columns.keys()[0]
|
||||
x_custom_model._rec_name = x_name
|
||||
a._rec_name = x_name
|
||||
|
||||
class ir_model_fields(osv.osv):
|
||||
_name = 'ir.model.fields'
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<field name="selection" attrs="{'required': [('ttype','in',['selection','reference'])], 'readonly': [('ttype','not in',['selection','reference'])]}"/>
|
||||
<field name="size" attrs="{'required': [('ttype','in',['char','reference'])], 'readonly': [('ttype','not in',['char','reference'])]}"/>
|
||||
<field name="domain" attrs="{'readonly': [('relation','=','')]}"/>
|
||||
<field name="serialization_field_id" attrs="{'readonly': [('state','=','base')]}" domain="[('ttype','=','serialized'), ('model_id', '=', model_id)]"/>
|
||||
<field name="serialization_field_id" attrs="{'readonly': [('state','=','base')]}" domain="[('ttype','=','serialized'), ('model_id', '=', parent.model)]"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="required"/>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2010-2011 OpenERP SA (<http://openerp.com>).
|
||||
# Copyright (C) 2010-2012 OpenERP SA (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
@@ -62,7 +62,7 @@ class ir_ui_menu(osv.osv):
|
||||
modelaccess = self.pool.get('ir.model.access')
|
||||
user_groups = set(self.pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['groups_id'])['groups_id'])
|
||||
result = []
|
||||
for menu in self.browse(cr, uid, ids, context=context):
|
||||
for menu in self.browse(cr, SUPERUSER_ID, ids, context=context):
|
||||
# this key works because user access rights are all based on user's groups (cfr ir_model_access.check)
|
||||
key = (cr.dbname, menu.id, tuple(user_groups))
|
||||
if key in self._cache:
|
||||
|
||||
@@ -20,7 +20,10 @@
|
||||
##############################################################################
|
||||
|
||||
import base64
|
||||
from docutils import io, nodes
|
||||
from docutils.core import publish_string
|
||||
from docutils.transforms import Transform, writer_aux
|
||||
from docutils.writers.html4css1 import Writer
|
||||
import imp
|
||||
import logging
|
||||
import re
|
||||
@@ -28,6 +31,7 @@ import urllib
|
||||
import zipimport
|
||||
|
||||
from openerp import modules, pooler, release, tools, addons
|
||||
from openerp.modules.db import create_categories
|
||||
from openerp.tools.parse_version import parse_version
|
||||
from openerp.tools.translate import _
|
||||
from openerp.osv import fields, osv, orm
|
||||
@@ -80,6 +84,32 @@ class module_category(osv.osv):
|
||||
'visible' : 1,
|
||||
}
|
||||
|
||||
class MyFilterMessages(Transform):
|
||||
"""
|
||||
Custom docutils transform to remove `system message` for a document and
|
||||
generate warnings.
|
||||
|
||||
(The standard filter removes them based on some `report_level` passed in
|
||||
the `settings_override` dictionary, but if we use it, we can't see them
|
||||
and generate warnings.)
|
||||
"""
|
||||
|
||||
default_priority = 870
|
||||
|
||||
def apply(self):
|
||||
for node in self.document.traverse(nodes.system_message):
|
||||
_logger.warning("docutils' system message present: %s", str(node))
|
||||
node.parent.remove(node)
|
||||
|
||||
class MyWriter(Writer):
|
||||
"""
|
||||
Custom docutils html4ccs1 writer that doesn't add the warnings to the
|
||||
output document.
|
||||
"""
|
||||
|
||||
def get_transforms(self):
|
||||
return [MyFilterMessages, writer_aux.Admonitions]
|
||||
|
||||
class module(osv.osv):
|
||||
_name = "ir.module.module"
|
||||
_rec_name = "shortdesc"
|
||||
@@ -100,7 +130,7 @@ class module(osv.osv):
|
||||
res = dict.fromkeys(ids, '')
|
||||
for module in self.browse(cr, uid, ids, context=context):
|
||||
overrides = dict(embed_stylesheet=False, doctitle_xform=False, output_encoding='unicode')
|
||||
output = publish_string(source=module.description, writer_name='html', settings_overrides=overrides)
|
||||
output = publish_string(source=module.description, settings_overrides=overrides, writer=MyWriter())
|
||||
res[module.id] = output
|
||||
return res
|
||||
|
||||
@@ -609,21 +639,8 @@ class module(osv.osv):
|
||||
|
||||
categs = category.split('/')
|
||||
if categs != current_category_path:
|
||||
p_id = None
|
||||
while categs:
|
||||
if p_id is not None:
|
||||
cr.execute('SELECT id FROM ir_module_category WHERE name=%s AND parent_id=%s', (categs[0], p_id))
|
||||
else:
|
||||
cr.execute('SELECT id FROM ir_module_category WHERE name=%s AND parent_id is NULL', (categs[0],))
|
||||
c_id = cr.fetchone()
|
||||
if not c_id:
|
||||
cr.execute('INSERT INTO ir_module_category (name, parent_id) VALUES (%s, %s) RETURNING id', (categs[0], p_id))
|
||||
c_id = cr.fetchone()[0]
|
||||
else:
|
||||
c_id = c_id[0]
|
||||
p_id = c_id
|
||||
categs = categs[1:]
|
||||
self.write(cr, uid, [mod_browse.id], {'category_id': p_id})
|
||||
cat_id = create_categories(cr, categs)
|
||||
mod_browse.write({'category_id': cat_id})
|
||||
|
||||
def update_translations(self, cr, uid, ids, filter_lang=None, context=None):
|
||||
if not filter_lang:
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
<field name="name">ir.module.module.tree</field>
|
||||
<field name="model">ir.module.module</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree colors="blue:state=='to upgrade' or state=='to install';red:state=='uninstalled';grey:state=='uninstallable';black:state=='installed'" string="Modules">
|
||||
<tree colors="blue:state=='to upgrade' or state=='to install';red:state=='uninstalled';grey:state=='uninstallable';black:state=='installed'" create="false" string="Modules">
|
||||
<field name="shortdesc"/>
|
||||
<field name="name" groups="base.group_no_one"/>
|
||||
<field name="author"/>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import datetime
|
||||
import math
|
||||
import openerp
|
||||
from osv import osv, fields
|
||||
@@ -178,6 +179,12 @@ class res_partner(osv.osv, format_address):
|
||||
result[obj.id] = tools.image_get_resized_images(obj.image)
|
||||
return result
|
||||
|
||||
def _get_tz_offset(self, cr, uid, ids, name, args, context=None):
|
||||
result = dict.fromkeys(ids, False)
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
result[obj.id] = datetime.datetime.now(pytz.timezone(obj.tz or 'GMT')).strftime('%z')
|
||||
return result
|
||||
|
||||
def _set_image(self, cr, uid, id, name, value, args, context=None):
|
||||
return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
|
||||
|
||||
@@ -195,6 +202,7 @@ class res_partner(osv.osv, format_address):
|
||||
help="The partner's timezone, used to output proper date and time values inside printed reports. "
|
||||
"It is important to set a value for this field. You should use the same timezone "
|
||||
"that is otherwise used to pick and render date and time values: your computer's timezone."),
|
||||
'tz_offset': fields.function(_get_tz_offset, type='char', size=5, string='Timezone offset', invisible=True),
|
||||
'user_id': fields.many2one('res.users', 'Salesperson', tracked=True, help='The internal user that is in charge of communicating with this contact if any.'),
|
||||
'vat': fields.char('TIN', size=32, help="Tax Identification Number. Check the box if this contact is subjected to taxes. Used by the some of the legal statements."),
|
||||
'bank_ids': fields.one2many('res.partner.bank', 'partner_id', 'Banks'),
|
||||
|
||||
@@ -345,8 +345,6 @@
|
||||
<li t-if="record.city.raw_value and !record.country.raw_value"><field name="city"/></li>
|
||||
<li t-if="!record.city.raw_value and record.country.raw_value"><field name="country"/></li>
|
||||
<li t-if="record.city.raw_value and record.country.raw_value"><field name="city"/>, <field name="country"/></li>
|
||||
<li t-if="record.phone.raw_value">Tel: <field name="phone"/></li>
|
||||
<li t-if="record.mobile.raw_value">Mobile: <field name="mobile"/></li>
|
||||
<li t-if="record.email.raw_value"><a t-attf-href="mailto:#{record.email.raw_value}"><field name="email"/></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -153,7 +153,7 @@ class res_users(osv.osv):
|
||||
help="Specify a value only when creating a user or if you're "\
|
||||
"changing the user's password, otherwise leave empty. After "\
|
||||
"a change of password, the user has to login again."),
|
||||
'signature': fields.text('Signature', size=64),
|
||||
'signature': fields.text('Signature'),
|
||||
'active': fields.boolean('Active'),
|
||||
'action_id': fields.many2one('ir.actions.actions', 'Home Action', help="If specified, this action will be opened at logon for this user, in addition to the standard menu."),
|
||||
'menu_id': fields.many2one('ir.actions.actions', 'Menu Action', help="If specified, the action will replace the standard menu for this user."),
|
||||
|
||||
@@ -50,20 +50,25 @@ class test_ir_values(common.TransactionCase):
|
||||
|
||||
# Create some action bindings for a non-existing model.
|
||||
|
||||
act_id_1 = self.ref('base.act_values_form_action')
|
||||
act_id_2 = self.ref('base.act_values_form_defaults')
|
||||
act_id_3 = self.ref('base.action_res_company_form')
|
||||
act_id_4 = self.ref('base.action_res_company_tree')
|
||||
|
||||
ir_values = self.registry('ir.values')
|
||||
ir_values.set(self.cr, self.uid, 'action', 'tree_but_open', 'OnDblClick Action', ['unexisting_model'], 'ir.actions.act_window,10', isobject=True)
|
||||
ir_values.set(self.cr, self.uid, 'action', 'tree_but_open', 'OnDblClick Action 2', ['unexisting_model'], 'ir.actions.act_window,11', isobject=True)
|
||||
ir_values.set(self.cr, self.uid, 'action', 'client_action_multi', 'Side Wizard', ['unexisting_model'], 'ir.actions.act_window,12', isobject=True)
|
||||
ir_values.set(self.cr, self.uid, 'action', 'tree_but_open', 'OnDblClick Action', ['unexisting_model'], 'ir.actions.act_window,%d' % act_id_1, isobject=True)
|
||||
ir_values.set(self.cr, self.uid, 'action', 'tree_but_open', 'OnDblClick Action 2', ['unexisting_model'], 'ir.actions.act_window,%d' % act_id_2, isobject=True)
|
||||
ir_values.set(self.cr, self.uid, 'action', 'client_action_multi', 'Side Wizard', ['unexisting_model'], 'ir.actions.act_window,%d' % act_id_3, isobject=True)
|
||||
report_ids = self.registry('ir.actions.report.xml').search(self.cr, self.uid, [], {})
|
||||
reports = self.registry('ir.actions.report.xml').browse(self.cr, self.uid, report_ids, {})
|
||||
report_id = [report.id for report in reports if not report.groups_id][0] # assume at least one
|
||||
ir_values.set(self.cr, self.uid, 'action', 'client_print_multi', 'Nice Report', ['unexisting_model'], 'ir.actions.report.xml,%s'%report_id, isobject=True)
|
||||
ir_values.set(self.cr, self.uid, 'action', 'client_action_relate', 'Related Stuff', ['unexisting_model'], 'ir.actions.act_window,14', isobject=True)
|
||||
report_id = [report.id for report in reports if not report.groups_id][0] # assume at least one
|
||||
ir_values.set(self.cr, self.uid, 'action', 'client_print_multi', 'Nice Report', ['unexisting_model'], 'ir.actions.report.xml,%d' % report_id, isobject=True)
|
||||
ir_values.set(self.cr, self.uid, 'action', 'client_action_relate', 'Related Stuff', ['unexisting_model'], 'ir.actions.act_window,%d' % act_id_4, isobject=True)
|
||||
|
||||
# Replace one action binding to set a new name.
|
||||
|
||||
ir_values = self.registry('ir.values')
|
||||
ir_values.set(self.cr, self.uid, 'action', 'tree_but_open', 'OnDblClick Action New', ['unexisting_model'], 'ir.actions.act_window,10', isobject=True)
|
||||
ir_values.set(self.cr, self.uid, 'action', 'tree_but_open', 'OnDblClick Action New', ['unexisting_model'], 'ir.actions.act_window,%d' % act_id_1, isobject=True)
|
||||
|
||||
# Retrieve the action bindings and check they're correct
|
||||
|
||||
@@ -73,17 +78,17 @@ class test_ir_values(common.TransactionCase):
|
||||
#first action
|
||||
assert len(actions[0]) == 3, "Malformed action definition"
|
||||
assert actions[0][1] == 'OnDblClick Action 2', 'Bound action does not match definition'
|
||||
assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 11, 'Bound action does not match definition'
|
||||
assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == act_id_2, 'Bound action does not match definition'
|
||||
#second action - this ones comes last because it was re-created with a different name
|
||||
assert len(actions[1]) == 3, "Malformed action definition"
|
||||
assert actions[1][1] == 'OnDblClick Action New', 'Re-Registering an action should replace it'
|
||||
assert isinstance(actions[1][2], dict) and actions[1][2]['id'] == 10, 'Bound action does not match definition'
|
||||
assert isinstance(actions[1][2], dict) and actions[1][2]['id'] == act_id_1, 'Bound action does not match definition'
|
||||
|
||||
actions = ir_values.get(self.cr, self.uid, 'action', 'client_action_multi', ['unexisting_model'])
|
||||
assert len(actions) == 1, "Mismatching number of bound actions"
|
||||
assert len(actions[0]) == 3, "Malformed action definition"
|
||||
assert actions[0][1] == 'Side Wizard', 'Bound action does not match definition'
|
||||
assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 12, 'Bound action does not match definition'
|
||||
assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == act_id_3, 'Bound action does not match definition'
|
||||
|
||||
actions = ir_values.get(self.cr, self.uid, 'action', 'client_print_multi', ['unexisting_model'])
|
||||
assert len(actions) == 1, "Mismatching number of bound actions"
|
||||
@@ -95,7 +100,7 @@ class test_ir_values(common.TransactionCase):
|
||||
assert len(actions) == 1, "Mismatching number of bound actions"
|
||||
assert len(actions[0]) == 3, "Malformed action definition"
|
||||
assert actions[0][1] == 'Related Stuff', 'Bound action does not match definition'
|
||||
assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 14, 'Bound action does not match definition'
|
||||
assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == act_id_4, 'Bound action does not match definition'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -131,12 +131,13 @@ def ustr(value, hint_encoding='utf-8', errors='strict'):
|
||||
upstream and should be tried first to decode ``value``.
|
||||
:param str error: optional `errors` flag to pass to the unicode
|
||||
built-in to indicate how illegal character values should be
|
||||
treated when converting a string: 'strict', 'ignore' or 'replace'.
|
||||
treated when converting a string: 'strict', 'ignore' or 'replace'
|
||||
(see ``unicode()`` constructor).
|
||||
Passing anything other than 'strict' means that the first
|
||||
encoding tried will be used, even if it's not the correct
|
||||
one to use, so be careful! Ignore if value is not a string/unicode.
|
||||
:rtype: unicode
|
||||
one to use, so be careful! Ignored if value is not a string/unicode.
|
||||
:raise: UnicodeError if value cannot be coerced to unicode
|
||||
:return: unicode string representing the given value
|
||||
"""
|
||||
if isinstance(value, Exception):
|
||||
return exception_to_unicode(value)
|
||||
|
||||
+6
-10
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
|
||||
# Copyright (C) 2010-2012 OpenERP s.a. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
@@ -119,21 +119,17 @@ def create_categories(cr, categories):
|
||||
category = []
|
||||
while categories:
|
||||
category.append(categories[0])
|
||||
if p_id is not None:
|
||||
cr.execute('SELECT id \
|
||||
FROM ir_module_category \
|
||||
WHERE name=%s AND parent_id=%s', (categories[0], p_id))
|
||||
else:
|
||||
cr.execute('SELECT id \
|
||||
FROM ir_module_category \
|
||||
WHERE name=%s AND parent_id IS NULL', (categories[0],))
|
||||
xml_id = 'module_category_' + ('_'.join(map(lambda x: x.lower(), category))).replace('&', 'and').replace(' ', '_')
|
||||
# search via xml_id (because some categories are renamed)
|
||||
cr.execute("SELECT res_id FROM ir_model_data WHERE name=%s AND module=%s AND model=%s",
|
||||
(xml_id, "base", "ir.module.category"))
|
||||
|
||||
c_id = cr.fetchone()
|
||||
if not c_id:
|
||||
cr.execute('INSERT INTO ir_module_category \
|
||||
(name, parent_id) \
|
||||
VALUES (%s, %s) RETURNING id', (categories[0], p_id))
|
||||
c_id = cr.fetchone()[0]
|
||||
xml_id = 'module_category_' + ('_'.join(map(lambda x: x.lower(), category))).replace('&', 'and').replace(' ', '_')
|
||||
cr.execute('INSERT INTO ir_model_data (module, name, res_id, model) \
|
||||
VALUES (%s, %s, %s, %s)', ('base', xml_id, c_id, 'ir.module.category'))
|
||||
else:
|
||||
|
||||
@@ -104,7 +104,10 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
|
||||
|
||||
"""
|
||||
for filename in package.data[kind]:
|
||||
_logger.info("module %s: loading %s", module_name, filename)
|
||||
if kind == 'test':
|
||||
_logger.log(logging.TEST, "module %s: loading %s", module_name, filename)
|
||||
else:
|
||||
_logger.info("module %s: loading %s", module_name, filename)
|
||||
_, ext = os.path.splitext(filename)
|
||||
pathname = os.path.join(module_name, filename)
|
||||
fp = tools.file_open(pathname)
|
||||
|
||||
@@ -553,7 +553,7 @@ def run_unit_tests(module_name):
|
||||
for m in ms:
|
||||
suite.addTests(unittest2.TestLoader().loadTestsFromModule(m))
|
||||
if ms:
|
||||
_logger.info('module %s: executing %s `fast_suite` and/or `checks` sub-modules', module_name, len(ms))
|
||||
_logger.log(logging.TEST, 'module %s: executing %s `fast_suite` and/or `checks` sub-modules', module_name, len(ms))
|
||||
# Use a custom stream object to log the test executions.
|
||||
class MyStream(object):
|
||||
def __init__(self):
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ def close_socket(sock):
|
||||
# of the other side (or something), see
|
||||
# http://bugs.python.org/issue4397
|
||||
# note: stdlib fixed test, not behavior
|
||||
if e.errno != errno.ENOTCONN or platform.system() != 'Darwin':
|
||||
if e.errno != errno.ENOTCONN or platform.system() not in ['Darwin', 'Windows']:
|
||||
raise
|
||||
sock.close()
|
||||
|
||||
|
||||
@@ -706,10 +706,7 @@ class many2many(_column):
|
||||
if where_c:
|
||||
where_c = ' AND ' + where_c
|
||||
|
||||
if offset or self._limit:
|
||||
order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
|
||||
else:
|
||||
order_by = ''
|
||||
order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
|
||||
|
||||
limit_str = ''
|
||||
if self._limit is not None:
|
||||
|
||||
+39
-16
@@ -916,7 +916,16 @@ class BaseModel(object):
|
||||
else:
|
||||
new.extend(cls.__dict__.get(s, []))
|
||||
nattr[s] = new
|
||||
|
||||
# Keep links to non-inherited constraints, e.g. useful when exporting translations
|
||||
nattr['_local_constraints'] = cls.__dict__.get('_constraints', [])
|
||||
nattr['_local_sql_constraints'] = cls.__dict__.get('_sql_constraints', [])
|
||||
|
||||
cls = type(name, (cls, parent_class), dict(nattr, _register=False))
|
||||
else:
|
||||
cls._local_constraints = getattr(cls, '_constraints', [])
|
||||
cls._local_sql_constraints = getattr(cls, '_sql_constraints', [])
|
||||
|
||||
if not getattr(cls, '_original_module', None):
|
||||
cls._original_module = cls._module
|
||||
obj = object.__new__(cls)
|
||||
@@ -1111,7 +1120,7 @@ class BaseModel(object):
|
||||
if not model_data.search(cr, uid, [('name', '=', n)]):
|
||||
break
|
||||
postfix += 1
|
||||
model_data.create(cr, uid, {
|
||||
model_data.create(cr, SUPERUSER_ID, {
|
||||
'name': n,
|
||||
'model': self._name,
|
||||
'res_id': r['id'],
|
||||
@@ -1515,6 +1524,8 @@ class BaseModel(object):
|
||||
error_msgs = []
|
||||
for constraint in self._constraints:
|
||||
fun, msg, fields = constraint
|
||||
# We don't pass around the context here: validation code
|
||||
# must always yield the same results.
|
||||
if not fun(self, cr, uid, ids):
|
||||
# Check presence of __call__ directly instead of using
|
||||
# callable() because it will be deprecated as of Python 3.0
|
||||
@@ -1838,7 +1849,7 @@ class BaseModel(object):
|
||||
if trans:
|
||||
node.set('string', trans)
|
||||
|
||||
for attr_name in ('confirm', 'sum', 'help', 'placeholder'):
|
||||
for attr_name in ('confirm', 'sum', 'avg', 'help', 'placeholder'):
|
||||
attr_value = node.get(attr_name)
|
||||
if attr_value:
|
||||
trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], attr_value)
|
||||
@@ -2670,13 +2681,19 @@ class BaseModel(object):
|
||||
|
||||
order = orderby or groupby
|
||||
data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=order, context=context)
|
||||
# the IDS of records that have groupby field value = False or '' should be sorted too
|
||||
data_ids += filter(lambda x:x not in data_ids, alldata.keys())
|
||||
data = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context)
|
||||
# restore order of the search as read() uses the default _order (this is only for groups, so the size of data_read shoud be small):
|
||||
data.sort(lambda x,y: cmp(data_ids.index(x['id']), data_ids.index(y['id'])))
|
||||
|
||||
# the IDs of records that have groupby field value = False or '' should be included too
|
||||
data_ids += set(alldata.keys()).difference(data_ids)
|
||||
|
||||
if groupby:
|
||||
data = self.read(cr, uid, data_ids, [groupby], context=context)
|
||||
# restore order of the search as read() uses the default _order (this is only for groups, so the footprint of data should be small):
|
||||
data_dict = dict((d['id'], d[groupby] ) for d in data)
|
||||
result = [{'id': i, groupby: data_dict[i]} for i in data_ids]
|
||||
else:
|
||||
result = [{'id': i} for i in data_ids]
|
||||
|
||||
for d in data:
|
||||
for d in result:
|
||||
if groupby:
|
||||
d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
|
||||
if not isinstance(groupby_list, (str, unicode)):
|
||||
@@ -2697,11 +2714,11 @@ class BaseModel(object):
|
||||
del d['id']
|
||||
|
||||
if groupby and groupby in self._group_by_full:
|
||||
data = self._read_group_fill_results(cr, uid, domain, groupby, groupby_list,
|
||||
aggregated_fields, data, read_group_order=order,
|
||||
context=context)
|
||||
result = self._read_group_fill_results(cr, uid, domain, groupby, groupby_list,
|
||||
aggregated_fields, result, read_group_order=order,
|
||||
context=context)
|
||||
|
||||
return data
|
||||
return result
|
||||
|
||||
def _inherits_join_add(self, current_table, parent_model_name, query):
|
||||
"""
|
||||
@@ -3898,7 +3915,7 @@ class BaseModel(object):
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
|
||||
result_store = self._store_get_values(cr, uid, ids, None, context)
|
||||
result_store = self._store_get_values(cr, uid, ids, self._all_columns.keys(), context)
|
||||
|
||||
self._check_concurrency(cr, ids, context)
|
||||
|
||||
@@ -4305,11 +4322,16 @@ class BaseModel(object):
|
||||
del vals[self._inherits[table]]
|
||||
|
||||
record_id = tocreate[table].pop('id', None)
|
||||
|
||||
|
||||
# When linking/creating parent records, force context without 'no_store_function' key that
|
||||
# defers stored functions computing, as these won't be computed in batch at the end of create().
|
||||
parent_context = dict(context)
|
||||
parent_context.pop('no_store_function', None)
|
||||
|
||||
if record_id is None or not record_id:
|
||||
record_id = self.pool.get(table).create(cr, user, tocreate[table], context=context)
|
||||
record_id = self.pool.get(table).create(cr, user, tocreate[table], context=parent_context)
|
||||
else:
|
||||
self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=context)
|
||||
self.pool.get(table).write(cr, user, [record_id], tocreate[table], context=parent_context)
|
||||
|
||||
upd0 += ',' + self._inherits[table]
|
||||
upd1 += ',%s'
|
||||
@@ -5198,6 +5220,7 @@ class AbstractModel(BaseModel):
|
||||
"""
|
||||
_auto = False # don't create any database backend for AbstractModels
|
||||
_register = False # not visible in ORM registry, meant to be python-inherited only
|
||||
_transient = False
|
||||
|
||||
def itemgetter_tuple(items):
|
||||
""" Fixes itemgetter inconsistency (useful in some cases) of not returning
|
||||
|
||||
@@ -163,15 +163,30 @@ class report_rml(report_int):
|
||||
# * (re)build/update the stylesheet with the translated items
|
||||
|
||||
def translate(doc, lang):
|
||||
for node in doc.xpath('//*[@t]'):
|
||||
if not node.text:
|
||||
continue
|
||||
translation = ir_translation_obj._get_source(cr, uid, self.name2, 'xsl', lang, node.text)
|
||||
if translation:
|
||||
node.text = translation
|
||||
translate_aux(doc, lang, False)
|
||||
|
||||
def translate_aux(doc, lang, t):
|
||||
for node in doc:
|
||||
t = t or node.get("t")
|
||||
if t:
|
||||
text = None
|
||||
tail = None
|
||||
if node.text:
|
||||
text = node.text.strip().replace('\n',' ')
|
||||
if node.tail:
|
||||
tail = node.tail.strip().replace('\n',' ')
|
||||
if text:
|
||||
translation1 = ir_translation_obj._get_source(cr, uid, self.name2, 'xsl', lang, text)
|
||||
if translation1:
|
||||
node.text = node.text.replace(text, translation1)
|
||||
if tail:
|
||||
translation2 = ir_translation_obj._get_source(cr, uid, self.name2, 'xsl', lang, tail)
|
||||
if translation2:
|
||||
node.tail = node.tail.replace(tail, translation2)
|
||||
translate_aux(node, lang, t)
|
||||
|
||||
if context.get('lang', False):
|
||||
translate(stylesheet, context['lang'])
|
||||
translate(stylesheet.iter(), context['lang'])
|
||||
|
||||
transform = etree.XSLT(stylesheet)
|
||||
xml = etree.tostring(
|
||||
|
||||
@@ -99,7 +99,7 @@ class NumberedCanvas(canvas.Canvas):
|
||||
key = key + 1
|
||||
self.setFont("Helvetica", 8)
|
||||
self.drawRightString((self._pagesize[0]-30), (self._pagesize[1]-40),
|
||||
"Page %(this)i of %(total)i" % {
|
||||
" %(this)i / %(total)i" % {
|
||||
'this': self._pageNumber+1,
|
||||
'total': self.pages.get(key,False),
|
||||
}
|
||||
@@ -118,15 +118,19 @@ class NumberedCanvas(canvas.Canvas):
|
||||
self._doc.SaveToFile(self._filename, self)
|
||||
|
||||
class PageCount(platypus.Flowable):
|
||||
def __init__(self, story_count=0):
|
||||
platypus.Flowable.__init__(self)
|
||||
self.story_count = story_count
|
||||
|
||||
def draw(self):
|
||||
self.canv.beginForm("pageCount")
|
||||
self.canv.beginForm("pageCount%d" % (self.story_count))
|
||||
self.canv.setFont("Helvetica", utils.unit_get(str(8)))
|
||||
self.canv.drawString(0, 0, str(self.canv.getPageNumber()))
|
||||
self.canv.endForm()
|
||||
|
||||
class PageReset(platypus.Flowable):
|
||||
def draw(self):
|
||||
self.canv._pageNumber = 0
|
||||
self.canv._doPageReset = True
|
||||
|
||||
class _rml_styles(object,):
|
||||
def __init__(self, nodes, localcontext):
|
||||
@@ -343,7 +347,7 @@ class _rml_canvas(object):
|
||||
if n.tag == 'pageCount':
|
||||
if x or y:
|
||||
self.canvas.translate(x,y)
|
||||
self.canvas.doForm('pageCount')
|
||||
self.canvas.doForm('pageCount%s' % (self.canvas._storyCount,))
|
||||
if x or y:
|
||||
self.canvas.translate(-x,-y)
|
||||
if n.tag == 'pageNumber':
|
||||
@@ -878,6 +882,13 @@ class EndFrameFlowable(ActionFlowable):
|
||||
ActionFlowable.__init__(self,('frameEnd',resume))
|
||||
|
||||
class TinyDocTemplate(platypus.BaseDocTemplate):
|
||||
|
||||
def beforeDocument(self):
|
||||
# Store some useful value directly inside canvas, so it's available
|
||||
# on flowable drawing (needed for proper PageCount handling)
|
||||
self.canv._doPageReset = False
|
||||
self.canv._storyCount = 0
|
||||
|
||||
def ___handle_pageBegin(self):
|
||||
self.page = self.page + 1
|
||||
self.pageTemplate.beforeDrawPage(self.canv,self)
|
||||
@@ -893,12 +904,24 @@ class TinyDocTemplate(platypus.BaseDocTemplate):
|
||||
self.frame = f
|
||||
break
|
||||
self.handle_frameBegin()
|
||||
def afterFlowable(self, flowable):
|
||||
if isinstance(flowable, PageReset):
|
||||
self.canv._pageCount=self.page
|
||||
self.page=0
|
||||
self.canv._flag=True
|
||||
|
||||
def afterPage(self):
|
||||
if self.canv._doPageReset:
|
||||
# Following a <pageReset/> tag:
|
||||
# - we reset page number to 0
|
||||
# - we add an new PageCount flowable (relative to the current
|
||||
# story number), but not for NumeredCanvas at is handle page
|
||||
# count itself)
|
||||
# NOTE: _rml_template render() method add a PageReset flowable at end
|
||||
# of each story, so we're sure to pass here at least once per story.
|
||||
if not isinstance(self.canv, NumberedCanvas):
|
||||
self.handle_flowable([ PageCount(story_count=self.canv._storyCount) ])
|
||||
self.canv._pageCount = self.page
|
||||
self.page = 0
|
||||
self.canv._flag = True
|
||||
self.canv._pageNumber = 0
|
||||
self.canv._doPageReset = False
|
||||
self.canv._storyCount += 1
|
||||
|
||||
class _rml_template(object):
|
||||
def __init__(self, localcontext, out, node, doc, images=None, path='.', title=None):
|
||||
@@ -965,7 +988,6 @@ class _rml_template(object):
|
||||
self.doc_tmpl.afterFlowable(fis)
|
||||
self.doc_tmpl.build(fis,canvasmaker=NumberedCanvas)
|
||||
else:
|
||||
fis.append(PageCount())
|
||||
self.doc_tmpl.build(fis)
|
||||
|
||||
def parseNode(rml, localcontext=None, fout=None, images=None, path='.', title=None):
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2010-2012 OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
@@ -15,7 +16,7 @@
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
@@ -23,10 +24,8 @@ import logging
|
||||
import threading
|
||||
import time
|
||||
|
||||
import http_server
|
||||
import netrpc_server
|
||||
import web_services
|
||||
import websrv_lib
|
||||
|
||||
import openerp.cron
|
||||
import openerp.modules
|
||||
@@ -61,6 +60,7 @@ Maybe you forgot to add those addons in your addons_path configuration."""
|
||||
_logger.exception('Failed to load server-wide module `%s`.%s', m, msg)
|
||||
|
||||
start_internal_done = False
|
||||
main_thread_id = threading.currentThread().ident
|
||||
|
||||
def start_internal():
|
||||
global start_internal_done
|
||||
@@ -99,7 +99,6 @@ def stop_services():
|
||||
|
||||
openerp.netsvc.Server.quitAll()
|
||||
openerp.service.wsgi_server.stop_server()
|
||||
config = openerp.tools.config
|
||||
_logger.info("Initiating shutdown")
|
||||
_logger.info("Hit CTRL-C again or send a second signal to force the shutdown.")
|
||||
logging.shutdown()
|
||||
@@ -107,8 +106,9 @@ def stop_services():
|
||||
# Manually join() all threads before calling sys.exit() to allow a second signal
|
||||
# to trigger _force_quit() in case some non-daemon threads won't exit cleanly.
|
||||
# threading.Thread.join() should not mask signals (at least in python 2.5).
|
||||
me = threading.currentThread()
|
||||
for thread in threading.enumerate():
|
||||
if thread != threading.currentThread() and not thread.isDaemon():
|
||||
if thread != me and not thread.isDaemon() and thread.ident != main_thread_id:
|
||||
while thread.isAlive():
|
||||
# Need a busyloop here as thread.join() masks signals
|
||||
# and would prevent the forced shutdown.
|
||||
@@ -119,10 +119,9 @@ def stop_services():
|
||||
|
||||
def start_services_workers():
|
||||
import openerp.service.workers
|
||||
openerp.multi_process = True # Nah!
|
||||
openerp.multi_process = True
|
||||
|
||||
openerp.service.workers.Multicorn(openerp.service.wsgi_server.application).run()
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
||||
@@ -196,10 +196,15 @@ class db(netsvc.ExportService):
|
||||
# Try to terminate all other connections that might prevent
|
||||
# dropping the database
|
||||
try:
|
||||
cr.execute("""SELECT pg_terminate_backend(procpid)
|
||||
|
||||
# PostgreSQL 9.2 renamed pg_stat_activity.procpid to pid:
|
||||
# http://www.postgresql.org/docs/9.2/static/release-9-2.html#AEN110389
|
||||
pid_col = 'pid' if cr._cnx.server_version >= 90200 else 'procpid'
|
||||
|
||||
cr.execute("""SELECT pg_terminate_backend(%(pid_col)s)
|
||||
FROM pg_stat_activity
|
||||
WHERE datname = %s AND
|
||||
procpid != pg_backend_pid()""",
|
||||
WHERE datname = %%s AND
|
||||
%(pid_col)s != pg_backend_pid()""" % {'pid_col': pid_col},
|
||||
(db_name,))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -1140,6 +1140,10 @@ class test_datetime(ImporterCase):
|
||||
""" If there is no tz either in the context or on the user, falls back
|
||||
to UTC
|
||||
"""
|
||||
self.registry('res.users').write(
|
||||
self.cr, openerp.SUPERUSER_ID, [openerp.SUPERUSER_ID],
|
||||
{'tz': False})
|
||||
|
||||
result = self.import_(['value'], [['2012-02-03 11:11:11']])
|
||||
self.assertFalse(result['messages'])
|
||||
self.assertEqual(
|
||||
|
||||
+16
-5
@@ -58,6 +58,8 @@ def check_ssl():
|
||||
except:
|
||||
return False
|
||||
|
||||
DEFAULT_LOG_HANDLER = [':INFO']
|
||||
|
||||
class configmanager(object):
|
||||
def __init__(self, fname=None):
|
||||
# Options not exposed on the command line. Command line options will be added
|
||||
@@ -179,7 +181,7 @@ class configmanager(object):
|
||||
group.add_option("--logfile", dest="logfile", help="file where the server log will be stored")
|
||||
group.add_option("--no-logrotate", dest="logrotate", action="store_false", my_default=True, help="do not rotate the logfile")
|
||||
group.add_option("--syslog", action="store_true", dest="syslog", my_default=False, help="Send the log to the syslog server")
|
||||
group.add_option('--log-handler', action="append", default=[':INFO'], my_default=[':INFO'], metavar="PREFIX:LEVEL", help='setup a handler at LEVEL for a given PREFIX. An empty PREFIX indicates the root logger. This option can be repeated. Example: "openerp.orm:DEBUG" or "werkzeug:CRITICAL" (default: ":INFO")')
|
||||
group.add_option('--log-handler', action="append", default=DEFAULT_LOG_HANDLER, my_default=DEFAULT_LOG_HANDLER, metavar="PREFIX:LEVEL", help='setup a handler at LEVEL for a given PREFIX. An empty PREFIX indicates the root logger. This option can be repeated. Example: "openerp.orm:DEBUG" or "werkzeug:CRITICAL" (default: ":INFO")')
|
||||
group.add_option('--log-request', action="append_const", dest="log_handler", const="openerp.netsvc.rpc.request:DEBUG", help='shortcut for --log-handler=openerp.netsvc.rpc.request:DEBUG')
|
||||
group.add_option('--log-response', action="append_const", dest="log_handler", const="openerp.netsvc.rpc.response:DEBUG", help='shortcut for --log-handler=openerp.netsvc.rpc.response:DEBUG')
|
||||
group.add_option('--log-web', action="append_const", dest="log_handler", const="openerp.addons.web.http:DEBUG", help='shortcut for --log-handler=openerp.addons.web.http:DEBUG')
|
||||
@@ -299,8 +301,9 @@ class configmanager(object):
|
||||
# Copy all optparse options (i.e. MyOption) into self.options.
|
||||
for group in parser.option_groups:
|
||||
for option in group.option_list:
|
||||
self.options[option.dest] = option.my_default
|
||||
self.casts[option.dest] = option
|
||||
if option.dest not in self.options:
|
||||
self.options[option.dest] = option.my_default
|
||||
self.casts[option.dest] = option
|
||||
|
||||
self.parse_config(None, False)
|
||||
|
||||
@@ -384,13 +387,18 @@ class configmanager(object):
|
||||
]
|
||||
|
||||
for arg in keys:
|
||||
# Copy the command-line argument...
|
||||
if getattr(opt, arg):
|
||||
# Copy the command-line argument (except the special case for log_handler, due to
|
||||
# action=append requiring a real default, so we cannot use the my_default workaround)
|
||||
if getattr(opt, arg) and getattr(opt, arg) != DEFAULT_LOG_HANDLER:
|
||||
self.options[arg] = getattr(opt, arg)
|
||||
# ... or keep, but cast, the config file value.
|
||||
elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
|
||||
self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
|
||||
|
||||
|
||||
if isinstance(self.options['log_handler'], basestring):
|
||||
self.options['log_handler'] = self.options['log_handler'].split(',')
|
||||
|
||||
# if defined but None take the configfile value
|
||||
keys = [
|
||||
'language', 'translate_out', 'translate_in', 'overwrite_existing_translations',
|
||||
@@ -613,6 +621,9 @@ class configmanager(object):
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.options[key] = value
|
||||
if key in self.options and isinstance(self.options[key], basestring) and \
|
||||
key in self.casts and self.casts[key].type in optparse.Option.TYPE_CHECKER:
|
||||
self.options[key] = optparse.Option.TYPE_CHECKER[self.casts[key].type](self.casts[key], key, self.options[key])
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.options[key]
|
||||
|
||||
@@ -358,10 +358,11 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
|
||||
"""
|
||||
|
||||
# If not cr, get cr from current thread database
|
||||
local_cr = None
|
||||
if not cr:
|
||||
db_name = getattr(threading.currentThread(), 'dbname', None)
|
||||
if db_name:
|
||||
cr = pooler.get_db_only(db_name).cursor()
|
||||
local_cr = cr = pooler.get_db(db_name).cursor()
|
||||
else:
|
||||
raise Exception("No database cursor found, please pass one explicitly")
|
||||
|
||||
@@ -380,7 +381,8 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
|
||||
_logger.exception("tools.email_send failed to deliver email")
|
||||
return False
|
||||
finally:
|
||||
cr.close()
|
||||
if local_cr:
|
||||
cr.close()
|
||||
return res
|
||||
|
||||
def email_split(text):
|
||||
|
||||
+5
-10
@@ -27,6 +27,7 @@ Miscellaneous tools used by OpenERP.
|
||||
"""
|
||||
|
||||
from functools import wraps
|
||||
import cProfile
|
||||
import subprocess
|
||||
import logging
|
||||
import os
|
||||
@@ -592,16 +593,10 @@ class profile(object):
|
||||
def __call__(self, f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
class profile_wrapper(object):
|
||||
def __init__(self):
|
||||
self.result = None
|
||||
def __call__(self):
|
||||
self.result = f(*args, **kwargs)
|
||||
pw = profile_wrapper()
|
||||
import cProfile
|
||||
fname = self.fname or ("%s.cprof" % (f.func_name,))
|
||||
cProfile.runctx('pw()', globals(), locals(), filename=fname)
|
||||
return pw.result
|
||||
profile = cProfile.Profile()
|
||||
result = profile.runcall(f, *args, **kwargs)
|
||||
profile.dump_stats(self.fname or ("%s.cprof" % (f.func_name,)))
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
|
||||
# Copyright (C) 2010-2012 OpenERP s.a. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
@@ -73,8 +73,9 @@ def try_report(cr, uid, rname, ids, data=None, context=None, our_module=None):
|
||||
os.write(fd, res_data)
|
||||
os.close(fd)
|
||||
|
||||
fp = Popen(['pdftotext', '-enc', 'UTF-8', '-nopgbrk', rfname, '-'], shell=False, stdout=PIPE).stdout
|
||||
res_text = tools.ustr(fp.read())
|
||||
proc = Popen(['pdftotext', '-enc', 'UTF-8', '-nopgbrk', rfname, '-'], shell=False, stdout=PIPE)
|
||||
stdout, stderr = proc.communicate()
|
||||
res_text = tools.ustr(stdout)
|
||||
os.unlink(rfname)
|
||||
except Exception:
|
||||
_logger.debug("Unable to parse PDF report: install pdftotext to perform automated tests.")
|
||||
|
||||
+55
-21
@@ -167,19 +167,22 @@ class GettextAlias(object):
|
||||
if db_name:
|
||||
return sql_db.db_connect(db_name)
|
||||
|
||||
def _get_cr(self, frame):
|
||||
def _get_cr(self, frame, allow_create=True):
|
||||
is_new_cr = False
|
||||
cr = frame.f_locals.get('cr', frame.f_locals.get('cursor'))
|
||||
if not cr:
|
||||
s = frame.f_locals.get('self', {})
|
||||
cr = getattr(s, 'cr', None)
|
||||
if not cr:
|
||||
if not cr and allow_create:
|
||||
db = self._get_db()
|
||||
if db:
|
||||
cr = db.cursor()
|
||||
is_new_cr = True
|
||||
return cr, is_new_cr
|
||||
|
||||
def _get_uid(self, frame):
|
||||
return frame.f_locals.get('uid') or frame.f_locals.get('user')
|
||||
|
||||
def _get_lang(self, frame):
|
||||
lang = None
|
||||
ctx = frame.f_locals.get('context')
|
||||
@@ -194,11 +197,21 @@ class GettextAlias(object):
|
||||
ctx = kwargs.get('context')
|
||||
if ctx:
|
||||
lang = ctx.get('lang')
|
||||
s = frame.f_locals.get('self', {})
|
||||
if not lang:
|
||||
s = frame.f_locals.get('self', {})
|
||||
c = getattr(s, 'localcontext', None)
|
||||
if c:
|
||||
lang = c.get('lang')
|
||||
if not lang:
|
||||
# Last resort: attempt to guess the language of the user
|
||||
# Pitfall: some operations are performed in sudo mode, and we
|
||||
# don't know the originial uid, so the language may
|
||||
# be wrong when the admin language differs.
|
||||
pool = getattr(s, 'pool', None)
|
||||
(cr, dummy) = self._get_cr(frame, allow_create=False)
|
||||
uid = self._get_uid(frame)
|
||||
if pool and cr and uid:
|
||||
lang = pool.get('res.users').context_get(cr, uid)['lang']
|
||||
return lang
|
||||
|
||||
def __call__(self, source):
|
||||
@@ -443,12 +456,17 @@ def trans_export(lang, modules, buffer, format, cr):
|
||||
for module, type, name, res_id, src, trad, comments in rows:
|
||||
row = grouped_rows.setdefault(src, {})
|
||||
row.setdefault('modules', set()).add(module)
|
||||
if ('translation' not in row) or (not row['translation']):
|
||||
if not row.get('translation') and trad != src:
|
||||
row['translation'] = trad
|
||||
row.setdefault('tnrs', []).append((type, name, res_id))
|
||||
row.setdefault('comments', set()).update(comments)
|
||||
|
||||
for src, row in grouped_rows.items():
|
||||
if not lang:
|
||||
# translation template, so no translation value
|
||||
row['translation'] = ''
|
||||
elif not row.get('translation'):
|
||||
row['translation'] = src
|
||||
writer.write(row['modules'], row['tnrs'], src, row['translation'], row['comments'])
|
||||
|
||||
elif format == 'tgz':
|
||||
@@ -484,16 +502,25 @@ def trans_export(lang, modules, buffer, format, cr):
|
||||
del translations
|
||||
|
||||
def trans_parse_xsl(de):
|
||||
return list(set(trans_parse_xsl_aux(de, False)))
|
||||
|
||||
def trans_parse_xsl_aux(de, t):
|
||||
res = []
|
||||
|
||||
for n in de:
|
||||
if n.get("t"):
|
||||
for m in n:
|
||||
if isinstance(m, SKIPPED_ELEMENT_TYPES) or not m.text:
|
||||
t = t or n.get("t")
|
||||
if t:
|
||||
if isinstance(n, SKIPPED_ELEMENT_TYPES) or n.tag.startswith('{http://www.w3.org/1999/XSL/Transform}'):
|
||||
continue
|
||||
l = m.text.strip().replace('\n',' ')
|
||||
if len(l):
|
||||
res.append(l.encode("utf8"))
|
||||
res.extend(trans_parse_xsl(n))
|
||||
if n.text:
|
||||
l = n.text.strip().replace('\n',' ')
|
||||
if len(l):
|
||||
res.append(l.encode("utf8"))
|
||||
if n.tail:
|
||||
l = n.tail.strip().replace('\n',' ')
|
||||
if len(l):
|
||||
res.append(l.encode("utf8"))
|
||||
res.extend(trans_parse_xsl_aux(n, t))
|
||||
return res
|
||||
|
||||
def trans_parse_rml(de):
|
||||
@@ -766,26 +793,33 @@ def trans_generate(lang, modules, cr):
|
||||
cr.execute(query_models, query_param)
|
||||
|
||||
def push_constraint_msg(module, term_type, model, msg):
|
||||
# Check presence of __call__ directly instead of using
|
||||
# callable() because it will be deprecated as of Python 3.0
|
||||
if not hasattr(msg, '__call__'):
|
||||
push_translation(module, term_type, model, 0, encode(msg))
|
||||
push_translation(encode(module), term_type, encode(model), 0, encode(msg))
|
||||
|
||||
def push_local_constraints(module, model, cons_type='sql_constraints'):
|
||||
"""Climb up the class hierarchy and ignore inherited constraints
|
||||
from other modules"""
|
||||
term_type = 'sql_constraint' if cons_type == 'sql_constraints' else 'constraint'
|
||||
msg_pos = 2 if cons_type == 'sql_constraints' else 1
|
||||
for cls in model.__class__.__mro__:
|
||||
if getattr(cls, '_module', None) != module:
|
||||
continue
|
||||
constraints = getattr(cls, '_local_' + cons_type, [])
|
||||
for constraint in constraints:
|
||||
push_constraint_msg(module, term_type, model._name, constraint[msg_pos])
|
||||
|
||||
for (_, model, module) in cr.fetchall():
|
||||
module = encode(module)
|
||||
model = encode(model)
|
||||
|
||||
model_obj = pool.get(model)
|
||||
|
||||
if not model_obj:
|
||||
_logger.error("Unable to find object %r", model)
|
||||
continue
|
||||
|
||||
for constraint in getattr(model_obj, '_constraints', []):
|
||||
push_constraint_msg(module, 'constraint', model, constraint[1])
|
||||
if model_obj._constraints:
|
||||
push_local_constraints(module, model_obj, 'constraints')
|
||||
|
||||
for constraint in getattr(model_obj, '_sql_constraints', []):
|
||||
push_constraint_msg(module, 'sql_constraint', model, constraint[2])
|
||||
if model_obj._sql_constraints:
|
||||
push_local_constraints(module, model_obj, 'sql_constraints')
|
||||
|
||||
def get_module_from_path(path, mod_paths=None):
|
||||
if not mod_paths:
|
||||
|
||||
@@ -350,39 +350,41 @@ class YamlInterpreter(object):
|
||||
return one2many_view
|
||||
|
||||
def process_val(key, val):
|
||||
if fg[key]['type']=='many2one':
|
||||
if fg[key]['type'] == 'many2one':
|
||||
if type(val) in (tuple,list):
|
||||
val = val[0]
|
||||
elif (fg[key]['type']=='one2many'):
|
||||
if val is False:
|
||||
val = []
|
||||
if len(val) and type(val[0]) == dict:
|
||||
#we want to return only the fields that aren't readonly
|
||||
#For that, we need to first get the right tree view to consider for the field `key´
|
||||
elif fg[key]['type'] == 'one2many':
|
||||
if val and isinstance(val, (list,tuple)) and isinstance(val[0], dict):
|
||||
# we want to return only the fields that aren't readonly
|
||||
# For that, we need to first get the right tree view to consider for the field `key´
|
||||
one2many_tree_view = _get_right_one2many_view(fg, key, 'tree')
|
||||
arch = etree.fromstring(one2many_tree_view['arch'].encode('utf-8'))
|
||||
for rec in val:
|
||||
#make a copy for the iteration, as we will alterate the size of `rec´ dictionary
|
||||
# make a copy for the iteration, as we will alter `rec´
|
||||
rec_copy = rec.copy()
|
||||
for field_key in rec_copy:
|
||||
#seek in the view for the field `field_key´ and removing it from `key´ values, as this column is readonly in the tree view
|
||||
subfield_obj = etree.fromstring(one2many_tree_view['arch'].encode('utf-8')).xpath("//field[@name='%s']"%(field_key))
|
||||
if subfield_obj and (subfield_obj[0].get('modifiers', '{}').find('"readonly": true') >= 0):
|
||||
#TODO: currently we only support if readonly is True in the modifiers. Some improvement may be done in
|
||||
#order to support also modifiers that look like {"readonly": [["state", "not in", ["draft", "confirm"]]]}
|
||||
del(rec[field_key])
|
||||
|
||||
#now that unwanted values have been removed from val, we can encapsulate it in a tuple as returned value
|
||||
# if field is missing in view or has a readonly modifier, drop it
|
||||
field_elem = arch.xpath("//field[@name='%s']" % field_key)
|
||||
if field_elem and (field_elem[0].get('modifiers', '{}').find('"readonly": true') >= 0):
|
||||
# TODO: currently we only support if readonly is True in the modifiers. Some improvement may be done in
|
||||
# order to support also modifiers that look like {"readonly": [["state", "not in", ["draft", "confirm"]]]}
|
||||
del rec[field_key]
|
||||
# now that unwanted values have been removed from val, we can encapsulate it in a tuple as returned value
|
||||
val = map(lambda x: (0,0,x), val)
|
||||
elif fg[key]['type'] == 'many2many':
|
||||
if val and isinstance(val,(list,tuple)) and isinstance(val[0], (int,long)):
|
||||
val = [(6,0,val)]
|
||||
|
||||
#we want to return only the fields that aren't readonly
|
||||
# we want to return only the fields that aren't readonly
|
||||
if el.get('modifiers', '{}').find('"readonly": true') >= 0:
|
||||
#TODO: currently we only support if readonly is True in the modifiers. Some improvement may be done in
|
||||
#order to support also modifiers that look like {"readonly": [["state", "not in", ["draft", "confirm"]]]}
|
||||
# TODO: currently we only support if readonly is True in the modifiers. Some improvement may be done in
|
||||
# order to support also modifiers that look like {"readonly": [["state", "not in", ["draft", "confirm"]]]}
|
||||
return False
|
||||
|
||||
return val
|
||||
|
||||
if view_info:
|
||||
arch = etree.fromstring(view_info['arch'].encode('utf-8'))
|
||||
arch = etree.fromstring(view_info['arch'].decode('utf-8'))
|
||||
view = arch if len(arch) else False
|
||||
else:
|
||||
view = False
|
||||
|
||||
@@ -46,6 +46,8 @@ def _eval_expr(cr, ident, workitem, action):
|
||||
assert action, 'You used a NULL action in a workflow, use dummy node instead.'
|
||||
for line in action.split('\n'):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
uid=ident[0]
|
||||
model=ident[1]
|
||||
ids=[ident[2]]
|
||||
|
||||
@@ -64,7 +64,7 @@ def py2exe_options():
|
||||
"skip_archive": 1,
|
||||
"optimize": 2,
|
||||
"dist_dir": 'dist',
|
||||
"packages": [ "DAV", "HTMLParser", "PIL", "asynchat", "asyncore", "commands", "dateutil", "decimal", "email", "encodings", "imaplib", "lxml", "lxml._elementpath", "lxml.builder", "lxml.etree", "lxml.objectify", "mako", "openerp", "poplib", "pychart", "pydot", "pyparsing", "reportlab", "select", "simplejson", "smtplib", "uuid", "vatnumber", "vobject", "xml", "xml.dom", "yaml", ],
|
||||
"packages": [ "DAV", "HTMLParser", "PIL", "asynchat", "asyncore", "commands", "dateutil", "decimal", "docutils", "email", "encodings", "imaplib", "lxml", "lxml._elementpath", "lxml.builder", "lxml.etree", "lxml.objectify", "mako", "openerp", "poplib", "pychart", "pydot", "pyparsing", "reportlab", "select", "simplejson", "smtplib", "uuid", "vatnumber", "vobject", "xml", "xml.dom", "yaml", ],
|
||||
"excludes" : ["Tkconstants","Tkinter","tcl"],
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user