Skip to content
Permalink
Browse files

ir_ui_view: code imp

  • Loading branch information...
rco-odoo committed Nov 7, 2019
1 parent e1bfb22 commit 4d5a8bb6b88e1b420ccb32f4b78c85041465898a
Showing with 92 additions and 53 deletions.
  1. +92 −53 odoo/addons/base/models/ir_ui_view.py
@@ -838,17 +838,16 @@ def _postprocess_access_rights(self, model, node):
not self._context.get(action, True) and is_base_model):
node.set(action, 'false')

if node.tag in ('kanban',):
if node.tag == 'kanban':
group_by_name = node.get('default_group_by')
if group_by_name in Model._fields:
group_by_field = Model._fields[group_by_name]
if group_by_field.type == 'many2one':
group_by_model = Model.env[group_by_field.comodel_name]
for action, operation in (('group_create', 'create'), ('group_delete', 'unlink'), ('group_edit', 'write')):
if (not node.get(action) and
not group_by_model.check_access_rights(operation, raise_exception=False) or
not self._context.get(action, True) and is_base_model):
node.set(action, 'false')
group_by_field = Model._fields.get(group_by_name)
if group_by_field and group_by_field.type == 'many2one':
group_by_model = Model.env[group_by_field.comodel_name]
for action, operation in (('group_create', 'create'), ('group_delete', 'unlink'), ('group_edit', 'write')):
if (not node.get(action) and
not group_by_model.check_access_rights(operation, raise_exception=False) or
not self._context.get(action, True) and is_base_model):
node.set(action, 'false')

