[MERGE] latest trunk

bzr revid: odo@openerp.com-20110617170517-pvhf772lewqk3z8c
This commit is contained in:
Olivier Dony
2011-06-17 19:05:17 +02:00
86 changed files with 717 additions and 576860 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -7,13 +7,13 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
"PO-Revision-Date: 2011-05-11 04:20+0000\n"
"Last-Translator: doingit.cl <Unknown>\n"
"PO-Revision-Date: 2011-06-06 17:53+0000\n"
"Last-Translator: Hector Rojas (doingIT.cl) <Unknown>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-05-12 04:34+0000\n"
"X-Launchpad-Export-Date: 2011-06-07 04:35+0000\n"
"X-Generator: Launchpad (build 12959)\n"
#. module: base
@@ -58,7 +58,7 @@ msgstr "Metadatos"
#: field:ir.ui.view,arch:0
#: field:ir.ui.view.custom,arch:0
msgid "View Architecture"
msgstr "Código vista"
msgstr "Estructura de la vista"
#. module: base
#: field:base.language.import,code:0
+10 -10
View File
@@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
"PO-Revision-Date: 2011-05-29 23:23+0000\n"
"PO-Revision-Date: 2011-06-14 10:07+0000\n"
"Last-Translator: Phong Nguyen-Thanh <Unknown>\n"
"Language-Team: Vietnamese <vi@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-05-31 04:47+0000\n"
"X-Generator: Launchpad (build 12959)\n"
"X-Launchpad-Export-Date: 2011-06-15 04:39+0000\n"
"X-Generator: Launchpad (build 13168)\n"
#. module: base
#: view:ir.filters:0
@@ -24,7 +24,7 @@ msgstr ""
#: field:ir.rule,domain_force:0
#: field:res.partner.title,domain:0
msgid "Domain"
msgstr "Miền"
msgstr "Miền giá trị"
#. module: base
#: model:res.country,name:base.sh
@@ -130,9 +130,9 @@ msgid ""
"specified as a Python expression defining a list of triplets. For example: "
"[('color','=','red')]"
msgstr ""
"Miền tùy chọn dùng để giới hạn các giá trị có thể đối với các trường quan "
"hệ, được chỉ định bởi một biểu thức Python định nghĩa một danh sách của các "
"bộ ba. Ví dụ: [('màu'='đỏ')]"
"Miền giá trị tùy chọn dùng để giới hạn các giá trị có thể đối với các trường "
"quan hệ, được chỉ định bởi một biểu thức Python định nghĩa một danh sách của "
"các bộ ba. Ví dụ: [('màu'='đỏ')]"
#. module: base
#: field:res.partner,ref:0
@@ -3787,7 +3787,7 @@ msgstr ""
#: field:ir.attachment,res_name:0
#: field:ir.ui.view_sc,resource:0
msgid "Resource Name"
msgstr "Tên tài nguyên"
msgstr "Tên Tài nguyên"
#. module: base
#: selection:ir.cron,interval_type:0
@@ -5381,7 +5381,7 @@ msgstr ""
#: code:addons/orm.py:3532
#, python-format
msgid "UserError"
msgstr "UserError"
msgstr "Lỗi người sử dụng"
#. module: base
#: model:res.country,name:base.ae
@@ -8841,7 +8841,7 @@ msgstr ""
#: code:addons/base/res/partner/partner.py:250
#, python-format
msgid "Warning"
msgstr "Warning"
msgstr "Cảnh báo"
#. module: base
#: selection:base.language.install,lang:0
-36
View File
@@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import fields,osv
class board(osv.osv):
_name = 'ir.board'
def create(self, cr, user, vals, context={}):
return False
def copy(self, cr, uid, id, default=None, context={}):
return False
_columns = {
'name': fields.char('Board', size=64),
}
board()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+4 -9
View File
@@ -44,15 +44,10 @@ class ir_translation(osv.osv):
_log_access = False
def _get_language(self, cr, uid, context):
lang_obj = self.pool.get('res.lang')
lang_ids = lang_obj.search(cr, uid, [('translatable', '=', True)],
context=context)
langs = lang_obj.browse(cr, uid, lang_ids, context=context)
res = [(lang.code, lang.name) for lang in langs]
for lang_dict in tools.scan_languages():
if lang_dict not in res:
res.append(lang_dict)
return res
lang_model = self.pool.get('res.lang')
lang_ids = lang_model.search(cr, uid, [('translatable', '=', True)], context=context)
lang_data = lang_model.read(cr, uid, lang_ids, ['code', 'name'], context=context)
return [(d['code'], d['name']) for d in lang_data]
_columns = {
'name': fields.char('Field Name', size=128, required=True),
+1
View File
@@ -46,6 +46,7 @@ def _check_xml(self, cr, uid, ids, context=None):
class view_custom(osv.osv):
_name = 'ir.ui.view.custom'
_order = 'create_date desc' # search(limit=1) should return the last customization
_columns = {
'ref_id': fields.many2one('ir.ui.view', 'Original View', select=True),
'user_id': fields.many2one('res.users', 'User', select=True),
+6 -3
View File
@@ -106,12 +106,13 @@ class ir_values(osv.osv):
value = pickle.dumps(value)
if meta:
meta = pickle.dumps(meta)
assert isinstance(models, (list, tuple)), models
ids_res = []
for model in models:
if isinstance(model, (list, tuple)):
model,res_id = model
else:
res_id=False
res_id = False
if replace:
search_criteria = [
('key', '=', key),
@@ -148,11 +149,13 @@ class ir_values(osv.osv):
if context is None:
context = {}
result = []
assert isinstance(models, (list, tuple)), models
for m in models:
if isinstance(m, (list, tuple)):
m, res_id = m
else:
res_id=False
res_id = False
where = ['key=%s','model=%s']
params = [key, str(m)]
@@ -161,7 +164,7 @@ class ir_values(osv.osv):
params.append(key2[:200])
elif key2_req and not meta:
where.append('key2 is null')
if res_id_req and (models[-1][0]==m):
if res_id_req and (models[-1][0] == m):
if res_id:
where.append('res_id=%s')
params.append(res_id)
@@ -158,7 +158,7 @@ showpage'''
(No workflow instance defined) show
showpage'''
else:
graph = pydot.Dot(
graph = pydot.Dot(graph_name=data['model'].replace('.','_'),
fontsize='16',
label="""\\\n\\nWorkflow: %s\\n OSV: %s""" % (wkfinfo['name'],wkfinfo['osv']),
size='7.3, 10.1', center='1', ratio='auto', rotate='0', rankdir='TB',
+4 -11
View File
@@ -20,8 +20,8 @@
##############################################################################
import time
from osv import fields,osv
from osv import osv
from osv import fields
#
# Sale/Purchase Canal, Media
@@ -31,7 +31,7 @@ class res_partner_canal(osv.osv):
_description = "Channels"
_order = 'name'
_columns = {
'name': fields.char('Channel Name',size=64, required=True),
'name': fields.char('Channel Name', size=64, required=True),
'active': fields.boolean('Active'),
}
_defaults = {
@@ -39,16 +39,10 @@ class res_partner_canal(osv.osv):
}
res_partner_canal()
def _links_get(self, cr, uid, context={}):
obj = self.pool.get('res.request.link')
ids = obj.search(cr, uid, [])
res = obj.read(cr, uid, ids, ['object', 'name'], context)
return [(r['object'], r['name']) for r in res]
class res_partner_event(osv.osv):
_name = "res.partner.event"
_columns = {
'name': fields.char('Events',size=64, required=True),
'name': fields.char('Events', size=64, required=True),
'description': fields.text('Description'),
'partner_id': fields.many2one('res.partner', 'Partner', select=True),
'date': fields.datetime('Date', size=16),
@@ -61,5 +55,4 @@ class res_partner_event(osv.osv):
}
res_partner_event()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+13 -26
View File
@@ -188,40 +188,27 @@ class res_company(osv.osv):
tools.config['root_path'], '..', 'pixmaps', 'openerp-header.png'),
'rb') .read().encode('base64')
def _get_header3(self,cr,uid,ids):
return """
_header = """
<header>
<pageTemplate>
<frame id="first" x1="28.0" y1="28.0" width="786" height="525"/>
<frame id="first" x1="28.0" y1="28.0" width="%s" height="%s"/>
<pageGraphics>
<fill color="black"/>
<stroke color="black"/>
<setFont name="DejaVu Sans" size="8"/>
<drawString x="25" y="555"> [[ formatLang(time.strftime("%Y-%m-%d"), date=True) ]] [[ time.strftime("%H:%M") ]]</drawString>
<drawString x="%s" y="%s"> [[ formatLang(time.strftime("%%Y-%%m-%%d"), date=True) ]] [[ time.strftime("%%H:%%M") ]]</drawString>
<setFont name="DejaVu Sans Bold" size="10"/>
<drawString x="382" y="555">[[ company.partner_id.name ]]</drawString>
<drawCentredString x="%s" y="%s">[[ company.partner_id.name ]]</drawCentredString>
<stroke color="#000000"/>
<lines>25 550 818 550</lines>
<lines>%s</lines>
</pageGraphics>
</pageTemplate>
</header>"""
def _get_header2(self,cr,uid,ids):
return """
<header>
<pageTemplate>
<frame id="first" x1="28.0" y1="28.0" width="539" height="772"/>
<pageGraphics>
<fill color="black"/>
<stroke color="black"/>
<setFont name="DejaVu Sans" size="8"/>
<drawString x="1.0cm" y="28.3cm"> [[ formatLang(time.strftime("%Y-%m-%d"), date=True) ]] [[ time.strftime("%H:%M") ]]</drawString>
<setFont name="DejaVu Sans Bold" size="10"/>
<drawString x="9.3cm" y="28.3cm">[[ company.partner_id.name ]]</drawString>
<stroke color="#000000"/>
<lines>1.0cm 28.1cm 20.1cm 28.1cm</lines>
</pageGraphics>
</pageTemplate>
</pageTemplate>
</header>"""
_header2 = _header % (539, 772, "1.0cm", "28.3cm", "11.1cm", "28.3cm", "1.0cm 28.1cm 20.1cm 28.1cm")
_header3 = _header % (786, 525, 25, 555, 440, 555, "25 550 818 550")
def _get_header(self,cr,uid,ids):
try :
header_file = tools.file_open(os.path.join('base', 'report', 'corporate_rml_header.rml'))
@@ -267,8 +254,8 @@ class res_company(osv.osv):
_defaults = {
'currency_id': _get_euro,
'rml_header':_get_header,
'rml_header2': _get_header2,
'rml_header3': _get_header3,
'rml_header2': _header2,
'rml_header3': _header3,
#'logo':_get_logo
}
+2 -2
View File
@@ -50,9 +50,9 @@ class lang(osv.osv):
if not lang:
return False
lang_ids = self.search(cr, uid, [('code','=', lang)])
ir_values_obj = self.pool.get('ir.values')
if not lang_ids:
lang_id = self.load_lang(cr, uid, lang)
self.load_lang(cr, uid, lang)
ir_values_obj = self.pool.get('ir.values')
default_value = ir_values_obj.get(cr, uid, 'default', False, ['res.partner'])
if not default_value:
ir_values_obj.set(cr, uid, 'default', False, 'lang', ['res.partner'], lang)
+1 -1
View File
@@ -38,7 +38,7 @@ class res_widget_user(osv.osv):
_order = "sequence"
_columns = {
'sequence': fields.integer('Sequence'),
'user_id': fields.many2one('res.users','User', select=1),
'user_id': fields.many2one('res.users','User', select=1, ondelete='cascade'),
'widget_id': fields.many2one('res.widget','Widget',required=True, ondelete='cascade'),
}
+4 -9
View File
@@ -274,22 +274,17 @@ def init_module_models(cr, module_name, obj_list):
logger.notifyChannel('init', netsvc.LOG_INFO,
'module %s: creating or updating database tables' % module_name)
# TODO _auto_init doesn't seem to return anything
# so this todo list would be useless.
todo = []
for obj in obj_list:
try:
# TODO the module in the context doesn't seem usefull:
# it is available (at least) in the class' _module attribute.
# (So module_name would be useless too.)
result = obj._auto_init(cr, {'module': module_name})
except Exception, e:
raise
result = obj._auto_init(cr, {'module': module_name})
if result:
todo += result
if hasattr(obj, 'init'):
obj.init(cr)
cr.commit()
for obj in obj_list:
obj._auto_end(cr, {'module': module_name})
cr.commit()
todo.sort()
for t in todo:
t[1](cr, *t[2])
+88 -40
View File
@@ -24,87 +24,135 @@
"""
import openerp.sql_db
import openerp.osv.orm
class BoundRegistry(object):
""" Model registry/database connection pair."""
def __init__(self, db, registry):
self.db = db
self.registry = registry
class Registry(object):
""" Model registry for a particular database.
The registry is essentially a mapping between model names and model
instances. There is one registry instance per database.
"""
def __init__(self, db_name):
self.models = {} # model name/model instance mapping
self._sql_error = {}
self._store_function = {}
self._init = True
self._init_parent = {}
self.db_name = db_name
self.db = openerp.sql_db.db_connect(db_name)
def do_parent_store(self, cr):
for o in self._init_parent:
self.get(o)._parent_store_compute(cr)
self._init = False
def obj_list(self):
""" Return the list of model names in this registry."""
return self.models.keys()
def add(self, model_name, model):
""" Add or replace a model in the registry."""
self.models[model_name] = model
def get(self, model_name):
""" Return a model for a given name or None if it doesn't exist."""
return self.models.get(model_name)
def __getitem__(self, model_name):
""" Return a model for a given name or raise KeyError if it doesn't exist."""
return self.models[model_name]
def instanciate(self, module, cr):
""" Instanciate all the classes of a given module for a particular db."""
res = []
# Instanciate classes registered through their constructor and
# add them to the pool.
for klass in openerp.osv.orm.module_class_list.get(module, []):
res.append(klass.createInstance(self, cr))
# Instanciate classes automatically discovered.
for cls in openerp.osv.orm.MetaModel.module_to_models.get(module, []):
if cls not in openerp.osv.orm.module_class_list.get(module, []):
res.append(cls.createInstance(self, cr))
return res
class RegistryManager(object):
""" Model registries manager.
The manager is responsible for creation and deletion of bound model
The manager is responsible for creation and deletion of model
registries (essentially database connection/model registry pairs).
"""
# TODO maybe should receive the addons paths
def __init__(self):
# Mapping between db name and bound model registry.
# Accessed through the methods below.
self.bound_registries = {}
# Mapping between db name and model registry.
# Accessed through the methods below.
registries = {}
def get(self, db_name, force_demo=False, status=None, update_module=False,
@classmethod
def get(cls, db_name, force_demo=False, status=None, update_module=False,
pooljobs=True):
""" Return a bound registry for a given database name."""
""" Return a registry for a given database name."""
if db_name in self.bound_registries:
bound_registry = self.bound_registries[db_name]
if db_name in cls.registries:
registry = cls.registries[db_name]
else:
bound_registry = self.new(db_name, force_demo, status,
registry = cls.new(db_name, force_demo, status,
update_module, pooljobs)
return bound_registry
return registry
def new(self, db_name, force_demo=False, status=None,
@classmethod
def new(cls, db_name, force_demo=False, status=None,
update_module=False, pooljobs=True):
""" Create and return a new bound registry for a given database name.
""" Create and return a new registry for a given database name.
The (possibly) previous bound registry for that database name is
discarded.
The (possibly) previous registry for that database name is discarded.
"""
import openerp.modules
import openerp.osv.osv as osv_osv
db = openerp.sql_db.db_connect(db_name)
pool = osv_osv.osv_pool()
registry = Registry(db_name)
# Initializing a registry will call general code which will in turn
# call registries.get (this object) to obtain the registry being
# initialized. Make it available in the bound_registries dictionary
# then remove it if an exception is raised.
self.delete(db_name)
bound_registry = BoundRegistry(db, pool)
self.bound_registries[db_name] = bound_registry
# initialized. Make it available in the registries dictionary then
# remove it if an exception is raised.
cls.delete(db_name)
cls.registries[db_name] = registry
try:
# This should be a method on BoundRegistry
openerp.modules.load_modules(db, force_demo, status, update_module)
# This should be a method on Registry
openerp.modules.load_modules(registry.db, force_demo, status, update_module)
except Exception:
del self.bound_registries[db_name]
del cls.registries[db_name]
raise
cr = db.cursor()
cr = registry.db.cursor()
try:
pool.do_parent_store(cr)
pool.get('ir.actions.report.xml').register_all(cr)
registry.do_parent_store(cr)
registry.get('ir.actions.report.xml').register_all(cr)
cr.commit()
finally:
cr.close()
if pooljobs:
pool.get('ir.cron').restart(db.dbname)
registry.get('ir.cron').restart(registry.db.dbname)
return bound_registry
return registry
def delete(self, db_name):
if db_name in self.bound_registries:
del self.bound_registries[db_name]
@classmethod
def delete(cls, db_name):
""" Delete the registry linked to a given database. """
if db_name in cls.registries:
del cls.registries[db_name]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+28 -18
View File
@@ -24,23 +24,45 @@
#
##############################################################################
import cgitb
import errno
import heapq
import logging
import logging.handlers
import os
import platform
import release
import socket
import sys
import threading
import time
import release
from pprint import pformat
import warnings
import heapq
import types
from pprint import pformat
# TODO modules that import netsvc only for things from loglevels must be changed to use loglevels.
from loglevels import *
import tools
def close_socket(sock):
""" Closes a socket instance cleanly
:param sock: the network socket to close
:type sock: socket.socket
"""
try:
sock.shutdown(socket.SHUT_RDWR)
except socket.error, e:
# On OSX, socket shutdowns both sides if any side closes it
# causing an error 57 'Socket is not connected' on shutdown
# 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':
raise
sock.close()
class Service(object):
""" Base class for *Local* services
@@ -380,19 +402,7 @@ class Server:
return '\n'.join(res)
def _close_socket(self):
if os.name != 'nt':
try:
self.socket.shutdown(getattr(socket, 'SHUT_RDWR', 2))
except socket.error, e:
if e.errno != errno.ENOTCONN: raise
# OSX, socket shutdowns both sides if any side closes it
# causing an error 57 'Socket is not connected' on shutdown
# of the other side (or something), see
# http://bugs.python.org/issue4397
self.__logger.debug(
'"%s" when shutting down server socket, '
'this is normal under OS X', e)
self.socket.close()
close_socket(self.socket)
class OpenERPDispatcherException(Exception):
def __init__(self, exception, traceback):
@@ -430,8 +440,8 @@ class OpenERPDispatcher:
except Exception, e:
self.log('exception', tools.exception_to_unicode(e))
tb = getattr(e, 'traceback', sys.exc_info())
tb_s = "".join(traceback.format_exception(*tb))
if tools.config['debug_mode']:
tb_s = cgitb.text(tb)
if tools.config['debug_mode'] and isinstance(tb, types.TracebackType):
import pdb
pdb.post_mortem(tb[2])
raise OpenERPDispatcherException(e, tb_s)
+93 -44
View File
@@ -692,24 +692,21 @@ class many2many(_column):
obj.datas[id][name] = act[2]
def get_nice_size(a):
(x,y) = a
if isinstance(y, (int,long)):
size = y
elif y:
size = len(y)
else:
size = 0
return (x, tools.human_size(size))
def get_nice_size(value):
size = 0
if isinstance(value, (int,long)):
size = value
elif value: # this is supposed to be a string
size = len(value)
return tools.human_size(size)
def sanitize_binary_value(dict_item):
def sanitize_binary_value(value):
# binary fields should be 7-bit ASCII base64-encoded data,
# but we do additional sanity checks to make sure the values
# are not something else that won't pass via xmlrpc
index, value = dict_item
if isinstance(value, (xmlrpclib.Binary, tuple, list, dict)):
# these builtin types are meant to pass untouched
return index, value
return value
# For all other cases, handle the value as a binary string:
# it could be a 7-bit ASCII string (e.g base64 data), but also
@@ -730,7 +727,7 @@ def sanitize_binary_value(dict_item):
# is not likely to produce the expected output, but this is
# just a safety mechanism (in these cases base64 data or
# xmlrpc.Binary values should be used instead)
return index, tools.ustr(value)
return tools.ustr(value)
# ---------------------------------------------------------
@@ -806,44 +803,44 @@ class function(_column):
return []
return self._fnct_search(obj, cr, uid, obj, name, args, context=context)
def get(self, cr, obj, ids, name, user=None, context=None, values=None):
def postprocess(self, cr, uid, obj, field, value=None, context=None):
if context is None:
context = {}
if values is None:
values = {}
res = {}
if self._method:
res = self._fnct(obj, cr, user, ids, name, self._arg, context)
else:
res = self._fnct(cr, obj._table, ids, name, self._arg, context)
result = value
field_type = obj._columns[field]._type
if field_type == "many2one":
# make the result a tuple if it is not already one
if isinstance(value, (int,long)) and hasattr(obj._columns[field], 'relation'):
obj_model = obj.pool.get(obj._columns[field].relation)
dict_names = dict(obj_model.name_get(cr, uid, [value], context))
result = (value, dict_names[value])
if self._type == "many2one" :
# Filtering only integer/long values if passed
res_ids = [x for x in res.values() if x and isinstance(x, (int,long))]
if res_ids:
obj_model = obj.pool.get(self._obj)
dict_names = dict(obj_model.name_get(cr, user, res_ids, context))
for r in res.keys():
if res[r] and res[r] in dict_names:
res[r] = (res[r], dict_names[res[r]])
if self._type == 'binary':
if field_type == 'binary':
if context.get('bin_size', False):
# client requests only the size of binary fields
res = dict(map(get_nice_size, res.items()))
result = get_nice_size(value)
else:
res = dict(map(sanitize_binary_value, res.items()))
result = sanitize_binary_value(value)
if field_type == "integer":
result = tools.ustr(value)
return result
def get(self, cr, obj, ids, name, uid=False, context=None, values=None):
result = {}
if self._method:
result = self._fnct(obj, cr, uid, ids, name, self._arg, context)
else:
result = self._fnct(cr, obj._table, ids, name, self._arg, context)
for id in ids:
if self._multi and id in result:
for field, value in result[id].iteritems():
if value:
result[id][field] = self.postprocess(cr, uid, obj, field, value, context)
elif result.get(id):
result[id] = self.postprocess(cr, uid, obj, name, result[id], context)
return result
if self._type == "integer":
for r in res.keys():
# Converting value into string so that it does not affect XML-RPC Limits
if isinstance(res[r],dict): # To treat integer values with _multi attribute
for record in res[r].keys():
res[r][record] = str(res[r][record])
else:
res[r] = str(res[r])
return res
get_memory = get
def set(self, cr, obj, id, name, value, user=None, context=None):
@@ -1120,5 +1117,57 @@ class property(function):
self.field_id = {}
def field_to_dict(self, cr, user, context, field):
""" Return a dictionary representation of a field.
The string, help, and selection attributes (if any) are untranslated. This
representation is the one returned by fields_get() (fields_get() will do
the translation).
"""
res = {'type': field._type}
# This additional attributes for M2M and function field is added
# because we need to display tooltip with this additional information
# when client is started in debug mode.
if isinstance(field, function):
res['function'] = field._fnct and field._fnct.func_name or False
res['store'] = field.store
if isinstance(field.store, dict):
res['store'] = str(field.store)
res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False
res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False
res['fnct_inv_arg'] = field._fnct_inv_arg or False
res['func_obj'] = field._obj or False
res['func_method'] = field._method
if isinstance(field, many2many):
res['related_columns'] = list((field._id1, field._id2))
res['third_table'] = field._rel
for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
'change_default', 'translate', 'help', 'select', 'selectable'):
if getattr(field, arg):
res[arg] = getattr(field, arg)
for arg in ('digits', 'invisible', 'filters'):
if getattr(field, arg, None):
res[arg] = getattr(field, arg)
if field.string:
res['string'] = field.string
if field.help:
res['help'] = field.help
if hasattr(field, 'selection'):
if isinstance(field.selection, (tuple, list)):
res['selection'] = field.selection
else:
# call the 'dynamic selection' function
res['selection'] = field.selection(self, cr, user, context)
if res['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
res['relation'] = field._obj
res['domain'] = field._domain
res['context'] = field._context
return res
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+299 -226
View File
@@ -68,6 +68,7 @@ regex_object_name = re.compile(r'^[a-z0-9_.]+$')
# Mapping between openerp module names and their osv classes.
module_class_list = {}
def check_object_name(name):
""" Check if the given name is a valid openerp object name.
@@ -416,6 +417,32 @@ def get_pg_type(f):
return f_type
class MetaModel(type):
""" Metaclass for the Model.
This class is used as the metaclass for the Model class to discover
the models defined in a module (i.e. without instanciating them).
If the automatic discovery is not needed, it is possible to set the
model's _register attribute to False.
"""
module_to_models = {}
def __init__(self, name, bases, attrs):
if not self._register:
self._register = True
super(MetaModel, self).__init__(name, bases, attrs)
return
module_name = self.__module__.split('.')[0]
if not hasattr(self, '_module'):
self._module = module_name
# Remember which models to instanciate for this module.
self.module_to_models.setdefault(self._module, []).append(self)
class orm_template(object):
""" Base class for OpenERP models.
@@ -445,6 +472,11 @@ class orm_template(object):
_sequence = None
_description = None
_inherits = {}
# Mapping from inherits'd field name to triple (m, r, f)
# where m is the model from which it is inherits'd,
# r is the (local) field towards m,
# and f is the _column object itself.
_inherit_fields = {}
_table = None
_invalids = set()
_log_create = False
@@ -469,9 +501,11 @@ class orm_template(object):
raise NotImplementedError(_('The read_group method is not implemented on this object !'))
def _field_create(self, cr, context=None):
"""
""" Create entries in ir_model_fields for all the model's fields.
Create/update entries in ir_model, ir_model_data, and ir_model_fields.
If necessary, also create an entry in ir_model, and if called from the
modules loading scheme (by receiving 'module' in the context), also
create entries in ir_model_data (for the model and the fields).
- create an entry in ir_model (if there is not already one),
- create an entry in ir_model_data (if there is not already one, and if
@@ -572,6 +606,9 @@ class orm_template(object):
raise_on_invalid_object_name(self._name)
self._field_create(cr, context=context)
def _auto_end(self, cr, context=None):
pass
#
# Goal: try to apply inheritance at the instanciation level and
# put objects in the pool var
@@ -637,7 +674,7 @@ class orm_template(object):
else:
new.extend(cls.__dict__.get(s, []))
nattr[s] = new
cls = type(name, (cls, parent_class), nattr)
cls = type(name, (cls, parent_class), dict(nattr, _register=False))
obj = object.__new__(cls)
obj.__init__(pool, cr)
return obj
@@ -830,9 +867,6 @@ class orm_template(object):
if x=='.id': return [x]
return x.replace(':id','/id').replace('.id','/.id').split('/')
fields_to_export = map(fsplit, fields_to_export)
fields_export = fields_to_export + []
warning = ''
warning_fields = []
datas = []
for row in self.browse(cr, uid, ids, context):
datas += self.__export_row(cr, uid, row, fields_to_export, context)
@@ -1020,7 +1054,6 @@ class orm_template(object):
if config.get('import_partial', False) and filename:
data = pickle.load(file(config.get('import_partial')))
original_value = data.get(filename, 0)
position = 0
while position<len(datas):
@@ -1033,7 +1066,7 @@ class orm_template(object):
return (-1, res, 'Line ' + str(position) +' : ' + '!\n'.join(warning), '')
try:
id = ir_model_data_obj._update(cr, uid, self._name,
ir_model_data_obj._update(cr, uid, self._name,
current_module, res, mode=mode, xml_id=xml_id,
noupdate=noupdate, res_id=res_id, context=context)
except Exception, e:
@@ -1222,92 +1255,62 @@ class orm_template(object):
def fields_get_keys(self, cr, user, context=None):
res = self._columns.keys()
# TODO I believe this loop can be replace by
# res.extend(self._inherit_fields.key())
for parent in self._inherits:
res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
return res
# returns the definition of each field in the object
# the optional fields parameter can limit the result to some fields
def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
""" Returns the definition of each field.
The returned value is a dictionary (indiced by field name) of
dictionaries. The _inherits'd fields are included. The string,
help, and selection (if present) attributes are translated.
"""
if context is None:
context = {}
res = {}
translation_obj = self.pool.get('ir.translation')
for parent in self._inherits:
res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
if self._columns.keys():
for f in self._columns.keys():
field_col = self._columns[f]
if allfields and f not in allfields:
continue
res[f] = {'type': field_col._type}
# This additional attributes for M2M and function field is added
# because we need to display tooltip with this additional information
# when client is started in debug mode.
if isinstance(field_col, fields.function):
res[f]['function'] = field_col._fnct and field_col._fnct.func_name or False
res[f]['store'] = field_col.store
if isinstance(field_col.store, dict):
res[f]['store'] = str(field_col.store)
res[f]['fnct_search'] = field_col._fnct_search and field_col._fnct_search.func_name or False
res[f]['fnct_inv'] = field_col._fnct_inv and field_col._fnct_inv.func_name or False
res[f]['fnct_inv_arg'] = field_col._fnct_inv_arg or False
res[f]['func_obj'] = field_col._obj or False
res[f]['func_method'] = field_col._method
if isinstance(field_col, fields.many2many):
res[f]['related_columns'] = list((field_col._id1, field_col._id2))
res[f]['third_table'] = field_col._rel
for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
'change_default', 'translate', 'help', 'select', 'selectable'):
if getattr(field_col, arg):
res[f][arg] = getattr(field_col, arg)
if not write_access:
res[f]['readonly'] = True
res[f]['states'] = {}
for arg in ('digits', 'invisible', 'filters'):
if getattr(field_col, arg, None):
res[f][arg] = getattr(field_col, arg)
for f, field in self._columns.iteritems():
if allfields and f not in allfields:
continue
if field_col.string:
res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
if res_trans:
res[f]['string'] = res_trans
if field_col.help:
help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
if help_trans:
res[f]['help'] = help_trans
res[f] = fields.field_to_dict(self, cr, user, context, field)
if hasattr(field_col, 'selection'):
if isinstance(field_col.selection, (tuple, list)):
sel = field_col.selection
# translate each selection option
sel2 = []
for (key, val) in sel:
val2 = None
if val:
val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
sel2.append((key, val2 or val))
sel = sel2
res[f]['selection'] = sel
else:
# call the 'dynamic selection' function
res[f]['selection'] = field_col.selection(self, cr, user, context)
if res[f]['type'] in ('one2many', 'many2many', 'many2one', 'one2one'):
res[f]['relation'] = field_col._obj
res[f]['domain'] = field_col._domain
res[f]['context'] = field_col._context
else:
#TODO : read the fields from the database
pass
if not write_access:
res['readonly'] = True
res['states'] = {}
if hasattr(res[f], 'string'):
res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
if res_trans:
res[f]['string'] = res_trans
if hasattr(res[f], 'help'):
help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
if help_trans:
res[f]['help'] = help_trans
if hasattr(res[f], 'selection'):
if isinstance(field.selection, (tuple, list)):
sel = field.selection
sel2 = []
for key, val in sel:
val2 = None
if val:
val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
sel2.append((key, val2 or val))
res[f]['selection'] = sel2
if allfields:
# filter out fields which aren't in the fields list
for r in res.keys():
if r not in allfields:
del res[r]
return res
#
# Overload this method if you need a window title which depends on the context
#
@@ -1599,7 +1602,7 @@ class orm_template(object):
:raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
"""
if not context:
if context is None:
context = {}
def encode(s):
@@ -1609,10 +1612,8 @@ class orm_template(object):
def raise_view_error(error_msg, child_view_id):
view, child_view = self.pool.get('ir.ui.view').browse(cr, user, [view_id, child_view_id], context)
raise AttributeError(("View definition error for inherited view '%(xml_id)s' on '%(model)s' model: " + error_msg)
% { 'xml_id': child_view.xml_id,
'parent_xml_id': view.xml_id,
'model': self._name, })
raise AttributeError("View definition error for inherited view '%s' on model '%s': %s"
% (child_view.xml_id, self._name, error_msg))
def _inherit_apply(src, inherit, inherit_id=None):
def _find(node, node2):
@@ -1705,7 +1706,6 @@ class orm_template(object):
result = {'type': view_type, 'model': self._name}
ok = True
model = True
sql_res = False
parent_view_model = None
while ok:
@@ -1739,7 +1739,6 @@ class orm_template(object):
ok = sql_res[5]
view_id = ok or sql_res[3]
model = False
parent_view_model = sql_res[6]
# if a view was found
@@ -1957,6 +1956,23 @@ class orm_template(object):
"""
return self._name_search(cr, user, name, args, operator, context, limit)
def name_create(self, cr, uid, name, context=None):
"""
Creates a new record by calling :py:meth:`~osv.osv.osv.create` with only one
value provided: the name of the new record (``_rec_name`` field).
The new record will also be initialized with any default values applicable
to this model, or provided through the context. The usual behavior of
:py:meth:`~osv.osv.osv.create` applies.
Similarly, this method may raise an exception if the model has multiple
required fields and some do not have default values.
:param name: name of the record to create
:return: the :py:meth:`~osv.osv.osv.name_get` value for the newly-created record.
"""
rec_id = self.create(cr, uid, {self._rec_name: name}, context);
return self.name_get(cr, uid, [rec_id], context)[0]
# private implementation of name_search, allows passing a dedicated user for the name_get part to
# solve some access rights issues
def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
@@ -2544,149 +2560,74 @@ class orm(orm_template):
- alter existing database columns to match _columns,
- create database tables to match _columns,
- add database indices to match _columns,
- save in self._foreign_keys a list a foreign keys to create (see
_auto_end).
"""
self._foreign_keys = []
raise_on_invalid_object_name(self._name)
if context is None:
context = {}
store_compute = False
create = False
todo_end = []
update_custom_fields = context.get('update_custom_fields', False)
self._field_create(cr, context=context)
create = not self._table_exist(cr)
if getattr(self, '_auto', True):
cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
if not cr.rowcount:
cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'", "''")))
create = True
self.__schema.debug("Table '%s': created", self._table)
if create:
self._create_table(cr)
cr.commit()
if self._parent_store:
cr.execute("""SELECT c.relname
FROM pg_class c, pg_attribute a
WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
""", (self._table, 'parent_left'))
if not cr.rowcount:
cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
if 'parent_left' not in self._columns:
self.__logger.error('create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)',
self._table)
self.__schema.debug("Table '%s': added column '%s' with definition=%s",
self._table, 'parent_left', 'INTEGER')
elif not self._columns['parent_left'].select:
self.__logger.error('parent_left column on object %s must be indexed! Add select=1 to the field definition)',
self._table)
if 'parent_right' not in self._columns:
self.__logger.error('create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)',
self._table)
self.__schema.debug("Table '%s': added column '%s' with definition=%s",
self._table, 'parent_right', 'INTEGER')
elif not self._columns['parent_right'].select:
self.__logger.error('parent_right column on object %s must be indexed! Add select=1 to the field definition)',
self._table)
if self._columns[self._parent_name].ondelete != 'cascade':
self.__logger.error("The column %s on object %s must be set as ondelete='cascade'",
self._parent_name, self._name)
cr.commit()
if not self._parent_columns_exist(cr):
self._create_parent_columns(cr)
store_compute = True
# Create the create_uid, create_date, write_uid, write_date, columns if desired.
if self._log_access:
logs = {
'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
'create_date': 'TIMESTAMP',
'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
'write_date': 'TIMESTAMP'
}
for k in logs:
cr.execute("""
SELECT c.relname
FROM pg_class c, pg_attribute a
WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
""", (self._table, k))
if not cr.rowcount:
cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
cr.commit()
self.__schema.debug("Table '%s': added column '%s' with definition=%s",
self._table, k, logs[k])
self._add_log_columns(cr)
self._check_removed_columns(cr, log=False)
# iterate on the "object columns"
todo_update_store = []
update_custom_fields = context.get('update_custom_fields', False)
column_data = self._select_column_data(cr)
cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size " \
"FROM pg_class c,pg_attribute a,pg_type t " \
"WHERE c.relname=%s " \
"AND c.oid=a.attrelid " \
"AND a.atttypid=t.oid", (self._table,))
col_data = dict(map(lambda x: (x['attname'], x),cr.dictfetchall()))
for k in self._columns:
for k, f in self._columns.iteritems():
if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'):
continue
#Not Updating Custom fields
if k.startswith('x_') and not update_custom_fields:
# Don't update custom (also called manual) fields
if f.manual and not update_custom_fields:
continue
f = self._columns[k]
if isinstance(f, fields.one2many):
cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,))
self._o2m_raise_on_missing_reference(cr, f)
if self.pool.get(f._obj):
if f._fields_id not in self.pool.get(f._obj)._columns.keys():
if not self.pool.get(f._obj)._inherits or (f._fields_id not in self.pool.get(f._obj)._inherit_fields.keys()):
raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
if cr.fetchone():
cr.execute("SELECT count(1) as c FROM pg_class c,pg_attribute a WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid", (f._obj, f._fields_id))
res = cr.fetchone()[0]
if not res:
cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY (%s) REFERENCES "%s" ON DELETE SET NULL' % (self._obj, f._fields_id, f._table))
self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE SET NULL",
self._obj, f._fields_id, f._table)
elif isinstance(f, fields.many2many):
cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
if not cr.dictfetchall():
if not self.pool.get(f._obj):
raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
ref = self.pool.get(f._obj)._table
# ref = f._obj.replace('.', '_')
cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL REFERENCES "%s" ON DELETE CASCADE, "%s" INTEGER NOT NULL REFERENCES "%s" ON DELETE CASCADE, UNIQUE("%s","%s")) WITH OIDS' % (f._rel, f._id1, self._table, f._id2, ref, f._id1, f._id2))
cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
cr.commit()
self.__schema.debug("Create table '%s': relation between '%s' and '%s'",
f._rel, self._table, ref)
self._m2m_raise_or_create_relation(cr, f)
else:
res = col_data.get(k, [])
res = res and [res] or []
res = column_data.get(k)
# The field is not found as-is in database, try if it
# exists with an old name.
if not res and hasattr(f, 'oldname'):
cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size " \
"FROM pg_class c,pg_attribute a,pg_type t " \
"WHERE c.relname=%s " \
"AND a.attname=%s " \
"AND c.oid=a.attrelid " \
"AND a.atttypid=t.oid", (self._table, f.oldname))
res_old = cr.dictfetchall()
if res_old and len(res_old) == 1:
res = column_data.get(f.oldname)
if res:
cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % (self._table, f.oldname, k))
res = res_old
res[0]['attname'] = k
res['attname'] = k
column_data[k] = res
self.__schema.debug("Table '%s': renamed column '%s' to '%s'",
self._table, f.oldname, k)
if len(res) == 1:
f_pg_def = res[0]
f_pg_type = f_pg_def['typname']
f_pg_size = f_pg_def['size']
f_pg_notnull = f_pg_def['attnotnull']
# The field already exists in database. Possibly
# change its type, rename it, drop it or change its
# constraints.
if res:
f_pg_type = res['typname']
f_pg_size = res['size']
f_pg_notnull = res['attnotnull']
if isinstance(f, fields.function) and not f.store and\
not getattr(f, 'nodrop', False):
self.__logger.info('column %s (%s) in table %s removed: converted to a function !\n',
@@ -2824,13 +2765,13 @@ class orm(orm_template):
if res2:
if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"')
cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete)
self._foreign_keys.append((self._table, k, ref, f.ondelete))
cr.commit()
self.__schema.debug("Table '%s': column '%s': XXX",
self._table, k)
elif len(res) > 1:
netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, "Programming error, column %s->%s has multiple instances !" % (self._table, k))
if not res:
# The field doesn't exist in database. Create it if necessary.
else:
if not isinstance(f, fields.function) or f.store:
# add the missing field
cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
@@ -2851,21 +2792,21 @@ class orm(orm_template):
cr.commit()
netsvc.Logger().notifyChannel('data', netsvc.LOG_DEBUG, "Table '%s': setting default value of new column %s" % (self._table, k))
# remember the functions to call for the stored fields
if isinstance(f, fields.function):
order = 10
if f.store is not True:
if f.store is not True: # i.e. if f.store is a dict
order = f.store[f.store.keys()[0]][2]
todo_update_store.append((order, f, k))
todo_end.append((order, self._update_store, (f, k)))
# and add constraints if needed
if isinstance(f, fields.many2one):
if not self.pool.get(f._obj):
raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
ref = self.pool.get(f._obj)._table
# ref = f._obj.replace('.', '_')
# ir_actions is inherited so foreign key doesn't work on it
if ref != 'ir_actions':
cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete))
self._foreign_keys.append((self._table, k, ref, f.ondelete))
self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s",
self._table, k, ref, f.ondelete)
if f.select:
@@ -2883,8 +2824,6 @@ class orm(orm_template):
"ALTER TABLE %s ALTER COLUMN %s SET NOT NULL"
self.__logger.warn(msg, k, self._table, self._table, k)
cr.commit()
for order, f, k in todo_update_store:
todo_end.append((order, self._update_store, (f, k)))
else:
cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
@@ -2892,6 +2831,134 @@ class orm(orm_template):
cr.commit() # start a new transaction
self._add_sql_constraints(cr)
if create:
self._execute_sql(cr)
if store_compute:
self._parent_store_compute(cr)
cr.commit()
return todo_end
def _auto_end(self, cr, context=None):
""" Create the foreign keys recorded by _auto_init. """
for t, k, r, d in self._foreign_keys:
cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (t, k, r, d))
cr.commit()
del self._foreign_keys
def _table_exist(self, cr):
cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (self._table,))
return cr.rowcount
def _create_table(self, cr):
cr.execute('CREATE TABLE "%s" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITHOUT OIDS' % (self._table,))
cr.execute("COMMENT ON TABLE \"%s\" IS '%s'" % (self._table, self._description.replace("'", "''")))
self.__schema.debug("Table '%s': created", self._table)
def _parent_columns_exist(self, cr):
cr.execute("""SELECT c.relname
FROM pg_class c, pg_attribute a
WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
""", (self._table, 'parent_left'))
return cr.rowcount
def _create_parent_columns(self, cr):
cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_left" INTEGER' % (self._table,))
cr.execute('ALTER TABLE "%s" ADD COLUMN "parent_right" INTEGER' % (self._table,))
if 'parent_left' not in self._columns:
self.__logger.error('create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)',
self._table)
self.__schema.debug("Table '%s': added column '%s' with definition=%s",
self._table, 'parent_left', 'INTEGER')
elif not self._columns['parent_left'].select:
self.__logger.error('parent_left column on object %s must be indexed! Add select=1 to the field definition)',
self._table)
if 'parent_right' not in self._columns:
self.__logger.error('create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)',
self._table)
self.__schema.debug("Table '%s': added column '%s' with definition=%s",
self._table, 'parent_right', 'INTEGER')
elif not self._columns['parent_right'].select:
self.__logger.error('parent_right column on object %s must be indexed! Add select=1 to the field definition)',
self._table)
if self._columns[self._parent_name].ondelete != 'cascade':
self.__logger.error("The column %s on object %s must be set as ondelete='cascade'",
self._parent_name, self._name)
cr.commit()
def _add_log_columns(self, cr):
logs = {
'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
'create_date': 'TIMESTAMP',
'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
'write_date': 'TIMESTAMP'
}
for k in logs:
cr.execute("""
SELECT c.relname
FROM pg_class c, pg_attribute a
WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid
""", (self._table, k))
if not cr.rowcount:
cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, logs[k]))
cr.commit()
self.__schema.debug("Table '%s': added column '%s' with definition=%s",
self._table, k, logs[k])
def _select_column_data(self, cr):
cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size " \
"FROM pg_class c,pg_attribute a,pg_type t " \
"WHERE c.relname=%s " \
"AND c.oid=a.attrelid " \
"AND a.atttypid=t.oid", (self._table,))
return dict(map(lambda x: (x['attname'], x),cr.dictfetchall()))
def _o2m_raise_on_missing_reference(self, cr, f):
# TODO this check should be a method on fields.one2many.
other = self.pool.get(f._obj)
if other:
# TODO the condition could use fields_get_keys().
if f._fields_id not in other._columns.keys():
if f._fields_id not in other._inherit_fields.keys():
raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
def _m2m_raise_or_create_relation(self, cr, f):
cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
if not cr.dictfetchall():
if not self.pool.get(f._obj):
raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
ref = self.pool.get(f._obj)._table
cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL, "%s" INTEGER NOT NULL, UNIQUE("%s","%s")) WITH OIDS' % (f._rel, f._id1, f._id2, f._id1, f._id2))
self._foreign_keys.append((f._rel, f._id1, self._table, 'CASCADE'))
self._foreign_keys.append((f._rel, f._id2, ref, 'CASCADE'))
cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
cr.commit()
self.__schema.debug("Create table '%s': relation between '%s' and '%s'",
f._rel, self._table, ref)
def _add_sql_constraints(self, cr):
"""
Modify this model's database table constraints so they match the one in
_sql_constraints.
"""
for (key, con, _) in self._sql_constraints:
conname = '%s_%s' % (self._table, key)
@@ -2940,17 +3007,16 @@ class orm(orm_template):
self.__schema.warn(sql_action['msg_err'])
cr.rollback()
if create:
if hasattr(self, "_sql"):
for line in self._sql.split(';'):
line2 = line.replace('\n', '').strip()
if line2:
cr.execute(line2)
cr.commit()
if store_compute:
self._parent_store_compute(cr)
cr.commit()
return todo_end
def _execute_sql(self, cr):
""" Execute the SQL code from the _sql attribute (if any)."""
if hasattr(self, "_sql"):
for line in self._sql.split(';'):
line2 = line.replace('\n', '').strip()
if line2:
cr.execute(line2)
cr.commit()
@classmethod
def createInstance(cls, pool, cr):
@@ -3029,18 +3095,18 @@ class orm(orm_template):
}
if field['ttype'] == 'selection':
self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs)
self._columns[field['name']] = fields.selection(eval(field['selection']), **attrs)
elif field['ttype'] == 'reference':
self._columns[field['name']] = getattr(fields, field['ttype'])(selection=eval(field['selection']), **attrs)
self._columns[field['name']] = fields.reference(selection=eval(field['selection']), **attrs)
elif field['ttype'] == 'many2one':
self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs)
self._columns[field['name']] = fields.many2one(field['relation'], **attrs)
elif field['ttype'] == 'one2many':
self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], field['relation_field'], **attrs)
self._columns[field['name']] = fields.one2many(field['relation'], field['relation_field'], **attrs)
elif field['ttype'] == 'many2many':
_rel1 = field['relation'].replace('.', '_')
_rel2 = field['model'].replace('.', '_')
_rel_name = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], _rel_name, 'id1', 'id2', **attrs)
self._columns[field['name']] = fields.many2many(field['relation'], _rel_name, 'id1', 'id2', **attrs)
else:
self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
self._inherits_check()
@@ -3059,18 +3125,25 @@ class orm(orm_template):
#
def _inherits_reload_src(self):
for obj in self.pool.obj_pool.values():
""" Recompute the _inherit_fields mapping on each _inherits'd child model."""
for obj in self.pool.models.values():
if self._name in obj._inherits:
obj._inherits_reload()
def _inherits_reload(self):
""" Recompute the _inherit_fields mapping.
This will also call itself on each inherits'd child model.
"""
res = {}
for table in self._inherits:
res.update(self.pool.get(table)._inherit_fields)
for col in self.pool.get(table)._columns.keys():
res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col])
for col in self.pool.get(table)._inherit_fields.keys():
res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2])
other = self.pool.get(table)
res.update(other._inherit_fields)
for col in other._columns.keys():
res[col] = (table, self._inherits[table], other._columns[col])
for col in other._inherit_fields.keys():
res[col] = (table, self._inherits[table], other._inherit_fields[col][2])
self._inherit_fields = res
self._inherits_reload_src()
+10 -55
View File
@@ -23,18 +23,16 @@
# OSV: Objects Services
#
import sys
import inspect
import orm
import openerp.netsvc as netsvc
import openerp.pooler as pooler
import openerp.sql_db as sql_db
import copy
import logging
from psycopg2 import IntegrityError, errorcodes
from openerp.tools.func import wraps
from openerp.tools.translate import translate
from openerp.osv.orm import module_class_list
from openerp.osv.orm import MetaModel
class except_osv(Exception):
def __init__(self, name, value, exc_type='warning'):
@@ -79,7 +77,7 @@ class object_proxy(netsvc.Service):
# We open a *new* cursor here, one reason is that failed SQL
# queries (as in IntegrityError) will invalidate the current one.
cr = False
if hasattr(src, '__call__'):
# callable. We need to find the right parameters to call
# the orm._sql_message(self, cr, uid, ids, context) function,
@@ -92,18 +90,18 @@ class object_proxy(netsvc.Service):
ids = args[3]
else:
ids = []
cr = sql_db.db_connect(db_name).cursor()
cr = sql_db.db_connect(dbname).cursor()
return src(obj, cr, uid, ids, context=(ctx or {}))
except Exception:
pass
finally:
if cr: cr.close()
return False # so that the original SQL error will
# be returned, it is the best we have.
try:
cr = sql_db.db_connect(db_name).cursor()
cr = sql_db.db_connect(dbname).cursor()
res = translate(cr, name=False, source_type=ttype,
lang=lang, source=src)
if res:
@@ -203,59 +201,16 @@ class object_proxy(netsvc.Service):
return res
class osv_pool(object):
""" Model registry for a particular database.
The registry is essentially a mapping between model names and model
instances. There is one registry instance per database.
"""
def __init__(self):
self.obj_pool = {} # model name/model instance mapping
self._sql_error = {}
self._store_function = {}
self._init = True
self._init_parent = {}
def do_parent_store(self, cr):
for o in self._init_parent:
self.get(o)._parent_store_compute(cr)
self._init = False
def obj_list(self):
""" Return the list of model names in this registry."""
return self.obj_pool.keys()
def add(self, model_name, model):
""" Add or replace a model in the registry."""
self.obj_pool[model_name] = model
def get(self, name):
""" Return a model for a given name or None if it doesn't exist."""
return self.obj_pool.get(name)
def instanciate(self, module, cr):
""" Instanciate all the classes of a given module for a particular db."""
res = []
# Instanciate classes registered through their constructor and
# add them to the pool.
for klass in module_class_list.get(module, []):
res.append(klass.createInstance(self, cr))
return res
class osv_memory(orm.orm_memory):
""" Deprecated class. """
pass
__metaclass__ = MetaModel
_register = False # Set to false if the model shouldn't be automatically discovered.
class osv(orm.orm):
""" Deprecated class. """
pass
__metaclass__ = MetaModel
_register = False # Set to false if the model shouldn't be automatically discovered.
def start_object_proxy():
+5 -16
View File
@@ -27,33 +27,22 @@
from openerp.modules.registry import RegistryManager
_Registries = None
def ensure_registries():
global _Registries
if _Registries is None:
_Registries = RegistryManager()
def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False, pooljobs=True):
"""Create and return a database connection and a newly initialized registry."""
ensure_registries()
bound_registry = _Registries.get(db_name, force_demo, status, update_module, pooljobs)
return bound_registry.db, bound_registry.registry
registry = RegistryManager.get(db_name, force_demo, status, update_module, pooljobs)
return registry.db, registry
def delete_pool(db_name):
"""Delete an existing registry."""
ensure_registries()
_Registries.delete(db_name)
RegistryManager.delete(db_name)
def restart_pool(db_name, force_demo=False, status=None, update_module=False):
"""Delete an existing registry and return a database connection and a newly initialized registry."""
ensure_registries()
bound_registry = _Registries.new(db_name, force_demo, status, update_module, True)
return bound_registry.db, bound_registry.registry
registry = RegistryManager.new(db_name, force_demo, status, update_module, True)
return registry.db, registry
def get_db(db_name):
+5 -3
View File
@@ -631,9 +631,11 @@ class _rml_flowable(object):
paraStyle = None
if tr.get('style'):
st = copy.deepcopy(self.styles.table_styles[tr.get('style')])
for s in st._cmds:
s[1][1] = posy
s[2][1] = posy
for si in range(len(st._cmds)):
s = list(st._cmds[si])
s[1] = (s[1][0],posy)
s[2] = (s[2][0],posy)
st._cmds[si] = tuple(s)
styles.append(st)
if tr.get('paraStyle'):
paraStyle = self.styles.styles[tr.get('paraStyle')]
+3 -10
View File
@@ -45,16 +45,6 @@ class TinySocketClientThread(threading.Thread, netsvc.OpenERPDispatcher):
self.sock.settimeout(1200)
self.threads = threads
def __del__(self):
if self.sock:
try:
self.socket.shutdown(
getattr(socket, 'SHUT_RDWR', 2))
except Exception:
pass
# That should garbage-collect and close it, too
self.sock = None
def run(self):
self.running = True
try:
@@ -87,11 +77,14 @@ class TinySocketClientThread(threading.Thread, netsvc.OpenERPDispatcher):
tb_s = "".join(traceback.format_exception(*tb))
logging.getLogger('web-services').debug("netrpc: communication-level exception", exc_info=True)
ts.mysend(e, exception=True, traceback=tb_s)
break
except Exception, ex:
#terminate this channel if we can't properly send back the error
logging.getLogger('web-services').exception("netrpc: cannot deliver exception message to client")
break
netsvc.close_socket(self.sock)
self.sock = None
self.threads.remove(self)
self.running = False
return True
+5
View File
@@ -90,7 +90,12 @@ class db(netsvc.ExportService):
cr = None
try:
serv.actions[id]['progress'] = 0
cr = sql_db.db_connect(db_name).cursor()
openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by pooler.restart_pool.
tools.config['lang'] = lang
cr.commit()
cr.close()
pool = pooler.restart_pool(db_name, demo, serv.actions[id],
update_module=True)[1]
+3 -2
View File
@@ -24,6 +24,8 @@ import cPickle
import cStringIO
import marshal
import netsvc
class Myexception(Exception):
"""
custome exception object store
@@ -56,8 +58,7 @@ class mysocket:
self.sock.connect((host, int(port)))
def disconnect(self):
self.sock.shutdown(socket.SHUT_RDWR)
self.sock.close()
netsvc.close_socket(self.sock)
def mysend(self, msg, exception=False, traceback=None):
msg = cPickle.dumps([msg,traceback])
+16 -2
View File
@@ -72,6 +72,9 @@ class configmanager(object):
['publisher_warranty_url', 'load_language', 'root_path',
'init', 'save', 'config', 'update'])
# dictionary mapping option destination (keys in self.options) to MyOptions.
self.casts = {}
self.misc = {}
self.config_file = fname
self.has_ssl = check_ssl()
@@ -251,11 +254,14 @@ class configmanager(object):
for group in parser.option_groups:
for option in group.option_list:
self.options[option.dest] = option.my_default
self.casts[option.dest] = option
self.parse_config()
def parse_config(self, args=[]):
opt, args = self.parser.parse_args()
def parse_config(self, args=None):
if args is None:
args = []
opt, args = self.parser.parse_args(args)
def die(cond, msg):
if cond:
@@ -314,8 +320,12 @@ class configmanager(object):
]
for arg in keys:
# Copy the command-line argument...
if getattr(opt, arg):
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])
keys = [
'language', 'translate_out', 'translate_in', 'overwrite_existing_translations',
@@ -327,8 +337,12 @@ class configmanager(object):
]
for arg in keys:
# Copy the command-line argument...
if getattr(opt, arg) is not None:
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 opt.assert_exit_level:
self.options['assert_exit_level'] = self._LOGLEVELS[opt.assert_exit_level]
+34 -5
View File
@@ -36,6 +36,7 @@ import threading
import time
import warnings
import zipfile
from collections import defaultdict
from datetime import datetime
from email.MIMEText import MIMEText
from email.MIMEBase import MIMEBase
@@ -1398,18 +1399,46 @@ class unquote(str):
or escaping, keeping the original string untouched. The name come from Lisp's unquote.
One of the uses for this is to preserve or insert bare variable names within dicts during eval()
of a dict's repr(). Use with care.
Some examples:
Some examples (notice that there are never quotes surrounding
the ``active_id`` name:
>>> unquote('active_id')
active_id
>>> repr(unquote('active_id'))
active_id
>>> d = {'test': unquote('active_id')}
>>> d
{'test': active_id}
>>> repr(d)
"{'test': active_id}"
>>> print d
{'test': active_id}
"""
def __repr__(self):
return self
class UnquoteEvalContext(defaultdict):
"""Defaultdict-based evaluation context that returns
an ``unquote`` string for any missing name used during
the evaluation.
Mostly useful for evaluating OpenERP domains/contexts that
may refer to names that are unknown at the time of eval,
so that when the context/domain is converted back to a string,
the original names are preserved.
**Warning**: using an ``UnquoteEvalContext`` as context for ``eval()`` or
``safe_eval()`` will shadow the builtins, which may cause other
failures, depending on what is evaluated.
Example (notice that ``section_id`` is preserved in the final
result) :
>>> context_str = "{'default_user_id': uid, 'default_section_id': section_id}"
>>> eval(context_str, UnquoteEvalContext(uid=1))
{'default_user_id': 1, 'default_section_id': section_id}
"""
def __init__(self, *args, **kwargs):
super(UnquoteEvalContext, self).__init__(None, *args, **kwargs)
def __missing__(self, key):
return unquote(key)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+1
View File
@@ -274,6 +274,7 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
'tuple': tuple,
'map': map,
'abs': abs,
'min': min,
'reduce': reduce,
'filter': filter,
'round': round,
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
import os
import signal
import sys
import threading
import traceback
import time
import openerp
openerp.tools.config.parse_config(sys.argv[1:])
config = openerp.tools.config
for name in [
'db_name',
'addons_path',
'demo',
'osv_memory_count_limit',
'osv_memory_age_limit',
]:
print "%s: %s - %s" % (name, config[name], type(config[name]))
+3
View File
@@ -0,0 +1,3 @@
[options]
osv_memory_count_limit = 5
osv_memory_age_limit = 3.4
+46
View File
@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
""" Tests for the configuration file/command-line arguments. """
# This test should be run from its directory.
# TODO A configmanager object cannot parse multiple times a config file
# and/or the command line, preventing to 'reload' a configuration.
import os
import config
config_file_00 = os.path.join(os.path.dirname(__file__),'test-config-values-00.conf')
# 1. No config file, no command-line arguments (a.k.a. default values)
conf = config.configmanager()
conf.parse_config()
assert conf['osv_memory_age_limit'] == 1.0
assert os.path.join(conf['root_path'], 'addons') == conf['addons_path']
# 2. No config file, some command-line arguments
conf = config.configmanager()
# mess with the optparse.Option definition to allow an invalid path
conf.casts['addons_path'].action = 'store'
conf.parse_config(['--addons-path=/xyz/dont-exist', '--osv-memory-age-limit=2.3'])
assert conf['osv_memory_age_limit'] == 2.3
assert conf['addons_path'] == '/xyz/dont-exist'
# 3. Config file, no command-line arguments
conf = config.configmanager()
conf.parse_config(['-c', config_file_00])
assert conf['osv_memory_age_limit'] == 3.4
# 4. Config file, and command-line arguments
conf = config.configmanager()
conf.parse_config(['-c', config_file_00, '--osv-memory-age-limit=2.3'])
assert conf['osv_memory_age_limit'] == 2.3
+1 -2
View File
@@ -536,7 +536,7 @@ def trans_generate(lang, modules, cr):
trans_obj = pool.get('ir.translation')
model_data_obj = pool.get('ir.model.data')
uid = 1
l = pool.obj_pool.items()
l = pool.models.items()
l.sort()
query = 'SELECT name, model, res_id, module' \
@@ -864,7 +864,6 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
pool = pooler.get_pool(db_name)
lang_obj = pool.get('res.lang')
trans_obj = pool.get('ir.translation')
model_data_obj = pool.get('ir.model.data')
iso_lang = misc.get_iso_codes(lang)
try:
uid = 1
+1 -1
View File
@@ -105,7 +105,7 @@ class TestReport(object):
success += self._report[severity][True]
failure += self._report[severity][False]
res.append("total\t%s\t%s" % (success, failure))
res.append("end of report (%s assertion(s) checked)" % success + failure)
res.append("end of report (%s assertion(s) checked)" % (success + failure))
return "\n".join(res)
class RecordDictWrapper(dict):
+1 -1
View File
@@ -89,7 +89,7 @@ if os.name == 'nt':
"pydot", "asyncore","asynchat", "reportlab", "vobject",
"HTMLParser", "select", "mako", "poplib",
"imaplib", "smtplib", "email", "yaml", "DAV",
"uuid", "commands", "openerp",
"uuid", "commands", "openerp", # TODO is it necessary to include cgitb?
],
"excludes" : ["Tkconstants","Tkinter","tcl"],
}