[MERGE] Forward-port 6.1 bugfixes up to rev. 4307

rev.4307 = rev-id launchpad_translations_on_behalf_of_openerp-20121125065030-g5if5pybr61la4yj

bzr revid: odo@openerp.com-20121126160320-t2suuik6lhk6wl0x
bzr revid: odo@openerp.com-20121126161836-mv8yymzgbnmif4ve
bzr revid: odo@openerp.com-20121126162023-ejvsapwouyudw6c6
bzr revid: odo@openerp.com-20121126181527-d4yx15372bis77qj
This commit is contained in:
Olivier Dony 2012-11-26 19:15:27 +01:00
commit 7f5ad72429
13 changed files with 193 additions and 84 deletions

View File

@ -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)

View File

@ -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):

View File

@ -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."),

View File

@ -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)

View File

@ -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:

View File

@ -1111,7 +1111,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'],
@ -1838,7 +1838,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 +2670,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 +2703,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 +3904,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 +4311,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 +5209,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

View File

@ -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(

View File

@ -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):

View File

@ -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]

View File

@ -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):

View File

@ -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):

View File

@ -350,35 +350,37 @@ 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:

View File

@ -19,9 +19,16 @@
#
##############################################################################
import os
import glob
from distutils.core import setup
import py2exe
def datas():
r = []
if os.name == 'nt':
r.append(("Microsoft.VC90.CRT", glob.glob('C:\Microsoft.VC90.CRT\*.*')))
return r
setup(service=["OpenERPServerService"],
options={"py2exe":{"excludes":["Tkconstants","Tkinter","tcl",
@ -29,7 +36,8 @@ setup(service=["OpenERPServerService"],
"ImageTk", "PIL.ImageTk",
"FixTk"],
"skip_archive": 1,
"optimize": 2,}}
"optimize": 2,}},
data_files=datas(),
)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: