[IMP] website: add qweb view to see view hierarchy
The core website behavior is the COW (copy on write) which makes it possible to
handle multiple websites on a single database.
When you do a CRUD operation on an `ir.ui.view` in a website context, the ORM
normal behavior is bypass to perform special stuff, such as:
- Write: if the view is generic (no `website_id`), we bifurcate the write on
the specific view (same `key` but with `website_id` set). If that
view doesn't exist, we create it and fork its whole descending
hierarchy. This write behavior is known as COW.
- Create: we ensure that view is created only for the website and no other.
Basically we just add a `website_id` to that view, but it might
also be about setting the correct `inherit_id` by finding the
specific view among multiple views with the same `key`.
- Unlink: if the view is generic, we will unlink that view but also create a
copy of that view for every other websites so they remain
unmodified: this is known as COU (copy on unlink).
All those behavior aims to satisfy to the multi website holy grail:
-- Any modification in a website context should never impact another website --
Because of those uncommon behaviors, it is sometimes hard to understand or
investigate a database, as views might be copied, up to the point where there
is multiple whole view hierarchies.
This commit attempts to create an user interface to easily figure what's going
on in a database at an `ir.ui.view` level.
The mains advantages of that new `ir.ui.view` Qweb view is to:
- easily figure which views are COW'd on which website
- easily detect which views have a real `arch` modification
- easily see that `arch` change
Closes #63040
Signed-off-by: Jérémy Kersten (jke) <jke@openerp.com>
This commit is contained in:
committed by
Jeremy Kersten
parent
905c9b52a7
commit
7cdf04df67
@@ -72,14 +72,15 @@ class View(models.Model):
|
||||
return super().create(vals_list)
|
||||
|
||||
def name_get(self):
|
||||
if (not self._context.get('display_website') and not self.env.user.has_group('website.group_multi_website')) or \
|
||||
not self._context.get('display_website'):
|
||||
if not (self._context.get('display_key') or self._context.get('display_website')):
|
||||
return super(View, self).name_get()
|
||||
|
||||
res = []
|
||||
for view in self:
|
||||
view_name = view.name
|
||||
if view.website_id:
|
||||
if self._context.get('display_key'):
|
||||
view_name += ' <%s>' % view.key
|
||||
if self._context.get('display_website') and view.website_id:
|
||||
view_name += ' [%s]' % view.website_id.name
|
||||
res.append((view.id, view_name))
|
||||
return res
|
||||
@@ -233,6 +234,10 @@ class View(models.Model):
|
||||
})
|
||||
page.menu_ids.filtered(lambda m: m.website_id.id == website.id).page_id = new_page.id
|
||||
|
||||
def _get_top_level_view(self):
|
||||
self.ensure_one()
|
||||
return self.inherit_id._get_top_level_view() if self.inherit_id else self
|
||||
|
||||
@api.model
|
||||
def get_related_views(self, key, bundles=False):
|
||||
'''Make this only return most specific views for website.'''
|
||||
@@ -358,15 +363,6 @@ class View(models.Model):
|
||||
return view.id
|
||||
return super(View, self.sudo()).get_view_id(xml_id)
|
||||
|
||||
def _get_original_view(self):
|
||||
"""Given a view, retrieve the original view it was COW'd from.
|
||||
The given view might already be the original one. In that case it will
|
||||
(and should) return itself.
|
||||
"""
|
||||
self.ensure_one()
|
||||
domain = [('key', '=', self.key), ('model_data_id', '!=', None)]
|
||||
return self.with_context(active_test=False).search(domain, limit=1) # Useless limit has multiple xmlid should not be possible
|
||||
|
||||
def _handle_visibility(self, do_raise=True):
|
||||
""" Check the visibility set on the main view and raise 403 if you should not have access.
|
||||
Order is: Public, Connected, Has group, Password
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
odoo.define('website.view_hierarchy', function (require) {
|
||||
"use strict";
|
||||
|
||||
const core = require('web.core');
|
||||
const qweb = require('web.qweb');
|
||||
const viewRegistry = require('web.view_registry');
|
||||
|
||||
const _t = core._t;
|
||||
|
||||
const Renderer = qweb.Renderer.extend({
|
||||
events: _.extend({}, qweb.Renderer.prototype.events, {
|
||||
'click .js_fold': '_onCollapseClick',
|
||||
'click .o_website_filter a': '_onWebsiteFilterClick',
|
||||
'click .o_search button': '_onSearchButtonClick',
|
||||
'click .o_show_diff': '_onShowDiffClick',
|
||||
'click .o_load_hierarchy': '_onLoadHierarchyClick',
|
||||
'keydown .o_search input': '_onSearchInputKeyDown',
|
||||
'input .o_search input': '_onSearchInputKeyInput',
|
||||
'change #o_show_inactive': '_onShowActiveClick',
|
||||
}),
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super(...arguments);
|
||||
|
||||
// Search
|
||||
this.cptFound = 0;
|
||||
this.prevSearch = '';
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
on_attach_callback: function () {
|
||||
this._super(...arguments);
|
||||
|
||||
const self = this;
|
||||
this._handleLastVisibleChild();
|
||||
// Fixed Navbar
|
||||
this.$('.o_tree_container').css({
|
||||
'padding-top': this.$('.o_tree_nav').outerHeight() + 10,
|
||||
});
|
||||
// Website Filters
|
||||
this.$wNodes = this.$("li[data-website_name]");
|
||||
this.$notwNodes = this.$("li:not([data-website_name])");
|
||||
const websiteNames = _.uniq($.map(self.$wNodes, el => el.getAttribute('data-website_name')));
|
||||
for (const websiteName of websiteNames) {
|
||||
this.$('.o_website_filter').append($('<a/>', {
|
||||
'class': 'dropdown-item',
|
||||
'data-website_name': websiteName,
|
||||
'text': websiteName,
|
||||
}));
|
||||
}
|
||||
this.$(`.o_website_filter a[data-website_name="${websiteNames[0] || '*'}"]`).click();
|
||||
// Highlight requested view as google does
|
||||
const reqViewId = this.$('.o_tree_container').data('requested-view-id');
|
||||
const $reqView = $(`[data-id="${reqViewId}"] span.js_fold`).first();
|
||||
$reqView.addClass('text-info');
|
||||
$('.o_content').scrollTo($reqView[0], 300, {offset: -200});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onCollapseClick: function (ev) {
|
||||
const $parent = $(ev.currentTarget).parent();
|
||||
const folded = $parent.find('.o_fold_icon').hasClass('fa-plus-square-o');
|
||||
let $ul, $oFoldIcon;
|
||||
if (folded) { // Unfold only self
|
||||
$ul = $parent.siblings('ul');
|
||||
$oFoldIcon = $parent.find('.o_fold_icon');
|
||||
} else { // Fold all
|
||||
$ul = $parent.parent().find('ul');
|
||||
$oFoldIcon = $parent.parent().find('.o_fold_icon');
|
||||
}
|
||||
$ul.toggleClass('d-none', !folded);
|
||||
$oFoldIcon.toggleClass('fa-minus-square-o', folded).toggleClass('fa-plus-square-o', !folded);
|
||||
this._handleLastVisibleChild();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onShowActiveClick: function (ev) {
|
||||
this.$('.o_is_inactive').toggleClass('d-none', !ev.currentTarget.checked);
|
||||
this._handleLastVisibleChild();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onWebsiteFilterClick: function (ev) {
|
||||
ev.preventDefault();
|
||||
// Update Dropdown Filter
|
||||
const $el = $(ev.currentTarget);
|
||||
$el.addClass('active').siblings().removeClass('active');
|
||||
$el.parent().siblings('.dropdown-toggle').text($el.text());
|
||||
// Show all views
|
||||
const websiteName = $el.data('website_name');
|
||||
this.$wNodes.add(this.$notwNodes).removeClass('d-none');
|
||||
if (websiteName !== '*') {
|
||||
// Hide all website views
|
||||
this.$wNodes.addClass('d-none');
|
||||
// Show selected website views
|
||||
const $selectedWebsiteNodes = this.$('li[data-website_name="${websiteName}"]');
|
||||
$selectedWebsiteNodes.removeClass('d-none');
|
||||
// Hide generic siblings
|
||||
$selectedWebsiteNodes.each(function () {
|
||||
$(this).siblings(`li[data-key="${$(this).data('key')}"]:not([data-website_name])`).addClass('d-none');
|
||||
});
|
||||
}
|
||||
// Preserve current inactive toggle state
|
||||
this.$('.o_is_inactive').toggleClass('d-none', !$('#o_show_inactive').prop('checked'));
|
||||
this._handleLastVisibleChild();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onSearchInputKeyDown: function (ev) {
|
||||
// <Tab> or <Enter>
|
||||
if (ev.which === 13 || ev.which === 9) {
|
||||
this._searchScrollTo($(ev.currentTarget).val(), !ev.shiftKey);
|
||||
ev.preventDefault();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onSearchInputKeyInput: function (ev) {
|
||||
// Useful for input empty either with ms-clear or by typing
|
||||
if (ev.currentTarget.value === "") {
|
||||
this._searchScrollTo("");
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onSearchButtonClick: function (ev) {
|
||||
this._searchScrollTo(this.$('.o_search input').val());
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onShowDiffClick: function (ev) {
|
||||
ev.preventDefault();
|
||||
this.do_action('base.reset_view_arch_wizard_action', {
|
||||
additional_context: {
|
||||
'active_model': 'ir.ui.view',
|
||||
'active_ids': [parseInt(ev.currentTarget.dataset['view_id'])],
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onLoadHierarchyClick: function (ev) {
|
||||
ev.preventDefault();
|
||||
this.do_action('website.action_show_viewhierarchy', {
|
||||
additional_context: {
|
||||
'active_model': 'ir.ui.view',
|
||||
'active_id': parseInt(ev.currentTarget.dataset['view_id']),
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Adds a class to the last visible element of every lists.
|
||||
* This is purely cosmetic to add a right angle dashed `:before` style in
|
||||
* css. This can't be done in css as there is no way to target a last
|
||||
* element by class.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_handleLastVisibleChild: function () {
|
||||
this.$('.o_last_visible_child').removeClass('o_last_visible_child');
|
||||
const lastElements = _.filter(_.map(
|
||||
this.$('ul'), el => $(el).find('> li:visible').last()[0]
|
||||
));
|
||||
$(lastElements).addClass('o_last_visible_child');
|
||||
|
||||
const selector = $('#o_show_inactive').prop('checked') ? '> li' : '> li:not(.o_is_inactive)';
|
||||
this.$('.o_fold_icon').map(function() {
|
||||
let $ico = $(this);
|
||||
let childs = $ico.parent().parent().first().find('ul').find(selector);
|
||||
$ico.toggleClass('d-none', !childs.length);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Searches and scrolls to view entries matching the given text. Exact
|
||||
* matches will be returned first. Search is done on `key`, `id` and `name`
|
||||
* for exact matches, and `key`, `name` for simple matches.
|
||||
*
|
||||
* @private
|
||||
* @param {string} search text to search and scroll to
|
||||
* @param {boolean} [forward] set to false to go to previous find
|
||||
*/
|
||||
_searchScrollTo: function (search, forward = true) {
|
||||
const foundClasses = 'o_search_found border border-info rounded px-2';
|
||||
this.$('.o_search_found').removeClass(foundClasses);
|
||||
this.$('.o_not_found').removeClass('o_not_found');
|
||||
this.$('.o_tab_hint').remove();
|
||||
if (search !== this.prevSearch) {
|
||||
this.prevSearch = search;
|
||||
this.cptFound = -1;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
// Exact match first
|
||||
const exactMatches = $(`[data-key="${search}" i], [data-id="${search}" i], [data-name="${search}" i]`).not(':hidden').get();
|
||||
let matches = $(`[data-key*="${search}" i], [data-name*="${search}" i]`).not(':hidden').not(exactMatches).get();
|
||||
matches = exactMatches.concat(matches);
|
||||
if (!matches.length) {
|
||||
this.$('.o_search input').addClass('o_not_found');
|
||||
} else {
|
||||
if (forward) {
|
||||
this.cptFound++;
|
||||
if (this.cptFound > matches.length - 1) {
|
||||
this.cptFound = 0;
|
||||
}
|
||||
} else {
|
||||
this.cptFound--;
|
||||
if (this.cptFound < 0) {
|
||||
this.cptFound = matches.length - 1;
|
||||
}
|
||||
}
|
||||
const el = matches[this.cptFound];
|
||||
$(el).children('p').addClass(foundClasses).append($('<span/>', {
|
||||
class: 'o_tab_hint text-info ml-auto small font-italic pr-2',
|
||||
text: _.str.sprintf(_t("Press %s for next %s"), "<Tab>", `[${this.cptFound + 1}/${matches.length}]`),
|
||||
}));
|
||||
$('.o_content').scrollTo(el, 0, {offset: -200});
|
||||
|
||||
this.prevSearch = search;
|
||||
this.$('.o_search input').focus();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const ViewHierarchy = qweb.View.extend({
|
||||
withSearchBar: false,
|
||||
config: _.extend({}, qweb.View.prototype.config, {
|
||||
Renderer: Renderer,
|
||||
}),
|
||||
});
|
||||
|
||||
viewRegistry.add('view_hierarchy', ViewHierarchy);
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
.o_tree_nav {
|
||||
top: unset;
|
||||
z-index: $zindex-dropdown - 1;
|
||||
|
||||
.o_search .o_not_found {
|
||||
box-shadow: 0 0 0 0.2rem red;
|
||||
}
|
||||
}
|
||||
|
||||
.o_tree_container {
|
||||
.o_text_orange {
|
||||
color: $orange;
|
||||
}
|
||||
.o_text_pink {
|
||||
color: $pink;
|
||||
}
|
||||
|
||||
.o_has_child .js_fold {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.o_search_found {
|
||||
background-color: $gray-200;
|
||||
}
|
||||
|
||||
.o_tree_entry {
|
||||
a {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
line-height: 2em;
|
||||
list-style: none;
|
||||
margin-left: 1em;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
border-left: 1px dashed;
|
||||
bottom: 0;
|
||||
content: '';
|
||||
display: block;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 0 1em;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
border-top: 1px dashed;
|
||||
content: '';
|
||||
display: block;
|
||||
height: 0;
|
||||
left: 0;
|
||||
margin-top: -1px;
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
&.o_last_visible_child:before {
|
||||
background: #fff;
|
||||
bottom: 0;
|
||||
height: auto;
|
||||
top: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
<template id="assets_backend" inherit_id="web.assets_backend" name="Website Backend Assets (used in backend interface)">
|
||||
<xpath expr="//link[last()]" position="after">
|
||||
<link rel="stylesheet" type="text/scss" href="/website/static/src/scss/view_hierarchy.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/website/static/src/scss/website.backend.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/website/static/src/scss/website_visitor_views.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/website/static/src/scss/website.theme_install.scss"/>
|
||||
@@ -44,6 +45,7 @@
|
||||
<script type="text/javascript" src="/website/static/src/js/backend/button.js"/>
|
||||
<script type="text/javascript" src="/website/static/src/js/backend/dashboard.js"/>
|
||||
<script type="text/javascript" src="/website/static/src/js/backend/res_config_settings.js"/>
|
||||
<script type="text/javascript" src="/website/static/src/js/backend/view_hierarchy.js"/>
|
||||
<script type="text/javascript" src="/website/static/src/js/widget_iframe.js"/>
|
||||
<script type="text/javascript" src="/website/static/src/js/theme_preview_kanban.js"/>
|
||||
<script type="text/javascript" src="/website/static/src/js/theme_preview_form.js"/>
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
<field name="context" eval="{'search_default_category_id': ref('base.module_category_website_website'), 'searchpanel_default_category_id': ref('base.module_category_website')}"/>
|
||||
</record>
|
||||
|
||||
<record id="action_show_viewhierarchy" model="ir.actions.act_window">
|
||||
<field name="name">Show View Hierarchy</field>
|
||||
<field name="res_model">ir.ui.view</field>
|
||||
<field name="view_mode">qweb</field>
|
||||
</record>
|
||||
|
||||
<!-- ====== website views ==============================================
|
||||
==================================================================== -->
|
||||
<record id="view_website_form" model="ir.ui.view">
|
||||
@@ -254,9 +260,13 @@
|
||||
<field name="inherit_id" position="attributes">
|
||||
<attribute name="context">{'display_website': True}</attribute>
|
||||
</field>
|
||||
<field name="type" position="after">
|
||||
<field name="model" position="before">
|
||||
<field name="website_id" options="{'no_create': True}" groups="website.group_multi_website"/>
|
||||
<field name="key"/>
|
||||
<label for="key"/>
|
||||
<div class='o_row'>
|
||||
<field name="key"/>
|
||||
<button string="" attrs="{'invisible': [('type', '!=', 'qweb')]}" name="website.action_show_viewhierarchy" icon="fa-sitemap" type="action" class="btn btn-link"/>
|
||||
</div>
|
||||
<field name="page_ids" invisible="1" />
|
||||
<field name="first_page_id" attrs="{'invisible': [('page_ids', '=', [])]}" />
|
||||
<field name="visibility" attrs="{'invisible': [('type', '!=', 'qweb')]}" />
|
||||
@@ -275,7 +285,11 @@
|
||||
<field name="model">ir.ui.view</field>
|
||||
<field name="inherit_id" ref="base.view_view_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree position="attributes">
|
||||
<attribute name="decoration-muted">not active</attribute>
|
||||
</tree>
|
||||
<field name="name" position="after">
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
</field>
|
||||
<field name="xml_id" position="before">
|
||||
@@ -284,6 +298,81 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_view_qweb" model="ir.ui.view">
|
||||
<field name="name">View Hierarchy</field>
|
||||
<field name="type">qweb</field>
|
||||
<field name="model">ir.ui.view</field>
|
||||
<field name="arch" type="xml">
|
||||
<qweb js_class="view_hierarchy">
|
||||
<nav class="o_tree_nav navbar justify-content-start w-100 fixed-top bg-white shadow">
|
||||
<div class="dropdown ml-2 border border-info rounded">
|
||||
<a href="#" role="button" class="btn dropdown-toggle text-info" data-toggle="dropdown">
|
||||
All Websites
|
||||
</a>
|
||||
<div class="dropdown-menu o_website_filter">
|
||||
<a href="#" class="dropdown-item active" data-website_name="*">All Websites</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-2 custom-control custom-switch">
|
||||
<input id="o_show_inactive" class="custom-control-input" type="checkbox"/>
|
||||
<label class="custom-control-label" for="o_show_inactive">Show inactive views</label>
|
||||
</div>
|
||||
<div class="o_search input-group ml-auto col-8 col-sm-6 col-md-4 col-xl-3" role="search">
|
||||
<input type="search" name="search" class="form-control border-info" placeholder="Name, id or key"/>
|
||||
<div class="input-group-append">
|
||||
<button type="submit" class="btn btn-info" aria-label="Search" title="Search">
|
||||
<i class="fa fa-search"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<t t-set="view" t-value="env['ir.ui.view'].browse(context['active_id']).with_context(active_test=False)"/>
|
||||
<t t-set="requested_view" t-value="view"/>
|
||||
<t t-set="view" t-value="view._get_top_level_view()"/>
|
||||
<div class="o_tree_container ml-2" t-att-data-requested-view-id="requested_view.id">
|
||||
<t t-set="sibling_views" t-value="view.search([('key', '=', view.key)]) - view"/>
|
||||
<div t-if="sibling_views" class="alert alert-info m-1 p-1">
|
||||
Multiple tree exists for this view
|
||||
<a t-foreach="sibling_views" t-as="sibling_view" href="#" class="o_load_hierarchy" t-att-data-view_id="sibling_view.id">
|
||||
<i class="fa fa-arrow-right mr-1"/>
|
||||
<t t-esc="sibling_view.with_context(display_website=True, display_key=True).display_name"/>
|
||||
</a>
|
||||
</div>
|
||||
<t t-call="website.report_viewhierarchy_children"/>
|
||||
</div>
|
||||
</qweb>
|
||||
</field>
|
||||
</record>
|
||||
<template id="report_viewhierarchy_children">
|
||||
<t t-set="classes_for_search" t-value="'d-flex align-items-center'"/>
|
||||
<p t-attf-class="o_tree_entry mb-0 #{'text-muted font-weight-normal' if not view.active else ''} #{view.inherit_children_ids and 'o_has_child' or ''} #{classes_for_search}">
|
||||
<i t-if="view.inherit_children_ids" class="js_fold o_fold_icon fa fa-minus-square-o mr-1"/>
|
||||
<i t-if="view.arch_updated" class="fa fa-pencil-square mr-1 o_text_pink" title="This view arch has been modified"/>
|
||||
<span class="js_fold">
|
||||
<t t-esc="view.name"/> (<span class="font-weight-bold" t-esc="view.key"/>)
|
||||
<span class="o_text_orange" t-if="view.website_id" t-esc="' [%s]' % view.website_id.name"/>
|
||||
</span>
|
||||
<a type="action" data-model="ir.ui.view" t-att-data-res-id="view.id">
|
||||
<i class="fa fa-eye ml-2 text-muted" title="Go to View"/>
|
||||
</a>
|
||||
<a href="#" class="o_show_diff" t-att-data-view_id="view.id">
|
||||
<i class="fa fa-files-o ml-2 text-muted" title="Show Arch Diff"/>
|
||||
</a>
|
||||
</p>
|
||||
<ul t-if="view.inherit_children_ids">
|
||||
<t t-foreach="view.inherit_children_ids" t-as="child">
|
||||
<li t-att-class="not child.active and 'o_is_inactive d-none'"
|
||||
t-att-data-website_name="child.website_id.name" t-att-data-key="child.key"
|
||||
t-att-data-id="child.id" t-att-data-name="child.name">
|
||||
<t t-call="website.report_viewhierarchy_children">
|
||||
<t t-set="view" t-value="child"/>
|
||||
</t>
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<record id="reset_view_arch_wizard_view" model="ir.ui.view">
|
||||
<field name="model">reset.view.arch.wizard</field>
|
||||
<field name="inherit_id" ref="base.reset_view_arch_wizard_view"/>
|
||||
|
||||
Reference in New Issue
Block a user