背景:同一個模組,兩組開發人員對同一個模型的form檢視進行了二開。在沒有指定外部ID的情況下,odoo是如何選擇展示展示哪個檢視呢?

上乾貨

  1. odoo在載入檢視的時候,首先呼叫的models.py中的load_views函式;
  1. @api.model
  2. def load_views(self, views, options=None):
  3. """ Returns the fields_views of given views, along with the fields of
  4. the current model, and optionally its filters for the given action.
  5. :param views: list of [view_id, view_type]
  6. :param options['toolbar']: True to include contextual actions when loading fields_views
  7. :param options['load_filters']: True to return the model's filters
  8. :param options['action_id']: id of the action to get the filters
  9. :return: dictionary with fields_views, fields and optionally filters
  10. """
  11. options = options or {}
  12. result = {}
  13. toolbar = options.get('toolbar')
  14. result['fields_views'] = {
  15. v_type: self.fields_view_get(v_id, v_type if v_type != 'list' else 'tree',
  16. toolbar=toolbar if v_type != 'search' else False)
  17. for [v_id, v_type] in views
  18. }
  19. result['fields'] = self.fields_get()
  20. if options.get('load_filters'):
  21. result['filters'] = self.env['ir.filters'].get_filters(self._name, options.get('action_id'))
  22. return result
  1. 上面的核心在fields_view_get函式,如下,擷取重要的內容
  1. @api.model
  2. def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
  3. self.check_access_rights('read')
  4. view = self.env['ir.ui.view'].sudo().browse(view_id)
  5. # Get the view arch and all other attributes describing the composition of the view
  6. result = self._fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
  7. ···
  1. 檢查許可權通過後,呼叫_fields_view_get函式,若使用者呼叫的檢視沒有指定檢視ID,那麼將呼叫預設的檢視
  1. @api.model
  2. def _fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
  3. View = self.env['ir.ui.view'].sudo()
  4. result = {
  5. 'model': self._name,
  6. 'field_parent': False,
  7. }
  8. # try to find a view_id if none provided
  9. if not view_id:
  10. # <view_type>_view_ref in context can be used to overrride the default view
  11. view_ref_key = view_type + '_view_ref'
  12. view_ref = self._context.get(view_ref_key)
  13. if view_ref:
  14. if '.' in view_ref:
  15. module, view_ref = view_ref.split('.', 1)
  16. query = "SELECT res_id FROM ir_model_data WHERE model='ir.ui.view' AND module=%s AND name=%s"
  17. self._cr.execute(query, (module, view_ref))
  18. view_ref_res = self._cr.fetchone()
  19. if view_ref_res:
  20. view_id = view_ref_res[0]
  21. else:
  22. _logger.warning('%r requires a fully-qualified external id (got: %r for model %s). '
  23. 'Please use the complete `module.view_id` form instead.', view_ref_key, view_ref,
  24. self._name)
  25. if not view_id:
  26. # otherwise try to find the lowest priority matching ir.ui.view
  27. view_id = View.default_view(self._name, view_type)
  28. if view_id:
  29. # read the view with inherited views applied
  30. root_view = View.browse(view_id).read_combined(['id', 'name', 'field_parent', 'type', 'model', 'arch'])
  31. result['arch'] = root_view['arch']
  32. result['name'] = root_view['name']
  33. result['type'] = root_view['type']
  34. result['view_id'] = root_view['id']
  35. result['field_parent'] = root_view['field_parent']
  36. result['base_model'] = root_view['model']
  37. else:
  38. # fallback on default views methods if no ir.ui.view could be found
  39. try:
  40. arch_etree = getattr(self, '_get_default_%s_view' % view_type)()
  41. result['arch'] = etree.tostring(arch_etree, encoding='unicode')
  42. result['type'] = view_type
  43. result['name'] = 'default'
  44. except AttributeError:
  45. raise UserError(_("No default view of type '%s' could be found !", view_type))
  46. return result
  1. 此處我們討論的是odoo是如何取預設檢視的,再進ir.ui.view模型的default_view函式檢視
  1. @api.model
  2. def default_view(self, model, view_type):
  3. """ Fetches the default view for the provided (model, view_type) pair:
  4. primary view with the lowest priority.
  5. :param str model:
  6. :param int view_type:
  7. :return: id of the default view of False if none found
  8. :rtype: int
  9. """
  10. domain = [('model', '=', model), ('type', '=', view_type), ('mode', '=', 'primary')]
  11. return self.search(domain, limit=1).id
  1. 是不是很驚喜,毛都沒有,看不出來如何做的選擇。彆著急,看ir.ui.view的模型吧。

有點坑啊,大家在檢視繼承的時候。權重基本上是預設的,也就是說若不考慮name的影響,那麼預設檢視將永遠是最先新增的基礎檢視。但是,這裡name的排序居然還在ID的前面,這就有的玩了嘛,起名字也是門藝術了。

  1. 好了迴歸正題,在步驟3中,拿到了預設檢視後,我們取到的檢視有可能是繼承檢視,也有可能是基礎檢視。那麼將通過read_combined函式拼接出來以基礎檢視為框架,包含所有繼承檢視內容的etree物件。

  1. def read_combined(self, fields=None):
  2. """
  3. Utility function to get a view combined with its inherited views.
  4. * Gets the top of the view tree if a sub-view is requested
  5. * Applies all inherited archs on the root view
  6. * Returns the view with all requested fields
  7. .. note:: ``arch`` is always added to the fields list even if not
  8. requested (similar to ``id``)
  9. """
  10. # introduce check_view_ids in context
  11. if 'check_view_ids' not in self._context:
  12. self = self.with_context(check_view_ids=[])
  13. check_view_ids = self._context['check_view_ids']
  14. # if view_id is not a root view, climb back to the top.
  15. root = self
  16. while root.mode != 'primary':
  17. # Add inherited views to the list of loading forced views
  18. # Otherwise, inherited views could not find elements created in their direct parents if that parent is defined in the same module
  19. check_view_ids.append(root.id)
  20. root = root.inherit_id
  21. # arch and model fields are always returned
  22. if fields:
  23. fields = list({'arch', 'model'}.union(fields))
  24. # read the view arch
  25. [view_data] = root.read(fields=fields)
  26. view_arch = etree.fromstring(view_data['arch'].encode('utf-8'))
  27. if not root.inherit_id:
  28. if self._context.get('inherit_branding'):
  29. view_arch.attrib.update({
  30. 'data-oe-model': 'ir.ui.view',
  31. 'data-oe-id': str(root.id),
  32. 'data-oe-field': 'arch',
  33. })
  34. arch_tree = view_arch
  35. else:
  36. if self._context.get('inherit_branding'):
  37. root.inherit_branding(view_arch)
  38. parent_view = root.inherit_id.read_combined(fields=fields)
  39. arch_tree = etree.fromstring(parent_view['arch'])
  40. arch_tree = self.browse(parent_view['id']).apply_inheritance_specs(arch_tree, view_arch)
  41. # and apply inheritance
  42. arch = root.apply_view_inheritance(arch_tree, self.model)
  43. return dict(view_data, arch=etree.tostring(arch, encoding='unicode'))

結束