diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py index f6c34f176fb..b3a70b684e5 100644 --- a/openerp/addons/base/ir/ir_ui_view.py +++ b/openerp/addons/base/ir/ir_ui_view.py @@ -57,19 +57,6 @@ class view_custom(osv.osv): class view(osv.osv): _name = 'ir.ui.view' - class NoViewError(Exception): pass - class NoDefaultError(NoViewError): pass - - def _type_field(self, cr, uid, ids, name, args, context=None): - result = {} - for record in self.browse(cr, uid, ids, context): - # Get the type from the inherited view if any. - if record.inherit_id: - result[record.id] = record.inherit_id.type - else: - result[record.id] = etree.fromstring(record.arch.encode('utf8')).tag - return result - def _arch_get(self, cr, uid, ids, name, arg, context=None): """ For each id being read, return arch_db or the content of arch_file @@ -130,17 +117,6 @@ class view(osv.osv): # Holds the RNG schema _relaxng_validator = None - def create(self, cr, uid, values, context=None): - if 'type' not in values: - if values.get('inherit_id'): - values['type'] = self.browse(cr, uid, values['inherit_id'], context).type - else: - values['type'] = etree.fromstring(values['arch']).tag - - if not values.get('name'): - values['name'] = "%s %s" % (values['model'], values['type']) - return super(view, self).create(cr, uid, values, context) - def _relaxng(self): if not self._relaxng_validator: frng = tools.file_open(os.path.join('base','rng','view.rng')) @@ -157,7 +133,7 @@ class view(osv.osv): for view in self.browse(cr, uid, ids, context): # Sanity check: the view should not break anything upon rendering! try: - fvg = self.read_combined(cr, uid, view.id, view.type, view.model, context=context) + fvg = self.read_combined(cr, uid, view.id, None, context=context) view_arch_utf8 = fvg['arch'] except Exception, e: _logger.exception(e) @@ -179,15 +155,8 @@ class view(osv.osv): return False return True - def _check_model(self, cr, uid, ids, context=None): - for view in self.browse(cr, uid, ids, context): - if view.model and view.model not in self.pool: - return False - return True - _constraints = [ (_check_xml, 'Invalid XML for View Architecture!', ['arch']) - #(_check_model, 'The model name does not exist.', ['model']), ] def _auto_init(self, cr, context=None): @@ -196,107 +165,85 @@ class view(osv.osv): if not cr.fetchone(): cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, inherit_id)') - def read_combined(self, cr, uid, view_id, view_type, model, - fields=None, fallback=None, context=None): + def create(self, cr, uid, values, context=None): + if 'type' not in values: + if values.get('inherit_id'): + values['type'] = self.browse(cr, uid, values['inherit_id'], context).type + else: + values['type'] = etree.fromstring(values['arch']).tag + + if not values.get('name'): + values['name'] = "%s %s" % (values['model'], values['type']) + return super(view, self).create(cr, uid, values, context) + + def write(self, cr, uid, ids, vals, context=None): + if not isinstance(ids, (list, tuple)): + ids = [ids] + + # drop the corresponding view customizations (used for dashboards for example), otherwise + # not all users would see the updated views + custom_view_ids = self.pool.get('ir.ui.view.custom').search(cr, uid, [('ref_id','in',ids)]) + if custom_view_ids: + self.pool.get('ir.ui.view.custom').unlink(cr, uid, custom_view_ids) + + return super(view, self).write(cr, uid, ids, vals, context) + + # default view selection + + def default_view(self, cr, uid, model, view_type, context=None): + """ Fetches the default view for the provided (model, view_type) pair: + view with no parent (inherit_id=Fase) with the lowest priority. + + :param str model: + :param int view_type: + :return: id of the default view of False if none found + :rtype: int """ - Utility function stringing together all method calls necessary to get - a full, final view: + ids = self.search(cr, uid, [ + ['model', '=', model], + ['type', '=', view_type], + ['inherit_id', '=', False], + ], limit=1, order='priority', context=context) + if not ids: + return False + return ids[0] - * Gets the default view if no view_id is provided - * Gets the top of the view tree if a sub-view is requested - * Applies all inherited archs on the root view - * Applies post-processing - * Returns the view with all requested fields + # inheritance - .. note:: ``arch`` is always added to the fields list even if not - requested (similar to ``id``) + def get_inheriting_views_arch(self, cr, uid, view_id, context=None): + """Retrieves the architecture of views that inherit from the given view, from the sets of + views that should currently be used in the system. During the module upgrade phase it + may happen that a view is present in the database but the fields it relies on are not + fully loaded yet. This method only considers views that belong to modules whose code + is already loaded. Custom views defined directly in the database are loaded only + after the module initialization phase is completely finished. - If no view is available (no view_id or invalid view_id provided, or - no view stored for (model, view_type)) a view record will be fetched - from the ``defaults`` mapping? - - :param fallback: a mapping of {view_type: view_dict}, if no view can - be found (read) will be used to provide a default - before post-processing - :type fallback: mapping + :param int view_id: id of the view whose inheriting views should be retrieved + :param str model: model identifier of the view's related model (for double-checking) + :rtype: list of tuples + :return: [(view_arch,view_id), ...] """ - if context is None: context = {} + user_groups = frozenset(self.pool.get('res.users').browse(cr, 1, uid, context).groups_id) - def clean_anotations(arch, parent_info=None): - for child in arch: - if child.tag == 't' or child.tag == 'field': - # can not anote t and field while cleaning - continue + conditions = [['inherit_id', '=', view_id]] + if self.pool._init: + # Module init currently in progress, only consider views from + # modules whose code is already loaded + conditions.extend([ + ['model_ids.model', '=', 'ir.ui.view'], + ['model_ids.module', 'in', tuple(self.pool._init_modules)], + ]) + view_ids = self.search(cr, uid, conditions, context=context) - child_text = "".join([etree.tostring(x) for x in child]) - if child.attrib.get('data-edit-model'): - if child_text.find('data-edit-model') != -1 or child_text.find('{1}'.format(name, arch) @@ -929,6 +756,75 @@ class view(osv.osv): engine = qweb.QWebXml(loader) return engine.render(id_or_xml_id, values) + # maybe used to print the workflow ? + + def graph_get(self, cr, uid, id, model, node_obj, conn_obj, src_node, des_node, label, scale, context=None): + nodes=[] + nodes_name=[] + transitions=[] + start=[] + tres={} + labels={} + no_ancester=[] + blank_nodes = [] + + _Model_Obj = self.pool[model] + _Node_Obj = self.pool[node_obj] + _Arrow_Obj = self.pool[conn_obj] + + for model_key,model_value in _Model_Obj._columns.items(): + if model_value._type=='one2many': + if model_value._obj==node_obj: + _Node_Field=model_key + _Model_Field=model_value._fields_id + flag=False + for node_key,node_value in _Node_Obj._columns.items(): + if node_value._type=='one2many': + if node_value._obj==conn_obj: + if src_node in _Arrow_Obj._columns and flag: + _Source_Field=node_key + if des_node in _Arrow_Obj._columns and not flag: + _Destination_Field=node_key + flag = True + + datas = _Model_Obj.read(cr, uid, id, [],context) + for a in _Node_Obj.read(cr,uid,datas[_Node_Field],[]): + if a[_Source_Field] or a[_Destination_Field]: + nodes_name.append((a['id'],a['name'])) + nodes.append(a['id']) + else: + blank_nodes.append({'id': a['id'],'name':a['name']}) + + if a.has_key('flow_start') and a['flow_start']: + start.append(a['id']) + else: + if not a[_Source_Field]: + no_ancester.append(a['id']) + for t in _Arrow_Obj.read(cr,uid, a[_Destination_Field],[]): + transitions.append((a['id'], t[des_node][0])) + tres[str(t['id'])] = (a['id'],t[des_node][0]) + label_string = "" + if label: + for lbl in eval(label): + if t.has_key(tools.ustr(lbl)) and tools.ustr(t[lbl])=='False': + label_string += ' ' + else: + label_string = label_string + " " + tools.ustr(t[lbl]) + labels[str(t['id'])] = (a['id'],label_string) + g = graph(nodes, transitions, no_ancester) + g.process(start) + g.scale(*scale) + result = g.result_get() + results = {} + for node in nodes_name: + results[str(node[0])] = result[node[0]] + results[str(node[0])]['name'] = node[1] + return {'nodes': results, + 'transitions': tres, + 'label' : labels, + 'blank_nodes': blank_nodes, + 'node_parent_field': _Model_Field,} + class view_sc(osv.osv): _name = 'ir.ui.view_sc' _columns = { diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 04571cf1717..51d49950aa5 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -634,27 +634,6 @@ class MetaModel(type): if not self._custom: self.module_to_models.setdefault(self._module, []).append(self) -class FallbackViewMapping(collections.defaultdict): - def __init__(self, cr, uid, model, context=None): - super(FallbackViewMapping, self).__init__() - self.cr = cr - self.uid = uid - self.model = model - self.context = context - - def __missing__(self, view_type): - try: - arch = getattr(self.model, '_get_default_%s_view' % view_type)( - self.cr, self.uid, self.context) - except AttributeError: - raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type) - return { - 'id': 0, - 'type': view_type, - 'name': 'default', - 'field_parent': False, - 'arch': arch, - } # Definition of log access columns, automatically added to models if # self._log_access is True @@ -1796,15 +1775,12 @@ class BaseModel(object): return view - def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): + def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): """ Get the detailed composition of the requested view like fields, model, view architecture - :param cr: database cursor - :param user: current user id :param view_id: id of the view or None :param view_type: type of the view to return if view_id is None ('form', tree', ...) - :param context: context arguments, like lang, time zone :param toolbar: true to include contextual actions :param submenu: deprecated :return: dictionary describing the composition of the requested view (including inherited views and extensions) @@ -1812,69 +1788,72 @@ class BaseModel(object): * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace' * if some tag other than 'position' is found in parent view :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure - """ if context is None: context = {} View = self.pool['ir.ui.view'] - view_ref = context.get(view_type + '_view_ref') - - if view_ref and not view_id and '.' in view_ref: - module, view_ref = view_ref.split('.', 1) - cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref)) - view_ref_res = cr.fetchone() - if view_ref_res: - view_id = view_ref_res[0] - - root_view = View.read_combined( - cr, user, view_id, view_type, self._name, fields=[ - 'id', 'name', 'field_parent', 'type', 'model', 'arch' - ], fallback=FallbackViewMapping(cr, user, self, context=context), - context=context) - result = { 'model': self._name, - 'arch': root_view['arch'], - 'type': root_view['type'], - 'view_id': root_view['id'], - 'name': root_view['name'], - 'field_parent': root_view['field_parent'] or False + 'field_parent': False, } + # try to find a view_id if none provided + if not view_id: + # _view_ref in context can be used to overrride the default view + view_ref = context.get(view_type + '_view_ref') + if view_ref and '.' in view_ref: + module, view_ref = view_ref.split('.', 1) + cr.execute("SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s", (module, view_ref)) + view_ref_res = cr.fetchone() + if view_ref_res: + view_id = view_ref_res[0] + else: + # otherwise try to find the lowest priority matching ir.ui.view + view_id = View.default_view(cr, uid, self._name, view_type, context=context) + + if view_id: + # read the view with inherited views applied + root_view = View.read_combined(cr, uid, view_id, fields=['id', 'name', 'field_parent', 'type', 'model', 'arch'], context=context) + result['arch'] = root_view['arch'] + result['name'] = root_view['name'] + result['type'] = root_view['type'] + result['view_id'] = root_view['id'] + result['field_parent'] = root_view['field_parent'] + else: + # fallback on default views methods if no ir.ui.view could be found + try: + get_func = getattr(self, '_get_default_%s_view' % view_type) + arch_etree = get_func(self.cr, self.uid, self.context) + result['arch'] = etree.tostring(arch_etree, encoding='utf-8') + result['type'] = view_type + result['name'] = 'default' + except AttributeError: + raise except_orm(_('Invalid Architecture!'), _("No default view of type '%s' could be found !") % view_type) + + # Apply post processing, groups and modifiers etc... ctx = context if root_view.get('model') != self._name: ctx = dict(context, base_model_name=root_view.get('model')) - xarch, xfields = View._view__view_look_dom_arch( - cr, user, self._name, etree.fromstring(result['arch']), - result['view_id'], context=ctx) + xarch, xfields = View.postprocess_and_fields( cr, uid, self._name, etree.fromstring(result['arch']), result['view_id'], context=ctx) result['arch'] = xarch result['fields'] = xfields + # Add related action information if aksed if toolbar: + toclean = ('report_sxw_content', 'report_rml_content', 'report_sxw', 'report_rml', 'report_sxw_content_data', 'report_rml_content_data') def clean(x): x = x[2] - for key in ('report_sxw_content', 'report_rml_content', - 'report_sxw', 'report_rml', - 'report_sxw_content_data', 'report_rml_content_data'): + for key in toclean: if key in x: del x[key] return x ir_values_obj = self.pool.get('ir.values') - resprint = ir_values_obj.get(cr, user, 'action', - 'client_print_multi', [(self._name, False)], False, - context) - resaction = ir_values_obj.get(cr, user, 'action', - 'client_action_multi', [(self._name, False)], False, - context) - - 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[2].get('multi')] - resprint = [clean(print_) for print_ in resprint - if view_type == 'tree' or not print_[2].get('multi')] + resprint = ir_values_obj.get(cr, uid, 'action', 'client_print_multi', [(self._name, False)], False, context) + resaction = ir_values_obj.get(cr, uid, 'action', 'client_action_multi', [(self._name, False)], False, context) + resrelate = ir_values_obj.get(cr, uid, 'action', 'client_action_relate', [(self._name, False)], False, context) + 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')] #When multi="True" set it will display only in More of the list view resrelate = [clean(action) for action in resrelate if (action[2].get('multi') and view_type == 'tree') or (not action[2].get('multi') and view_type == 'form')] @@ -1890,7 +1869,7 @@ class BaseModel(object): return result def _view_look_dom_arch(self, cr, uid, node, view_id, context=None): - return self['ir.ui.view']._view__view_look_dom_arch( + return self['ir.ui.view'].postprocess_and_fields( cr, uid, self._name, node, view_id, context=context) def search_count(self, cr, user, args, context=None): @@ -3045,31 +3024,6 @@ class BaseModel(object): self._columns[field_name].required = True self._columns[field_name].ondelete = "cascade" - #def __getattr__(self, name): - # """ - # Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent - # (though inherits doesn't use Python inheritance). - # Handles translating between local ids and remote ids. - # Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance - # when you have inherits. - # """ - # for model, field in self._inherits.iteritems(): - # proxy = self.pool.get(model) - # if hasattr(proxy, name): - # attribute = getattr(proxy, name) - # if not hasattr(attribute, '__call__'): - # return attribute - # break - # else: - # return super(orm, self).__getattr__(name) - - # def _proxy(cr, uid, ids, *args, **kwargs): - # objects = self.browse(cr, uid, ids, kwargs.get('context', None)) - # lst = [obj[field].id for obj in objects if obj[field]] - # return getattr(proxy, name)(cr, uid, lst, *args, **kwargs) - - # return _proxy - def fields_get(self, cr, user, allfields=None, context=None, write_access=True): """ Return the definition of each field. @@ -4911,6 +4865,32 @@ class BaseModel(object): """ stuff to do right after the registry is built """ pass + + #def __getattr__(self, name): + # """ + # Proxies attribute accesses to the `inherits` parent so we can call methods defined on the inherited parent + # (though inherits doesn't use Python inheritance). + # Handles translating between local ids and remote ids. + # Known issue: doesn't work correctly when using python's own super(), don't involve inherit-based inheritance + # when you have inherits. + # """ + # for model, field in self._inherits.iteritems(): + # proxy = self.pool.get(model) + # if hasattr(proxy, name): + # attribute = getattr(proxy, name) + # if not hasattr(attribute, '__call__'): + # return attribute + # break + # else: + # return super(orm, self).__getattr__(name) + + # def _proxy(cr, uid, ids, *args, **kwargs): + # objects = self.browse(cr, uid, ids, kwargs.get('context', None)) + # lst = [obj[field].id for obj in objects if obj[field]] + # return getattr(proxy, name)(cr, uid, lst, *args, **kwargs) + + # return _proxy + def __getattr__(self, name): if name.startswith('signal_'): signal_name = name[len('signal_'):] @@ -4976,12 +4956,12 @@ def itemgetter_tuple(items): if len(items) == 1: return lambda gettable: (gettable[items[0]],) return operator.itemgetter(*items) + class ImportWarning(Warning): """ Used to send warnings upwards the stack during the import process """ pass - def convert_pgerror_23502(model, fields, info, e): m = re.match(r'^null value in column "(?P\w+)" violates ' r'not-null constraint\n', @@ -4997,6 +4977,7 @@ def convert_pgerror_23502(model, fields, info, e): 'message': message, 'field': field_name, } + def convert_pgerror_23505(model, fields, info, e): m = re.match(r'^duplicate key (?P\w+) violates unique constraint', str(e))