bzr revid: fp@tinyerp.com-20100917171629-871qzyay4tiyri20
This commit is contained in:
Fabien Pinckaers
2010-09-17 19:16:29 +02:00
16 changed files with 163 additions and 166 deletions
+2 -1
View File
@@ -286,7 +286,8 @@ class act_window_view(osv.osv):
('form', 'Form'),
('graph', 'Graph'),
('calendar', 'Calendar'),
('gantt', 'Gantt')), string='View Type', required=True),
('gantt', 'Gantt'),
('gallery', 'Gallery')), string='View Type', required=True),
'act_window_id': fields.many2one('ir.actions.act_window', 'Action', ondelete='cascade'),
'multi': fields.boolean('On Multiple Doc.',
help="If set to true, the action will not be displayed on the right toolbar of a form view."),
+1
View File
@@ -64,6 +64,7 @@ class view(osv.osv):
('calendar', 'Calendar'),
('diagram','Diagram'),
('gantt', 'Gantt'),
('gallery', 'Gallery'),
('search','Search')), 'View Type', required=True),
'arch': fields.text('View Architecture', required=True),
'inherit_id': fields.many2one('ir.ui.view', 'Inherited View', ondelete='cascade'),
+1 -1
View File
@@ -203,7 +203,7 @@ class res_company(osv.osv):
return """
<header>
<pageTemplate>
<frame id="first" x1="25" y1="25" width="1070" height="717"/>
<frame id="first" x1="22.0" y1="22.0" width="1080" height="700"/>
<pageGraphics>
<fill color="black"/>
<stroke color="black"/>
+8 -2
View File
@@ -412,8 +412,14 @@ class users(osv.osv):
return self.name_get(cr, user, ids)
def copy(self, cr, uid, id, default=None, context={}):
login = self.read(cr, uid, [id], ['login'])[0]['login']
default.update({'login': login+' (copy)'})
user2copy = self.read(cr, uid, [id], ['login','name'])[0]
if default is None:
default = {}
copy_pattern = _("%s (copy)")
default.update(login=(copy_pattern % user2copy['login']),
name=(copy_pattern % user2copy['name']),
address_id=False, # avoid sharing the address of the copied user!
)
return super(users, self).copy(cr, uid, id, default, context)
def context_get(self, cr, uid, context=None):
-21
View File
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<rng:grammar xmlns:rng="http://relaxng.org/ns/structure/1.0">
<rng:define name="any">
<rng:element>
<rng:anyName/>
<rng:zeroOrMore>
<rng:choice>
<rng:attribute>
<rng:anyName/>
</rng:attribute>
<rng:text/>
<rng:ref name="any"/>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:start>
<rng:ref name="any" />
</rng:start>
</rng:grammar>
-21
View File
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<rng:grammar xmlns:rng="http://relaxng.org/ns/structure/1.0">
<rng:define name="any">
<rng:element>
<rng:anyName/>
<rng:zeroOrMore>
<rng:choice>
<rng:attribute>
<rng:anyName/>
</rng:attribute>
<rng:text/>
<rng:ref name="any"/>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:start>
<rng:ref name="any" />
</rng:start>
</rng:grammar>
-21
View File
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<rng:grammar xmlns:rng="http://relaxng.org/ns/structure/1.0">
<rng:define name="any">
<rng:element>
<rng:anyName/>
<rng:zeroOrMore>
<rng:choice>
<rng:attribute>
<rng:anyName/>
</rng:attribute>
<rng:text/>
<rng:ref name="any"/>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:start>
<rng:ref name="any" />
</rng:start>
</rng:grammar>
-21
View File
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<rng:grammar xmlns:rng="http://relaxng.org/ns/structure/1.0">
<rng:define name="any">
<rng:element>
<rng:anyName/>
<rng:zeroOrMore>
<rng:choice>
<rng:attribute>
<rng:anyName/>
</rng:attribute>
<rng:text/>
<rng:ref name="any"/>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:start>
<rng:ref name="any" />
</rng:start>
</rng:grammar>
-21
View File
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<rng:grammar xmlns:rng="http://relaxng.org/ns/structure/1.0">
<rng:define name="any">
<rng:element>
<rng:anyName/>
<rng:zeroOrMore>
<rng:choice>
<rng:attribute>
<rng:anyName/>
</rng:attribute>
<rng:text/>
<rng:ref name="any"/>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:start>
<rng:ref name="any" />
</rng:start>
</rng:grammar>
-21
View File
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<rng:grammar xmlns:rng="http://relaxng.org/ns/structure/1.0">
<rng:define name="any">
<rng:element>
<rng:anyName/>
<rng:zeroOrMore>
<rng:choice>
<rng:attribute>
<rng:anyName/>
</rng:attribute>
<rng:text/>
<rng:ref name="any"/>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:start>
<rng:ref name="any" />
</rng:start>
</rng:grammar>
+17
View File
@@ -397,6 +397,7 @@
<rng:ref name="button"/>
<rng:ref name="group"/>
<rng:ref name="graph"/>
<rng:ref name="gallery"/>
<rng:ref name="html"/>
<rng:element name="newline"><rng:empty/></rng:element>
</rng:choice>
@@ -440,6 +441,7 @@
<rng:optional><rng:attribute name="required"/></rng:optional>
<rng:optional><rng:attribute name="readonly"/></rng:optional>
<rng:optional><rng:attribute name="view_mode"/></rng:optional>
<rng:optional><rng:attribute name="search_mode"/></rng:optional>
<rng:optional><rng:attribute name="widget"/></rng:optional>
<rng:optional><rng:attribute name="context"/></rng:optional>
<rng:optional><rng:attribute name="states"/></rng:optional>
@@ -470,6 +472,7 @@
<rng:ref name="button"/>
<rng:ref name="group"/>
<rng:ref name="graph"/>
<rng:ref name="gallery"/>
<rng:ref name="filter"/>
<rng:ref name="notebook"/>
<rng:ref name="html"/>
@@ -532,6 +535,18 @@
</rng:element>
</rng:define>
<rng:define name="gallery">
<rng:element name="gallery">
<rng:optional><rng:attribute name="string"/></rng:optional>
<rng:optional><rng:attribute name="width"/></rng:optional>
<rng:optional><rng:attribute name="height"/></rng:optional>
<rng:optional><rng:attribute name="col"/></rng:optional>
<rng:oneOrMore>
<rng:ref name="field"/>
</rng:oneOrMore>
</rng:element>
</rng:define>
<rng:define name="button">
<rng:element name="button">
<rng:ref name="overload"/>
@@ -561,6 +576,7 @@
<rng:ref name="graph" />
<rng:ref name="calendar" />
<rng:ref name="gantt" />
<rng:ref name="gallery" />
<rng:ref name="xpath" />
<rng:ref name="page" />
<rng:ref name="separator"/>
@@ -629,6 +645,7 @@
<rng:ref name="calendar" />
<rng:ref name="data" />
<rng:ref name="gantt" />
<rng:ref name="gallery" />
<rng:ref name="xpath" />
<rng:ref name="page" />
<rng:ref name="separator"/>
+90 -30
View File
@@ -2215,7 +2215,7 @@ class orm(orm_template):
# Take care of adding join(s) if groupby is an '_inherits'ed field
groupby_list = groupby
if groupby:
if groupby and isinstance(groupby, list):
if isinstance(groupby, list):
groupby = groupby[0]
tables, where_clause, qfield = self._inherits_join_calc(groupby, tables, where_clause)
@@ -2234,7 +2234,7 @@ class orm(orm_template):
flist = ''
group_by = groupby
if groupby:
if fget.get(groupby, False):
if fget.get(groupby):
if fget[groupby]['type'] in ('date', 'datetime'):
flist = "to_char(%s,'yyyy-mm') as %s " % (groupby, groupby)
groupby = "to_char(%s,'yyyy-mm')" % (groupby)
@@ -2256,10 +2256,7 @@ class orm(orm_template):
flist += ','
flist += operator+'('+f+') as '+f
if groupby:
gb = ' group by '+groupby
else:
gb = ''
gb = groupby and (' group by '+groupby) or ''
cr.execute('select min(%s.id) as id,' % self._table + flist + ' from ' + ','.join(tables) + where_clause + gb + limit_str + offset_str, where_clause_params)
alldata = {}
groupby = group_by
@@ -2268,7 +2265,15 @@ class orm(orm_template):
if val == None: r[fld] = False
alldata[r['id']] = r
del r['id']
data = self.read(cr, uid, alldata.keys(), groupby and [groupby] or ['id'], context=context)
if groupby and fget[groupby]['type'] == 'many2one':
data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=groupby, context=context)
data_read = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context)
# restore order of the search as read() uses the default _order:
data = [data_read[id] for id in data_ids]
else:
data = self.read(cr, uid, alldata.keys(), groupby and [groupby] or ['id'], context=context)
if groupby:
data.sort(lambda x,y:cmp(x[groupby],y[groupby]))
for d in data:
if groupby:
d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
@@ -2314,7 +2319,7 @@ class orm(orm_template):
:param tables: list of table._table names enclosed in double quotes as returned
by _where_calc()
:param where_clause: current list of WHERE clause params
:return: (table, where_clause, qualified_field) where ``table`` and ``where_clause`` are the updated
:return: (tables, where_clause, qualified_field) where ``tables`` and ``where_clause`` are the updated
versions of the parameters, and ``qualified_field`` is the qualified name of ``field``
in the form ``table.field``, to be referenced in queries.
"""
@@ -3926,7 +3931,7 @@ class orm(orm_template):
def _check_qorder(self, word):
if not regex_order.match(word):
raise except_orm(_('AccessError'), _('Bad query.'))
raise except_orm(_('AccessError'), _('Invalid "order" specified. A valid "order" specification is a comma-separated list of valid field names (optionally followed by asc/desc for the direction)'))
return True
def _apply_ir_rules(self, cr, uid, where_clause, where_clause_params, tables, mode='read', model_name=None, context=None):
@@ -3952,6 +3957,79 @@ class orm(orm_template):
return True
return False
def _generate_m2o_order_by(self, order_field, tables, where_clause):
"""
Add possibly missing JOIN and generate the ORDER BY clause for m2o fields,
either native m2o fields or function/related fields that are stored, including
intermediate JOINs for inheritance if required.
"""
if order_field not in self._columns and order_field in self._inherit_fields:
# also add missing joins for reaching the table containing the m2o field
tables, where_clause, qualified_field = self._inherits_join_calc(order_field, tables, where_clause)
order_field_column = self._inherit_fields[order_field][2]
else:
qualified_field = '"%s"."%s"' % (self._table, order_field)
order_field_column = self._columns[order_field]
assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
assert order_field_column._classic_write or getattr(order_field_column, 'store', False), "Many2one function/related fields must be stored to be used as ordering fields"
# figure out the applicable order_by for the m2o
dest_model = self.pool.get(order_field_column._obj)
m2o_order = dest_model._order
if not regex_order.match(m2o_order):
# _order is complex, can't use it here, so we default to _rec_name
m2o_order = dest_model._rec_name
else:
# extract the first field name, to be able to qualify it and add desc/asc
m2o_order = m2o_order.split(",",1)[0].strip().split(" ",1)[0]
# the perhaps missing join:
quoted_model_table = '"%s"' % dest_model._table
if quoted_model_table not in tables:
tables.append(quoted_model_table)
where_clause.append('%s = %s.id' % (qualified_field, quoted_model_table))
return ('%s.%s' % (quoted_model_table, m2o_order), tables, where_clause)
def _generate_order_by(self, order_spec, tables, where_clause):
"""
Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
:raise" except_orm in case order_spec is malformed
"""
order_by_clause = self._order
if order_spec:
order_by_elements = []
self._check_qorder(order_spec)
for order_part in order_spec.split(','):
order_split = order_part.strip().split(' ')
order_field = order_split[0].strip()
order_direction = order_split[1].strip() if len(order_split) == 2 else ''
if order_field in self._columns:
order_column = self._columns[order_field]
if order_column._classic_read:
order_by_clause = '"%s"."%s"' % (self._table, order_field)
elif order_column._type == 'many2one':
order_by_clause, tables, where_clause = self._generate_m2o_order_by(order_field, tables, where_clause)
else:
continue # ignore non-readable or "non-joignable" fields
elif order_field in self._inherit_fields:
parent_obj = self.pool.get(self._inherit_fields[order_field][0])
order_column = parent_obj._columns[order_field]
if order_column._classic_read:
tables, where_clause, order_by_clause = self._inherits_join_calc(order_field, tables, where_clause)
elif order_column._type == 'many2one':
order_by_clause, tables, where_clause = self._generate_m2o_order_by(order_field, tables, where_clause)
else:
continue # ignore non-readable or "non-joignable" fields
order_by_elements.append("%s %s" % (order_by_clause, order_direction))
order_by_clause = ",".join(order_by_elements)
return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
"""
Private implementation of search() method, allowing specifying the uid to use for the access right check.
@@ -3982,35 +4060,17 @@ class orm(orm_template):
tables = list(set(tables).union(set(previous_tables)))
where = where_clause
order_by = self._order
if order:
self._check_qorder(order)
o = order.split(' ')[0]
if (o in self._columns):
# we can only do efficient sort if the fields is stored in database
if getattr(self._columns[o], '_classic_read'):
order_by = order
elif (o in self._inherit_fields):
parent_obj = self.pool.get(self._inherit_fields[o][0])
if getattr(parent_obj._columns[o], '_classic_read'):
# Allowing inherits'ed field for server side sorting, if they can be sorted by the dbms
inherited_tables, inherit_join, order_by = self._inherits_join_calc(o, tables, where_clause)
order_by = self._generate_order_by(order, tables, where_clause)
limit_str = limit and ' limit %d' % limit or ''
offset_str = offset and ' offset %d' % offset or ''
if where:
where_str = " WHERE %s" % " AND ".join(where)
else:
where_str = ""
where_str = where and (" WHERE %s" % " AND ".join(where)) or ''
if count:
cr.execute('select count(%s.id) from ' % self._table +
','.join(tables) + where_str + limit_str + offset_str, where_clause_params)
res = cr.fetchall()
return res[0][0]
cr.execute('select %s.id from ' % self._table + ','.join(tables) + where_str +' order by '+order_by+limit_str+offset_str, where_clause_params)
cr.execute('select %s.id from ' % self._table + ','.join(tables) + where_str + order_by + limit_str+offset_str, where_clause_params)
res = cr.fetchall()
return [x[0] for x in res]
+10 -3
View File
@@ -65,12 +65,19 @@ class osv_pool(netsvc.Service):
for key in self._sql_error.keys():
if key in inst[0]:
self.abortResponse(1, _('Constraint Error'), 'warning', _(self._sql_error[key]))
if inst.pgcode == errorcodes.NOT_NULL_VIOLATION:
if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION):
msg = _('The operation cannot be completed, probably due to the following:\n- deletion: you may be trying to delete a record while other records still reference it\n- creation/update: a mandatory field is not correctly set')
self.logger.debug("IntegrityError", exc_info=True)
try:
context = inst.pgerror.split('"public".')[1]
model_name = table = context.split('"')[1]
errortxt = inst.pgerror.replace('«','"').replace('»','"')
if '"public".' in errortxt:
context = errortxt.split('"public".')[1]
model_name = table = context.split('"')[1]
else:
last_quote_end = errortxt.rfind('"')
last_quote_begin = errortxt.rfind('"', 0, last_quote_end)
model_name = table = errortxt[last_quote_begin+1:last_quote_end].strip()
print "MODEL", last_quote_begin, last_quote_end, model_name
model = table.replace("_",".")
model_obj = self.get(model)
if model_obj:
+3 -1
View File
@@ -64,7 +64,7 @@ class _rml_styles(object,):
for style in node.findall('paraStyle'):
sname = style.get('name')
self.styles[sname] = self._para_style_update(style)
self.styles_obj[sname] = reportlab.lib.styles.ParagraphStyle(sname, self.default_style["Normal"], **self.styles[sname])
for variable in node.findall('initialize'):
@@ -809,6 +809,8 @@ class _rml_template(object):
if story_cnt > 0:
fis.append(platypus.PageBreak())
fis += r.render(node_story)
# Reset Page Number with new story tag
fis.append(PageReset())
story_cnt += 1
if self.localcontext:
fis.append(PageCount())
+19
View File
@@ -68,6 +68,24 @@ def _obj(pool, cr, uid, model_str, context=None):
model = pool.get(model_str)
return lambda x: model.browse(cr, uid, x, context=context)
def _fix_multiple_roots(node):
"""
Surround the children of the ``node`` element of an XML field with a
single root "data" element, to prevent having a document with multiple
roots once parsed separately.
XML nodes should have one root only, but we'd like to support
direct multiple roots in our partial documents (like inherited view architectures).
As a convention we'll surround multiple root with a container "data" element, to be
ignored later when parsing.
"""
if len(node) > 1:
data_node = etree.Element("data")
for child in node:
data_node.append(child)
node.append(data_node)
def _eval_xml(self, node, pool, cr, uid, idref, context=None):
if context is None:
context = {}
@@ -115,6 +133,7 @@ def _eval_xml(self, node, pool, cr, uid, idref, context=None):
if not id in idref:
idref[id]=self.id_get(cr, False, id)
return s % idref
_fix_multiple_roots(node)
return '<?xml version="1.0"?>\n'\
+_process("".join([etree.tostring(n, encoding='utf-8')
for n in node]),
+12 -2
View File
@@ -70,14 +70,16 @@ def is_url(node):
def is_eval(node):
return isinstance(node, yaml_tag.Eval)
def is_ref(node):
return isinstance(node, yaml_tag.Ref) \
or _is_yaml_mapping(node, yaml_tag.Ref)
def is_ir_set(node):
return _is_yaml_mapping(node, yaml_tag.IrSet)
def is_string(node):
return isinstance(node, basestring)
class TestReport(object):
def __init__(self):
@@ -374,6 +376,14 @@ class YamlInterpreter(object):
elif column._type == "many2many":
ids = [self.get_id(xml_id) for xml_id in expression]
value = [(6, 0, ids)]
elif column._type == "date" and is_string(expression):
# enforce ISO format for string date values, to be locale-agnostic during tests
time.strptime(expression, misc.DEFAULT_SERVER_DATE_FORMAT)
value = expression
elif column._type == "datetime" and is_string(expression):
# enforce ISO format for string datetime values, to be locale-agnostic during tests
time.strptime(expression, misc.DEFAULT_SERVER_DATETIME_FORMAT)
value = expression
else: # scalar field
if is_eval(expression):
value = self.process_eval(expression)