From 8a9c0dbad7323509b07cda605aed84bf51584753 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 13:12:13 +0200 Subject: [PATCH 01/12] [IMP] bulk-update fields_view_get result via dict.update instead of a bunch of __setitem__ bzr revid: xmo@openerp.com-20110915111213-zigij077k9ss9p6e --- openerp/osv/orm.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 7efe2272754..315d2bce0de 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1954,14 +1954,13 @@ class orm_template(object): # if a view was found if sql_res: - result['type'] = sql_res['type'] - result['view_id'] = sql_res['id'] - source = etree.fromstring(encode(sql_res['arch'])) - result['arch'] = apply_view_inheritance(source, result['view_id']) - - result['name'] = sql_res['name'] - result['field_parent'] = sql_res['field_parent'] or False + result.update( + arch=apply_view_inheritance(source, sql_res['id']), + type=sql_res['type'], + view_id=sql_res['id'], + name=sql_res['name'], + field_parent=sql_res['field_parent'] or False) else: # otherwise, build some kind of default view @@ -1994,10 +1993,11 @@ class orm_template(object): else: # what happens here, graph case? raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type) - result['arch'] = etree.fromstring(encode(xml)) - result['name'] = 'default' - result['field_parent'] = False - result['view_id'] = 0 + result.update( + arch=etree.fromstring(encode(xml)), + name='default', + field_parent=False, + view_id=0) if parent_view_model != self._name: ctx = context.copy() From 734c1a43a0e4a208185ab967cc1bf087a2d15e5d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 13:12:21 +0200 Subject: [PATCH 02/12] [REF] extract the two inlined default view generators left into fields_view_get into their own methods follow the preexisting __get_default_$name_view bzr revid: xmo@openerp.com-20110915111221-20ktl1bi2qne5wz7 --- openerp/osv/orm.py | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 315d2bce0de..960217b4de8 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1689,6 +1689,28 @@ class orm_template(object): raise except_orm('View error', msg) return arch, fields + def __get_default_form_view(self, cr, user, context=None): + # TODO it seems fields_get can be replaced by _all_columns (no need for translation) + res = self.fields_get(cr, user, context=context) + xml = ' ' \ + '
' % (self._description,) + for x in res: + if res[x]['type'] not in ('one2many', 'many2many'): + xml += '' % (x,) + if res[x]['type'] == 'text': + xml += "" + xml += "" + return xml + + def __get_default_tree_view(self, cr, user, context=None): + _rec_name = self._rec_name + if _rec_name not in self._columns: + _rec_name = self._columns.keys()[0] + xml = '' \ + '' \ + % (self._description, _rec_name) + return xml + def __get_default_calendar_view(self): """Generate a default calendar view (For internal use only). """ @@ -1965,24 +1987,10 @@ class orm_template(object): # otherwise, build some kind of default view if view_type == 'form': - # TODO it seems fields_get can be replaced by _all_columns (no need for translation) - res = self.fields_get(cr, user, context=context) - xml = ' ' \ - '
' % (self._description,) - for x in res: - if res[x]['type'] not in ('one2many', 'many2many'): - xml += '' % (x,) - if res[x]['type'] == 'text': - xml += "" - xml += "" + xml = self.__get_default_form_view(cr, user, context) elif view_type == 'tree': - _rec_name = self._rec_name - if _rec_name not in self._columns: - _rec_name = self._columns.keys()[0] - xml = '' \ - '' \ - % (self._description, _rec_name) + xml = self.__get_default_tree_view(cr, user, context) elif view_type == 'calendar': xml = self.__get_default_calendar_view() From ebb2072207c23fe1ad12310207a72ac27e2a754e Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 13:12:55 +0200 Subject: [PATCH 03/12] [IMP] regroup all __get_default_$name_view calls into a single parametric getattr Also rename __get_default_ to _get_default_ so it can be found by getattr bzr revid: xmo@openerp.com-20110915111255-1i0y555er3nwtkzg --- openerp/osv/orm.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 960217b4de8..773feb5808d 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1689,7 +1689,7 @@ class orm_template(object): raise except_orm('View error', msg) return arch, fields - def __get_default_form_view(self, cr, user, context=None): + def _get_default_form_view(self, cr, user, context=None): # TODO it seems fields_get can be replaced by _all_columns (no need for translation) res = self.fields_get(cr, user, context=context) xml = ' ' \ @@ -1702,7 +1702,7 @@ class orm_template(object): xml += "" return xml - def __get_default_tree_view(self, cr, user, context=None): + def _get_default_tree_view(self, cr, user, context=None): _rec_name = self._rec_name if _rec_name not in self._columns: _rec_name = self._columns.keys()[0] @@ -1711,7 +1711,7 @@ class orm_template(object): % (self._description, _rec_name) return xml - def __get_default_calendar_view(self): + def _get_default_calendar_view(self, cr, user, context=None): """Generate a default calendar view (For internal use only). """ # TODO could return an etree instead of a string @@ -1758,7 +1758,7 @@ class orm_template(object): return arch - def __get_default_search_view(self, cr, uid, context=None): + def _get_default_search_view(self, cr, uid, context=None): form_view = self.fields_view_get(cr, uid, False, 'form', context=context) tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context) @@ -1984,23 +1984,14 @@ class orm_template(object): name=sql_res['name'], field_parent=sql_res['field_parent'] or False) else: - # otherwise, build some kind of default view - if view_type == 'form': - xml = self.__get_default_form_view(cr, user, context) - - elif view_type == 'tree': - xml = self.__get_default_tree_view(cr, user, context) - - elif view_type == 'calendar': - xml = self.__get_default_calendar_view() - - elif view_type == 'search': - xml = self.__get_default_search_view(cr, user, context) - - else: + try: + xml = getattr(self, '_get_default_%s_view' % view_type)( + cr, user, context) + except AttributeError: # what happens here, graph case? raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type) + result.update( arch=etree.fromstring(encode(xml)), name='default', From 005e2b87a57bba82cec4b73b26ce8af6392ffc4d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:04:29 +0200 Subject: [PATCH 04/12] [IMP] etree-ify default view generations * Search already used etree internally, remove serialization to string * form and tree were easy to convert * pull down fvg's parsing (etree.fromstring) into calendar generator before converting it bzr revid: xmo@openerp.com-20110915120429-syz190w61iq52rel --- openerp/osv/orm.py | 70 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 773feb5808d..c60c440af24 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1690,32 +1690,53 @@ class orm_template(object): return arch, fields def _get_default_form_view(self, cr, user, context=None): + """ Generates a default single-line form view using all fields + of the current model except the m2m and o2m ones. + + :param cr: database cursor + :param int user: user id + :param dict context: connection context + :returns: a form view as an lxml document + :rtype: etree._Element + """ + view = etree.Element('form', string=self._description) # TODO it seems fields_get can be replaced by _all_columns (no need for translation) - res = self.fields_get(cr, user, context=context) - xml = ' ' \ - '
' % (self._description,) - for x in res: - if res[x]['type'] not in ('one2many', 'many2many'): - xml += '' % (x,) - if res[x]['type'] == 'text': - xml += "" - xml += "" - return xml + for field, descriptor in self.fields_get(cr, user, context=context).iteritems(): + if descriptor['type'] in ('one2many', 'many2many'): + continue + etree.SubElement(view, 'field', name=field) + if descriptor['type'] == 'text': + etree.SubElement(view, 'newline') + return view def _get_default_tree_view(self, cr, user, context=None): + """ Generates a single-field tree view, using _rec_name if + it's one of the columns or the first column it finds otherwise + + :param cr: database cursor + :param int user: user id + :param dict context: connection context + :returns: a tree view as an lxml document + :rtype: etree._Element + """ _rec_name = self._rec_name if _rec_name not in self._columns: _rec_name = self._columns.keys()[0] - xml = '' \ - '' \ - % (self._description, _rec_name) - return xml + + view = etree.Element('tree', string=self._description) + etree.SubElement(view, 'field', name=_rec_name) + return view def _get_default_calendar_view(self, cr, user, context=None): - """Generate a default calendar view (For internal use only). + """ Generates a default calendar view by trying to infer + calendar fields from a number of pre-set attribute names + + :param cr: database cursor + :param int user: user id + :param dict context: connection context + :returns: a calendar view + :rtype: etree._Element """ - # TODO could return an etree instead of a string - arch = ('\n' '\n' '') % (self._rec_name) - return arch + return etree.fromstring(arch.encode('utf-8')) def _get_default_search_view(self, cr, uid, context=None): + """ + :param cr: database cursor + :param int user: user id + :param dict context: connection context + :returns: an lxml document of the view + :rtype: etree._Element + """ form_view = self.fields_view_get(cr, uid, False, 'form', context=context) tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context) @@ -1783,7 +1811,7 @@ class orm_template(object): field_group.append(etree.Element("field", attrib={'name': field_name})) #TODO tostring can be removed as fromstring is call directly after... - return etree.tostring(search_view, encoding="utf-8").replace('\t', '') + return search_view # # if view_id, view_type is not required @@ -1986,14 +2014,14 @@ class orm_template(object): else: # otherwise, build some kind of default view try: - xml = getattr(self, '_get_default_%s_view' % view_type)( + view = getattr(self, '_get_default_%s_view' % view_type)( cr, user, context) except AttributeError: # what happens here, graph case? raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type) result.update( - arch=etree.fromstring(encode(xml)), + arch=view, name='default', field_parent=False, view_id=0) From 4cdda26b6c959d2c6c7323dd9e173f352e83c788 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:11:29 +0200 Subject: [PATCH 05/12] [IMP] convert default calendar view generation (from fields_view_get) to lxml.etree bzr revid: xmo@openerp.com-20110915121129-fcdq8e0rix2ocpwt --- openerp/osv/orm.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index c60c440af24..e8556dbc9d4 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1737,8 +1737,8 @@ class orm_template(object): :returns: a calendar view :rtype: etree._Element """ - arch = ('\n' - '\n' - ' \n' - '') % (self._rec_name) - - return etree.fromstring(arch.encode('utf-8')) + return view def _get_default_search_view(self, cr, uid, context=None): """ From 74e98f422043bec3b2b10067b1e5665eeb9f9aff Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:15:44 +0200 Subject: [PATCH 06/12] [ADD] error when neither date_stop nor date_delay can be generated for a default calendar view bzr revid: xmo@openerp.com-20110915121544-eq3mumulb0qp9yfa --- openerp/osv/orm.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index e8556dbc9d4..1d9fb9ccfa0 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1761,12 +1761,15 @@ class orm_template(object): if dt_stop in self._columns: view.set('date_stop', dt_stop) break - - if not view.get('date_stop'): + if 'date_stop' not in view.attrib: for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]: if dt_delay in self._columns: view.set('date_delay', dt_delay) break + if 'date_delay' not in view.attrib: + raise except_orm( + _('Invalid Object Architecture!'), + _("Insufficient fields to generate a Calendar View for %s, missing a date_stop or a date_delay" % (self._name))) return view From e878560c7450f4f3bbf7b43d93aa04214218a07a Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:24:43 +0200 Subject: [PATCH 07/12] [ADD] small helper to calendar generation code, in order to make flow clearer bzr revid: xmo@openerp.com-20110915122443-hvk1u2372ppd77m1 --- openerp/osv/orm.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 1d9fb9ccfa0..c0ca7e9d696 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1737,6 +1737,19 @@ class orm_template(object): :returns: a calendar view :rtype: etree._Element """ + def set_first_of(seq, in_, to): + """Sets the first value of ``set`` also found in ``in`` to + the ``to`` attribute of the view being closed over. + + Returns whether it's found a suitable value (and set it on + the attribute) or not + """ + for item in seq: + if item in in_: + view.set(key, item) + return True + return False + view = etree.Element('calendar', string=self._description) etree.SubElement(view, 'field', name=self._rec_name) @@ -1752,21 +1765,13 @@ class orm_template(object): raise except_orm(_('Invalid Object Architecture!'), _("Insufficient fields for Calendar View!")) view.set('date_start', self._date_name) - for color in ["user_id", "partner_id", "x_user_id", "x_partner_id"]: - if color in self._columns: - view.set('color', color) - break + set_first_of(["user_id", "partner_id", "x_user_id", "x_partner_id"], + self._columns, 'color') - for dt_stop in ["date_stop", "date_end", "x_date_stop", "x_date_end"]: - if dt_stop in self._columns: - view.set('date_stop', dt_stop) - break - if 'date_stop' not in view.attrib: - for dt_delay in ["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"]: - if dt_delay in self._columns: - view.set('date_delay', dt_delay) - break - if 'date_delay' not in view.attrib: + if not set_first_of(["date_stop", "date_end", "x_date_stop", "x_date_end"], + self._columns, 'date_stop'): + if not set_first_of(["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"], + self._columns, 'date_delay'): raise except_orm( _('Invalid Object Architecture!'), _("Insufficient fields to generate a Calendar View for %s, missing a date_stop or a date_delay" % (self._name))) From eeb31efa882408be3bbdb98afb928bd8a297edb7 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:28:34 +0200 Subject: [PATCH 08/12] [IMP] automatic search view generation: comprehensions are awesome bzr revid: xmo@openerp.com-20110915122834-0ec4dkaznbhtc0kc --- openerp/osv/orm.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index c0ca7e9d696..48b607273de 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1789,12 +1789,11 @@ class orm_template(object): form_view = self.fields_view_get(cr, uid, False, 'form', context=context) tree_view = self.fields_view_get(cr, uid, False, 'tree', context=context) - fields_to_search = set() # TODO it seems _all_columns could be used instead of fields_get (no need for translated fields info) fields = self.fields_get(cr, uid, context=context) - for field in fields: - if fields[field].get('select'): - fields_to_search.add(field) + fields_to_search = set( + field for field, descriptor in fields.iteritems() + if descriptor.get('select')) for view in (form_view, tree_view): view_root = etree.fromstring(view['arch']) # Only care about select=1 in xpath below, because select=2 is covered From 7bb856b834b6ba30f406459d0b36b34dcd351973 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:31:55 +0200 Subject: [PATCH 09/12] [IMP] search view generation: update fields set in place instead of creating a new set via union bzr revid: xmo@openerp.com-20110915123155-a5ht8nhfp9kq0kst --- openerp/osv/orm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 48b607273de..625247d464f 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1794,11 +1794,12 @@ class orm_template(object): fields_to_search = set( field for field, descriptor in fields.iteritems() if descriptor.get('select')) + for view in (form_view, tree_view): view_root = etree.fromstring(view['arch']) # Only care about select=1 in xpath below, because select=2 is covered # by the custom advanced search in clients - fields_to_search = fields_to_search.union(view_root.xpath("//field[@select=1]/@name")) + fields_to_search.update(view_root.xpath("//field[@select=1]/@name")) tree_view_root = view_root # as provided by loop above search_view = etree.Element("search", attrib={'string': tree_view_root.get("string", "")}) From a09be0e9b94626d45db20272ba9ecab469aa8c66 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:37:07 +0200 Subject: [PATCH 10/12] [IMP] simplify search view generation: rely on etree.SubElement to graft new nodes to the search view bzr revid: xmo@openerp.com-20110915123707-901mkfkz8e6ikre4 --- openerp/osv/orm.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 625247d464f..8070d56d9e4 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1802,14 +1802,12 @@ class orm_template(object): fields_to_search.update(view_root.xpath("//field[@select=1]/@name")) tree_view_root = view_root # as provided by loop above - search_view = etree.Element("search", attrib={'string': tree_view_root.get("string", "")}) - field_group = etree.Element("group") - search_view.append(field_group) + search_view = etree.Element("search", string=tree_view_root.get("string", "")) + field_group = etree.SubElement(search_view, "group") for field_name in fields_to_search: - field_group.append(etree.Element("field", attrib={'name': field_name})) + etree.SubElement(field_group, "field", name=field_name)) - #TODO tostring can be removed as fromstring is call directly after... return search_view # From 662c8842f4eef498feb6711c87a3fdd2c75a3611 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 15 Sep 2011 14:47:41 +0200 Subject: [PATCH 11/12] [IMP] fields_view_get: listcomps are still awesome also use itertools.chain to iterate over multiple lists in sequence, instead of concatenating them bzr revid: xmo@openerp.com-20110915124741-v5uneqmt2c3v6cmp --- openerp/osv/orm.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 8070d56d9e4..c08fe1e2357 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -44,6 +44,7 @@ import calendar import copy import datetime +import itertools import logging import warnings import operator @@ -2052,13 +2053,11 @@ class orm_template(object): resrelate = ir_values_obj.get(cr, user, 'action', 'client_action_relate', [(self._name, False)], False, context) - resprint = map(clean, resprint) - resaction = map(clean, resaction) - resaction = filter(lambda x: not x.get('multi', False), resaction) - resprint = filter(lambda x: not x.get('multi', False), resprint) + resaction = [clean(action) for action in resaction if not action.get('multi')] + resprint = [clean(print_) for print_ in resprint if not print_.get('multi')] resrelate = map(lambda x: x[2], resrelate) - for x in resprint + resaction + resrelate: + for x in itertools.chain(resprint, resaction, resrelate): x['string'] = x['name'] result['toolbar'] = { From 3cee85eaba9ce9fe8706b14482324a765e817c74 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 29 Sep 2011 15:28:22 +0200 Subject: [PATCH 12/12] [FIX] typo syntax error bzr revid: xmo@openerp.com-20110929132822-3qbbl1qttnvfhl40 --- openerp/osv/orm.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 4740e8e37e5..3426dfd99d5 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1848,7 +1848,7 @@ class BaseModel(object): field_group = etree.SubElement(search_view, "group") for field_name in fields_to_search: - etree.SubElement(field_group, "field", name=field_name)) + etree.SubElement(field_group, "field", name=field_name) return search_view @@ -2092,8 +2092,10 @@ class BaseModel(object): resrelate = ir_values_obj.get(cr, user, 'action', 'client_action_relate', [(self._name, False)], False, context) - resaction = [clean(action) for action in resaction if view_type == 'tree' or not action.get('multi')] - resprint = [clean(print_) for print_ in resprint if view_type == 'tree' or not print_.get('multi')] + resaction = [clean(action) for action in resaction + if view_type == 'tree' or not action[2].get('multi')] + resprint = [clean(print_) for print_ in resprint + if view_type == 'tree' or not print_[2].get('multi')] resrelate = map(lambda x: x[2], resrelate) for x in itertools.chain(resprint, resaction, resrelate):