def postprocess(self, node, current_node_path, editable, name_manager):
""" Process the given arch node, which may be the complete arch or some
@@ -916,16 +915,21 @@ def _postprocess_tag_field(self, node, name_manager, node_info):
or get_dict_asts(node.get('attrs') or "{}")
)
if name_manager.validate:
name_manager.must_have_fields(self._get_field_domain_variables(node, field, node_info['editable']))
name_manager.must_have_fields(
self._get_field_domain_variables(node, field, node_info['editable'])
)
views = {}
for child in node:
if child.tag in ('form', 'tree', 'graph', 'kanban', 'calendar'):
node.remove(child)
xarch, sub_name_manager = self.with_context(
base_model_name=name_manager.Model._name,
)._postprocess_view(child, field.comodel_name, name_manager.validate, editable=node_info['editable'])
)._postprocess_view(
child, field.comodel_name, name_manager.validate,
editable=node_info['editable'],
)
name_manager.must_have_fields(sub_name_manager.mandatory_parent_fields)
views[str(child.tag)] = {
views[child.tag] = {
'arch': xarch,
'fields': sub_name_manager.available_fields,
}
@@ -934,8 +938,10 @@ def _postprocess_tag_field(self, node, name_manager, node_info):
Comodel = self.env[field.comodel_name]
node_info['attr_model'] = Comodel
if field.type in ('many2one', 'many2many'):
node.set('can_create', 'true' if Comodel.check_access_rights('create', raise_exception=False) else 'false')
node.set('can_write', 'true' if Comodel.check_access_rights('write', raise_exception=False) else 'false')
can_create = Comodel.check_access_rights('create', raise_exception=False)
can_write = Comodel.check_access_rights('write', raise_exception=False)
node.set('can_create', 'true' if can_create else 'false')
node.set('can_write', 'true' if can_write else 'false')

name_manager.has_field(node.get('name'), attrs)
field = name_manager.fields_get.get(node.get('name'))
@@ -980,9 +986,11 @@ def _postprocess_tag_label(self, node, name_manager, node_info):
def _postprocess_tag_search(self, node, name_manager, node_info):
searchpanel = [child for child in node if child.tag == 'searchpanel']
if searchpanel:
self.with_context( # todo check me, doesnt get result, is it usefull if validate is False
self.with_context(
base_model_name=name_manager.Model._name,
)._postprocess_view(searchpanel[0], name_manager.Model._name, name_manager.validate, editable=False)
)._postprocess_view(
searchpanel[0], name_manager.Model._name, name_manager.validate, editable=False,
)
node_info['children'] = [child for child in node if child.tag != 'searchpanel']
node_info['editable'] = False

@@ -1001,19 +1009,19 @@ def _validate_tag_field(self, node, name_manager, node_info):
if not field and name in name_manager.fields_get:
return
if not field:
msg = _('Field "%s" does not exist in model "%s"') % (node.get('name'), name_manager.Model._name)
self.handle_view_error(msg)
msg = _('Field "%s" does not exist in model "%s"')
self.handle_view_error(msg % (node.get('name'), name_manager.Model._name))
if node.get('domain') and field.comodel_name not in self.env:
msg = _('Domain on field without comodel makes no sence for %s (domain:%s)') % (node.get('name'), node.get('domain'))
self.handle_view_error(msg)
msg = _('Domain on field without comodel makes no sense for %s (domain:%s)')
self.handle_view_error(msg % (node.get('name'), node.get('domain')))

for attribute in ('invisible', 'readonly', 'required'):
val = node.get(attribute)
if val:
res = safe_eval(val, {'context': self._context})
if res not in (1, 0, True, False, None):
msg = _('Attribute %s evalution must give a boolean, got %s in %s') % (attribute, res, val)
self.handle_view_error(msg)
msg = _('Attribute %s evalution must give a boolean, got %s in %s')
self.handle_view_error(msg % (attribute, res, val))

def _validate_tag_button(self, node, name_manager, node_info):
name = node.get('name')
@@ -1030,15 +1038,18 @@ def _validate_tag_button(self, node, name_manager, node_info):
elif type_ == 'object':
func = getattr(type(name_manager.Model), name, None)
if not func:
self.handle_view_error(_("%s is not a valid action on %s") % (name, name_manager.Model._name))
msg = _("%s is not a valid action on %s")
self.handle_view_error(msg % (name, name_manager.Model._name))
try:
check_method_name(name)
except AccessError:
self.handle_view_error(_("%s on %s is private and cannot be called from a button") % (name, name_manager.Model._name))
msg = _("%s on %s is private and cannot be called from a button")
self.handle_view_error(msg % (name, name_manager.Model._name))
try:
inspect.signature(func).bind(self=name_manager.Model)
except TypeError:
self.handle_view_error(_("%s on %s has parameters and cannot be called from a button") % (name, name_manager.Model._name), raise_exception=False)
msg = _("%s on %s has parameters and cannot be called from a button")
self.handle_view_error(msg % (name, name_manager.Model._name), raise_exception=False)
elif type_ == 'action':
# logic mimics /web/action/load behaviour
action = False
@@ -1047,19 +1058,23 @@ def _validate_tag_button(self, node, name_manager, node_info):
except ValueError:
action_id = self.env['ir.model.data'].xmlid_to_res_id(name, raise_if_not_found=False)
if not action_id:
self.handle_view_error(_("Invalid xmlid %s for button of type action.") % name)
msg = _("Invalid xmlid %s for button of type action.")
self.handle_view_error(msg % name)
action = self.env['ir.actions.actions'].browse(action_id).exists()
if not action:
self.handle_view_error(_("Action %s (id: %s) does not exist for button of type action.") % (name, action_id))
msg = _("Action %s (id: %s) does not exist for button of type action.")
self.handle_view_error(msg % (name, action_id))

name_manager.has_action(name)
elif node.get('icon'):
self._validate_fa_class_accessibility(node, 'A button with icon attribute (%s)' % node.get('icon'))
description = 'A button with icon attribute (%s)' % node.get('icon')
self._validate_fa_class_accessibility(node, description)

def _validate_tag_graph(self, node, name_manager, node_info):
for child in node_children(node):
if child.tag != 'field' and not isinstance(child, etree._Comment):
self.handle_view_error(_('A <graph> can only contains <field> nodes, found a <%s>') % child.tag)
msg = _('A <graph> can only contains <field> nodes, found a <%s>')
self.handle_view_error(msg % child.tag)

def _validate_tag_groupby(self, node, name_manager, node_info):
# groupby nodes should be considered as nested view because they may
@@ -1069,17 +1084,21 @@ def _validate_tag_groupby(self, node, name_manager, node_info):
field = name_manager.Model._fields.get(name)
if field:
if field.type != 'many2one':
self.handle_view_error(_("'groupby' tags can only target many2one (%s)") % field.name)
name_manager.must_have_fields(self._get_field_domain_variables(node, field, node_info['editable']))
msg = _("'groupby' tags can only target many2one (%s)")
self.handle_view_error(msg % field.name)
name_manager.must_have_fields(
self._get_field_domain_variables(node, field, node_info['editable'])
)
else:
msg = _('field %s used in groupby node does not exist in model %s') % (name, name_manager.Model._name)
self.handle_view_error(msg)
msg = _('field %s used in groupby node does not exist in model %s')
self.handle_view_error(msg % (name, name_manager.Model._name))

def _validate_tag_tree(self, node, name_manager, node_info):
allowed_tags = ('field', 'button', 'control', 'groupby', 'widget')
for child in node_children(node):
if not child.tag in allowed_tags and not isinstance(child, etree._Comment):
self.handle_view_error(_('Tree child can only be have one of %s tag (not %s)' % (', '.join(allowed_tags), child.tag)))
if child.tag not in allowed_tags and not isinstance(child, etree._Comment):
msg = _('Tree child can only be have one of %s tag (not %s)')
self.handle_view_error(msg % (', '.join(allowed_tags), child.tag))

def _validate_tag_search(self, node, name_manager, node_info):
if len([c for c in node if c.tag == 'searchpanel']) > 1:
@@ -1088,13 +1107,16 @@ def _validate_tag_search(self, node, name_manager, node_info):
def _validate_tag_searchpanel(self, node, name_manager, node_info):
for child in node_children(node):
if child.get('domain') and child.get('select') != 'multi':
self.handle_view_error(_('Searchpanel item with select multi cannot have a domain.'))
msg = _('Searchpanel item with select multi cannot have a domain.')
self.handle_view_error(msg)

def _validate_tag_label(self, node, name_manager, node_info):
# replace return not arch.xpath('//label[not(@for) and not(descendant::input)]')
for_ = node.get('for')
if not for_:
self.handle_view_error(_('Label tag must contain a for. To match label style without corresponding field or button, use \'class="o_form_label"\''))
msg = _('Label tag must contain a "for". To match label style '
'without corresponding field or button, use \'class="o_form_label"\'.')
self.handle_view_error(msg)
else:
name_manager.must_have_name(for_, 'label for') # this could be done in check_attr

@@ -1104,16 +1126,18 @@ def _validate_tag_page(self, node, name_manager, node_info):

def _validate_tag_img(self, node, name_manager, node_info):
if not any(node.get(alt) for alt in self._att_list('alt')):
src = next((node.get(src) for src in self._att_list('src') if node.get(src)), None) or ''
src = next((node.get(src) for src in self._att_list('src') if node.get(src)), "")
if src:
src = ' with src (%s)'
self.handle_view_error(_('<img> tag%s must contain an alt attribute') % src, raise_exception=False)
msg = _('<img> tag%s must contain an alt attribute')
self.handle_view_error(msg % src, raise_exception=False)

def _validate_tag_a(self, node, name_manager, node_info):
#('calendar', 'form', 'graph', 'kanban', 'pivot', 'search', 'tree', 'activity')
if any('btn' in node.get(cl, '') for cl in self._att_list('class')):
if node.get('role') != 'button':
self.handle_view_error(_('"<a>" tag with "btn" class must have "button" role'), raise_exception=False)
msg = _('"<a>" tag with "btn" class must have "button" role')
self.handle_view_error(msg, raise_exception=False)

def _validate_tag_ul(self, node, name_manager, node_info):
self._check_dropdown_menu(node) # was applied to all node, but in practice, only used on div and ul
@@ -1130,19 +1154,24 @@ def _check_dropdown_menu(self, node):
#('calendar', 'form', 'graph', 'kanban', 'pivot', 'search', 'tree', 'activity')
if any('dropdown-menu' in node.get(cl, '') for cl in self._att_list('class')):
if node.get('role') != 'menu':
self.handle_view_error(_('dropdown-menu class must have menu role'), raise_exception=False)
msg = _('dropdown-menu class must have menu role')
self.handle_view_error(msg, raise_exception=False)

def _check_progress_bar(self, node):
# todo check me, only used in one view, shoud we check progress-bar class instead?
if any('o_progressbar' in node.get(cl, '') for cl in self._att_list('class')):
if node.get('role') != 'progressbar':
self.handle_view_error(_('o_progressbar class must have progressbar role'), raise_exception=False)
msg = _('o_progressbar class must have progressbar role')
self.handle_view_error(msg, raise_exception=False)
if not any(node.get(at) for at in self._att_list('aria-valuenow')):
self.handle_view_error(_('o_progressbar class must have aria-valuenow attribute'), raise_exception=False)
msg = _('o_progressbar class must have aria-valuenow attribute')
self.handle_view_error(msg, raise_exception=False)
if not any(node.get(at) for at in self._att_list('aria-valuemin')):
self.handle_view_error(_('o_progressbar class must have aria-valuemin attribute'), raise_exception=False)
msg = _('o_progressbar class must have aria-valuemin attribute')
self.handle_view_error(msg, raise_exception=False)
if not any(node.get(at) for at in self._att_list('aria-valuemax')):
self.handle_view_error(_('o_progressbar class must have aria-valuemaxattribute'), raise_exception=False)
msg = _('o_progressbar class must have aria-valuemaxattribute')
self.handle_view_error(msg, raise_exception=False)

def _att_list(self, name):
return [name, 't-att-%s' % name, 't-attf-%s' % name]
@@ -1252,12 +1281,19 @@ def _validate_classes(self, node, expr):
self.handle_view_error(msg, raise_exception=False)

if any(klass.startswith('alert-') for klass in classes):
if not node.get('role') in ('alert', 'alertdialog', 'status') and 'alert-link' not in classes:
msg = _("An alert (class alert-*) must have an alert, alertdialog or status role or an alert-link class. Please use alert and alertdialog only for what expects to stop any activity to be read immediatly.")
if (
node.get('role') not in ('alert', 'alertdialog', 'status')
and 'alert-link' not in classes
):
msg = _("An alert (class alert-*) must have an alert, alertdialog or "
"status role or an alert-link class. Please use alert and "
"alertdialog only for what expects to stop any activity to "
"be read immediately.")
self.handle_view_error(msg, raise_exception=False)

if any(klass.startswith('fa-') for klass in classes):
self._validate_fa_class_accessibility(node, 'A <%s> with fa class (%s)' % (node.tag, expr))
description = 'A <%s> with fa class (%s)' % (node.tag, expr)
self._validate_fa_class_accessibility(node, description)

if any(klass.startswith('btn') for klass in classes):
if node.tag in ('a', 'button', 'select'):
@@ -1267,7 +1303,9 @@ def _validate_classes(self, node, expr):
elif any(klass in classes for klass in ('btn-group', 'btn-toolbar', 'btn-ship')):
pass
else:
msg = _("A simili button must be in tag a/button/select or tag `input` with type button/submit/reset or have class in btn-group/btn-toolbar/btn-ship")
msg = _("A simili button must be in tag a/button/select or tag `input` "
"with type button/submit/reset or have class in "
"btn-group/btn-toolbar/btn-ship")
self.handle_view_error(msg, raise_exception=False)

def _validate_fa_class_accessibility(self, node, description):
@@ -1279,7 +1317,8 @@ def _validate_fa_class_accessibility(self, node, description):
valid_t_attrs = {'t-value', 't-raw', 't-field', 't-esc'}

## Following or preceding text
if (node.tail or '').strip() or (node.getparent().text or '').strip(): # text<i class="fa-..."/> or <i class="fa-..."/>text or
if (node.tail or '').strip() or (node.getparent().text or '').strip():
# text<i class="fa-..."/> or <i class="fa-..."/>text or
return

## Following or preceding text in span
@@ -1324,8 +1363,8 @@ def contains_description(node, depth=0):
if contains_description(node):
return

msg = _('%s must have title in its tag, parents, descendants or have text') % description
self.handle_view_error(msg, raise_exception=False)
msg = _('%s must have title in its tag, parents, descendants or have text')
self.handle_view_error(msg % description, raise_exception=False)

def _get_client_domain_variables(self, domain, key, expr):
""" Returns all field and variable names present in the given domain

0 comments on commit 4d5a8bb

Please sign in to comment.
You can’t perform that action at this time.