[FWD] [MERGE] Foward port of web-7.0 until rev 3771.
bzr revid: tde@openerp.com-20130221110355-p00935eqn4tstkx9
This commit is contained in:
commit
5e09c5c361
|
@ -13,6 +13,7 @@ import os
|
|||
import re
|
||||
import simplejson
|
||||
import time
|
||||
import urllib
|
||||
import urllib2
|
||||
import zlib
|
||||
from xml.etree import ElementTree
|
||||
|
@ -293,9 +294,9 @@ def manifest_list(req, extension, mods=None, db=None):
|
|||
if not req.debug:
|
||||
path = '/web/webclient/' + extension
|
||||
if mods is not None:
|
||||
path += '?mods=' + mods
|
||||
path += '?' + urllib.urlencode({'mods': mods})
|
||||
elif db:
|
||||
path += '?db=' + db
|
||||
path += '?' + urllib.urlencode({'db': db})
|
||||
return [path]
|
||||
files = manifest_glob(req, extension, addons=mods, db=db)
|
||||
i_am_diabetic = req.httprequest.environ["QUERY_STRING"].count("no_sugar") >= 1 or \
|
||||
|
|
|
@ -32,12 +32,12 @@ NOMODULE_TEMPLATE = Template(u"""<!DOCTYPE html>
|
|||
</form>
|
||||
</body>
|
||||
</html>
|
||||
""")
|
||||
""", default_filters=['h'])
|
||||
NOTFOUND = Template(u"""
|
||||
<p>Unable to find the module [${module}], please check that the module
|
||||
name is correct and the module is on OpenERP's path.</p>
|
||||
<a href="/web/tests"><< Back to tests</a>
|
||||
""")
|
||||
""", default_filters=['h'])
|
||||
TESTING = Template(u"""<!DOCTYPE html>
|
||||
<html style="height: 100%">
|
||||
<%def name="to_path(module, p)">/${module}/${p}</%def>
|
||||
|
@ -51,9 +51,9 @@ TESTING = Template(u"""<!DOCTYPE html>
|
|||
<script src="/web/static/lib/qunit/qunit.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var oe_db_info = ${db_info};
|
||||
var oe_db_info = ${db_info | n};
|
||||
// List of modules, each module is preceded by its dependencies
|
||||
var oe_all_dependencies = ${dependencies};
|
||||
var oe_all_dependencies = ${dependencies | n};
|
||||
QUnit.config.testTimeout = 5 * 60 * 1000;
|
||||
</script>
|
||||
</head>
|
||||
|
@ -83,7 +83,7 @@ TESTING = Template(u"""<!DOCTYPE html>
|
|||
% endif
|
||||
% endfor
|
||||
</html>
|
||||
""")
|
||||
""", default_filters=['h'])
|
||||
|
||||
class TestRunnerController(http.Controller):
|
||||
_cp_path = '/web/tests'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@charset "UTF-8";
|
||||
@charset "utf-8";
|
||||
@font-face {
|
||||
font-family: "mnmliconsRegular";
|
||||
src: url("/web/static/src/font/mnmliconsv21-webfont.eot") format("eot");
|
||||
|
@ -1415,7 +1415,13 @@
|
|||
display: inline-block;
|
||||
overflow: hidden;
|
||||
}
|
||||
.openerp .oe_view_manager {
|
||||
display: table;
|
||||
height: inherit;
|
||||
width: 100%;
|
||||
}
|
||||
.openerp .oe_view_manager .oe_view_manager_body {
|
||||
display: table-row;
|
||||
height: inherit;
|
||||
}
|
||||
.openerp .oe_view_manager .oe_view_manager_view_kanban {
|
||||
|
@ -3089,7 +3095,6 @@
|
|||
}
|
||||
|
||||
.kitten-mode-activated {
|
||||
background-image: url(http://placekitten.com/g/1365/769);
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
|
|
@ -1137,7 +1137,11 @@ $sheet-padding: 16px
|
|||
// }}}
|
||||
// ViewManager common {{{
|
||||
.oe_view_manager
|
||||
display: table
|
||||
height: inherit
|
||||
width: 100%
|
||||
.oe_view_manager_body
|
||||
display: table-row
|
||||
height: inherit
|
||||
.oe_view_manager_view_kanban
|
||||
height: inherit
|
||||
|
@ -2436,7 +2440,6 @@ $sheet-padding: 16px
|
|||
// }}}
|
||||
// Kitten Mode {{{
|
||||
.kitten-mode-activated
|
||||
background-image: url(http://placekitten.com/g/1365/769)
|
||||
background-size: cover
|
||||
background-attachment: fixed
|
||||
>*
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
|
@ -1100,6 +1100,9 @@ instance.web.UserMenu = instance.web.Widget.extend({
|
|||
};
|
||||
this.update_promise = this.update_promise.then(fct, fct);
|
||||
},
|
||||
on_menu_help: function() {
|
||||
window.open('http://help.openerp.com', '_blank');
|
||||
},
|
||||
on_menu_logout: function() {
|
||||
this.trigger('user_logout');
|
||||
},
|
||||
|
@ -1213,6 +1216,7 @@ instance.web.WebClient = instance.web.Client.extend({
|
|||
return $.when(this._super()).then(function() {
|
||||
if (jQuery.param !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined) {
|
||||
$("body").addClass("kitten-mode-activated");
|
||||
$("body").css("background-image", "url(" + instance.session.origin + "/web/static/src/img/back-enable.jpg" + ")");
|
||||
if ($.blockUI) {
|
||||
$.blockUI.defaults.message = '<img src="http://www.amigrave.com/kitten.gif">';
|
||||
}
|
||||
|
@ -1396,8 +1400,9 @@ instance.web.WebClient = instance.web.Client.extend({
|
|||
},
|
||||
on_hashchange: function(event) {
|
||||
var self = this;
|
||||
var state = event.getState(true);
|
||||
if (!_.isEqual(this._current_state, state)) {
|
||||
var stringstate = event.getState(false);
|
||||
if (!_.isEqual(this._current_state, stringstate)) {
|
||||
var state = event.getState(true);
|
||||
if(!state.action && state.menu_id) {
|
||||
self.menu.has_been_loaded.done(function() {
|
||||
self.menu.do_reload().done(function() {
|
||||
|
@ -1409,13 +1414,13 @@ instance.web.WebClient = instance.web.Client.extend({
|
|||
this.action_manager.do_load_state(state, !!this._current_state);
|
||||
}
|
||||
}
|
||||
this._current_state = state;
|
||||
this._current_state = stringstate;
|
||||
},
|
||||
do_push_state: function(state) {
|
||||
this.set_title(state.title);
|
||||
delete state.title;
|
||||
var url = '#' + $.param(state);
|
||||
this._current_state = _.clone(state);
|
||||
this._current_state = $.deparam($.param(state), false); // stringify all values
|
||||
$.bbq.pushState(url);
|
||||
this.trigger('state_pushed', state);
|
||||
},
|
||||
|
@ -1425,9 +1430,10 @@ instance.web.WebClient = instance.web.Client.extend({
|
|||
.then(function (result) {
|
||||
return self.action_mutex.exec(function() {
|
||||
if (options.needaction) {
|
||||
result.context = new instance.web.CompoundContext(
|
||||
result.context,
|
||||
{search_default_message_unread: true});
|
||||
result.context = new instance.web.CompoundContext(result.context, {
|
||||
search_default_message_unread: true,
|
||||
search_disable_custom_filters: true,
|
||||
});
|
||||
}
|
||||
var completed = $.Deferred();
|
||||
$.when(self.action_manager.do_action(result, {
|
||||
|
|
|
@ -690,7 +690,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
return filter.user_id && filter.is_default;
|
||||
});
|
||||
if (personal_filter) {
|
||||
this.custom_filters.enable_filter(personal_filter, true);
|
||||
this.custom_filters.toggle_filter(personal_filter, true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -698,7 +698,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
return !filter.user_id && filter.is_default;
|
||||
});
|
||||
if (global_filter) {
|
||||
this.custom_filters.enable_filter(global_filter, true);
|
||||
this.custom_filters.toggle_filter(global_filter, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1541,6 +1541,9 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
|
|||
})
|
||||
.on('reset', this.proxy('clear_selection'));
|
||||
this.$el.on('submit', 'form', this.proxy('save_current'));
|
||||
this.$el.on('click', 'input[type=checkbox]', function() {
|
||||
$(this).siblings('input[type=checkbox]').prop('checked', false);
|
||||
});
|
||||
this.$el.on('click', 'h4', function () {
|
||||
self.$el.toggleClass('oe_opened');
|
||||
});
|
||||
|
@ -1591,6 +1594,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
|
|||
get_groupby: function () { return [filter.context]; },
|
||||
get_domain: function () { return filter.domain; }
|
||||
},
|
||||
_id: filter['id'],
|
||||
is_custom_filter: true,
|
||||
values: [{label: filter.name, value: null}]
|
||||
};
|
||||
|
@ -1632,10 +1636,18 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
|
|||
}
|
||||
|
||||
$filter.unbind('click').click(function () {
|
||||
self.enable_filter(filter);
|
||||
self.toggle_filter(filter);
|
||||
});
|
||||
},
|
||||
enable_filter: function (filter, preventSearch) {
|
||||
toggle_filter: function (filter, preventSearch) {
|
||||
var current = this.view.query.find(function (facet) {
|
||||
return facet.get('_id') === filter.id;
|
||||
});
|
||||
if (current) {
|
||||
this.view.query.remove(current);
|
||||
this.$filters[this.key_for(filter)].removeClass('oe_selected');
|
||||
return;
|
||||
}
|
||||
this.view.query.reset([this.facet_for(filter)], {
|
||||
preventSearch: preventSearch || false});
|
||||
this.$filters[this.key_for(filter)].addClass('oe_selected');
|
||||
|
@ -1800,6 +1812,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @
|
|||
template: 'SearchView.extended_search.proposition',
|
||||
events: {
|
||||
'change .searchview_extended_prop_field': 'changed',
|
||||
'change .searchview_extended_prop_op': 'operator_changed',
|
||||
'click .searchview_extended_delete_prop': function (e) {
|
||||
e.stopPropagation();
|
||||
this.getParent().remove_proposition(this);
|
||||
|
@ -1831,6 +1844,17 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @
|
|||
this.select_field(_.detect(this.fields, function(x) {return x.name == nval;}));
|
||||
}
|
||||
},
|
||||
operator_changed: function (e) {
|
||||
var $value = this.$('.searchview_extended_prop_value');
|
||||
switch ($(e.target).val()) {
|
||||
case '∃':
|
||||
case '∄':
|
||||
$value.hide();
|
||||
break;
|
||||
default:
|
||||
$value.show();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Selects the provided field object
|
||||
*
|
||||
|
@ -1859,7 +1883,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @
|
|||
.text(String(operator.text))
|
||||
.appendTo(self.$('.searchview_extended_prop_op'));
|
||||
});
|
||||
var $value_loc = this.$('.searchview_extended_prop_value').empty();
|
||||
var $value_loc = this.$('.searchview_extended_prop_value').show().empty();
|
||||
this.value.appendTo($value_loc);
|
||||
|
||||
},
|
||||
|
@ -1867,19 +1891,12 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @
|
|||
if ( this.attrs.selected == null)
|
||||
return null;
|
||||
var field = this.attrs.selected;
|
||||
var op = this.$('.searchview_extended_prop_op')[0];
|
||||
var operator = op.options[op.selectedIndex];
|
||||
var op_select = this.$('.searchview_extended_prop_op')[0];
|
||||
var operator = op_select.options[op_select.selectedIndex];
|
||||
|
||||
return {
|
||||
label: _.str.sprintf(_t('%(field)s %(operator)s "%(value)s"'), {
|
||||
field: field.string,
|
||||
// According to spec, HTMLOptionElement#label should return
|
||||
// HTMLOptionElement#text when not defined/empty, but it does
|
||||
// not in older Webkit (between Safari 5.1.5 and Chrome 17) and
|
||||
// Gecko (pre Firefox 7) browsers, so we need a manual fallback
|
||||
// for those
|
||||
operator: operator.label || operator.text,
|
||||
value: this.value}),
|
||||
value: [field.name, operator.value, this.value.get_value()]
|
||||
label: this.value.get_label(field, operator),
|
||||
value: this.value.get_domain(field, operator),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -1889,6 +1906,37 @@ instance.web.search.ExtendedSearchProposition.Field = instance.web.Widget.extend
|
|||
this._super(parent);
|
||||
this.field = field;
|
||||
},
|
||||
get_label: function (field, operator) {
|
||||
var format;
|
||||
switch (operator.value) {
|
||||
case '∃': case '∄': format = _t('%(field)s %(operator)s'); break;
|
||||
default: format = _t('%(field)s %(operator)s "%(value)s"'); break;
|
||||
}
|
||||
return this.format_label(format, field, operator);
|
||||
},
|
||||
format_label: function (format, field, operator) {
|
||||
return _.str.sprintf(format, {
|
||||
field: field.string,
|
||||
// According to spec, HTMLOptionElement#label should return
|
||||
// HTMLOptionElement#text when not defined/empty, but it does
|
||||
// not in older Webkit (between Safari 5.1.5 and Chrome 17) and
|
||||
// Gecko (pre Firefox 7) browsers, so we need a manual fallback
|
||||
// for those
|
||||
operator: operator.label || operator.text,
|
||||
value: this
|
||||
});
|
||||
},
|
||||
get_domain: function (field, operator) {
|
||||
switch (operator.value) {
|
||||
case '∃': return this.make_domain(field.name, '!=', false);
|
||||
case '∄': return this.make_domain(field.name, '=', false);
|
||||
default: return this.make_domain(
|
||||
field.name, operator.value, this.get_value());
|
||||
}
|
||||
},
|
||||
make_domain: function (field, operator, value) {
|
||||
return [field, operator, value];
|
||||
},
|
||||
/**
|
||||
* Returns a human-readable version of the value, in case the "logical"
|
||||
* and the "semantic" values of a field differ (as for selection fields,
|
||||
|
@ -1908,7 +1956,9 @@ instance.web.search.ExtendedSearchProposition.Char = instance.web.search.Extende
|
|||
{value: "ilike", text: _lt("contains")},
|
||||
{value: "not ilike", text: _lt("doesn't contain")},
|
||||
{value: "=", text: _lt("is equal to")},
|
||||
{value: "!=", text: _lt("is not equal to")}
|
||||
{value: "!=", text: _lt("is not equal to")},
|
||||
{value: "∃", text: _lt("is set")},
|
||||
{value: "∄", text: _lt("is not set")}
|
||||
],
|
||||
get_value: function() {
|
||||
return this.$el.val();
|
||||
|
@ -1922,7 +1972,9 @@ instance.web.search.ExtendedSearchProposition.DateTime = instance.web.search.Ext
|
|||
{value: ">", text: _lt("greater than")},
|
||||
{value: "<", text: _lt("less than")},
|
||||
{value: ">=", text: _lt("greater or equal than")},
|
||||
{value: "<=", text: _lt("less or equal than")}
|
||||
{value: "<=", text: _lt("less or equal than")},
|
||||
{value: "∃", text: _lt("is set")},
|
||||
{value: "∄", text: _lt("is not set")}
|
||||
],
|
||||
/**
|
||||
* Date widgets live in view_form which is not yet loaded when this is
|
||||
|
@ -1956,7 +2008,9 @@ instance.web.search.ExtendedSearchProposition.Integer = instance.web.search.Exte
|
|||
{value: ">", text: _lt("greater than")},
|
||||
{value: "<", text: _lt("less than")},
|
||||
{value: ">=", text: _lt("greater or equal than")},
|
||||
{value: "<=", text: _lt("less or equal than")}
|
||||
{value: "<=", text: _lt("less or equal than")},
|
||||
{value: "∃", text: _lt("is set")},
|
||||
{value: "∄", text: _lt("is not set")}
|
||||
],
|
||||
toString: function () {
|
||||
return this.$el.val();
|
||||
|
@ -1981,7 +2035,9 @@ instance.web.search.ExtendedSearchProposition.Float = instance.web.search.Extend
|
|||
{value: ">", text: _lt("greater than")},
|
||||
{value: "<", text: _lt("less than")},
|
||||
{value: ">=", text: _lt("greater or equal than")},
|
||||
{value: "<=", text: _lt("less or equal than")}
|
||||
{value: "<=", text: _lt("less or equal than")},
|
||||
{value: "∃", text: _lt("is set")},
|
||||
{value: "∄", text: _lt("is not set")}
|
||||
],
|
||||
toString: function () {
|
||||
return this.$el.val();
|
||||
|
@ -1999,7 +2055,9 @@ instance.web.search.ExtendedSearchProposition.Selection = instance.web.search.Ex
|
|||
template: 'SearchView.extended_search.proposition.selection',
|
||||
operators: [
|
||||
{value: "=", text: _lt("is")},
|
||||
{value: "!=", text: _lt("is not")}
|
||||
{value: "!=", text: _lt("is not")},
|
||||
{value: "∃", text: _lt("is set")},
|
||||
{value: "∄", text: _lt("is not set")}
|
||||
],
|
||||
toString: function () {
|
||||
var select = this.$el[0];
|
||||
|
@ -2016,7 +2074,10 @@ instance.web.search.ExtendedSearchProposition.Boolean = instance.web.search.Exte
|
|||
{value: "=", text: _lt("is true")},
|
||||
{value: "!=", text: _lt("is false")}
|
||||
],
|
||||
toString: function () { return ''; },
|
||||
get_label: function (field, operator) {
|
||||
return this.format_label(
|
||||
_t('%(field)s %(operator)s'), field, operator);
|
||||
},
|
||||
get_value: function() {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -251,9 +251,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
this.dataset.ids.push(state.id);
|
||||
}
|
||||
this.dataset.select_id(state.id);
|
||||
if (warm) {
|
||||
this.do_show();
|
||||
}
|
||||
this.do_show({ reload: warm });
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
@ -511,9 +509,13 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
var on_change = widget.node.attrs.on_change;
|
||||
if (on_change) {
|
||||
var change_spec = self.parse_on_change(on_change, widget);
|
||||
var id = [self.datarecord.id == null ? [] : [self.datarecord.id]];
|
||||
var ids = [];
|
||||
if (self.datarecord.id && !instance.web.BufferedDataSet.virtual_id_regex.test(self.datarecord.id)) {
|
||||
// In case of a o2m virtual id, we should pass an empty ids list
|
||||
ids.push(self.datarecord.id);
|
||||
}
|
||||
def = new instance.web.Model(self.dataset.model).call(
|
||||
change_spec.method, id.concat(change_spec.args));
|
||||
change_spec.method, [ids].concat(change_spec.args));
|
||||
} else {
|
||||
def = $.when({});
|
||||
}
|
||||
|
@ -2376,6 +2378,7 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({
|
|||
type_of_date: "datetime",
|
||||
events: {
|
||||
'change .oe_datepicker_master': 'change_datetime',
|
||||
'dragstart img.oe_datepicker_trigger': function () { return false; },
|
||||
},
|
||||
init: function(parent) {
|
||||
this._super(parent);
|
||||
|
@ -2957,7 +2960,9 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
|
|||
case $.ui.keyCode.DOWN:
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
},
|
||||
'dragstart .oe_m2o_drop_down_button img': function () { return false; },
|
||||
'dragstart .oe_m2o_cm_button': function () { return false; }
|
||||
},
|
||||
init: function(field_manager, node) {
|
||||
this._super(field_manager, node);
|
||||
|
@ -4450,6 +4455,7 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
|
|||
* options:
|
||||
* -readonly: only applicable when not in creation mode, default to false
|
||||
* - alternative_form_view
|
||||
* - view_id
|
||||
* - write_function
|
||||
* - read_function
|
||||
* - create_function
|
||||
|
@ -4516,7 +4522,7 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
|
|||
_.extend(options, {
|
||||
$buttons: this.$buttonpane,
|
||||
});
|
||||
this.view_form = new instance.web.FormView(this, this.dataset, false, options);
|
||||
this.view_form = new instance.web.FormView(this, this.dataset, this.options.view_id || false, options);
|
||||
if (this.options.alternative_form_view) {
|
||||
this.view_form.set_embedded_view(this.options.alternative_form_view);
|
||||
}
|
||||
|
@ -4545,6 +4551,7 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
|
|||
});
|
||||
var $cbutton = self.$buttonpane.find(".oe_abstractformpopup-form-close");
|
||||
$cbutton.click(function() {
|
||||
self.view_form.trigger('on_button_cancel');
|
||||
self.check_exit();
|
||||
});
|
||||
self.view_form.do_show();
|
||||
|
@ -5216,6 +5223,8 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
|
|||
this.options.clickable = this.options.clickable || (this.node.attrs || {}).clickable || false;
|
||||
this.options.visible = this.options.visible || (this.node.attrs || {}).statusbar_visible || false;
|
||||
this.set({value: false});
|
||||
this.field_manager.on("view_content_has_changed", this, this.render_value);
|
||||
this.selection_mutex = new $.Mutex();
|
||||
},
|
||||
start: function() {
|
||||
if (this.options.clickable) {
|
||||
|
@ -5234,14 +5243,16 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
|
|||
},
|
||||
render_value: function() {
|
||||
var self = this;
|
||||
self.get_selection().done(function() {
|
||||
var content = QWeb.render("FieldStatus.content", {widget: self});
|
||||
self.$el.html(content);
|
||||
var colors = JSON.parse((self.node.attrs || {}).statusbar_colors || "{}");
|
||||
var color = colors[self.get('value')];
|
||||
if (color) {
|
||||
self.$("oe_active").css("color", color);
|
||||
}
|
||||
self.selection_mutex.exec(function() {
|
||||
return self.get_selection().done(function() {
|
||||
var content = QWeb.render("FieldStatus.content", {widget: self});
|
||||
self.$el.html(content);
|
||||
var colors = JSON.parse((self.node.attrs || {}).statusbar_colors || "{}");
|
||||
var color = colors[self.get('value')];
|
||||
if (color) {
|
||||
self.$("oe_active").css("color", color);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
/** Get the selection and render it
|
||||
|
|
|
@ -1186,7 +1186,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
|
|||
}
|
||||
});
|
||||
instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.web.ListView.Groups# */{
|
||||
passtrough_events: 'action deleted row_link',
|
||||
passthrough_events: 'action deleted row_link',
|
||||
/**
|
||||
* Grouped display for the ListView. Handles basic DOM events and interacts
|
||||
* with the :js:class:`~DataGroup` bound to it.
|
||||
|
@ -1406,7 +1406,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
|
|||
// can have selections spanning multiple links
|
||||
var selection = self.get_selection();
|
||||
$this.trigger(e, [selection.ids, selection.records]);
|
||||
}).bind(this.passtrough_events, function (e) {
|
||||
}).bind(this.passthrough_events, function (e) {
|
||||
// additional positional parameters are provided to trigger as an
|
||||
// Array, following the event type or event object, but are
|
||||
// provided to the .bind event handler as *args.
|
||||
|
|
|
@ -795,7 +795,7 @@ openerp.web.list_editable = function (instance) {
|
|||
});
|
||||
|
||||
instance.web.ListView.Groups.include(/** @lends instance.web.ListView.Groups# */{
|
||||
passtrough_events: instance.web.ListView.Groups.prototype.passtrough_events + " edit saved",
|
||||
passthrough_events: instance.web.ListView.Groups.prototype.passthrough_events + " edit saved",
|
||||
get_row_for: function (record) {
|
||||
return _(this.children).chain()
|
||||
.invoke('get_row_for', record)
|
||||
|
|
|
@ -190,6 +190,18 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
|||
});
|
||||
state = _.extend(params || {}, state);
|
||||
}
|
||||
if (this.inner_action.context) {
|
||||
var active_id = this.inner_action.context.active_id;
|
||||
if (active_id) {
|
||||
state["active_id"] = active_id;
|
||||
}
|
||||
var active_ids = this.inner_action.context.active_ids;
|
||||
if (active_ids && !(active_ids.length === 1 && active_ids[0] === active_id)) {
|
||||
// We don't push active_ids if it's a single element array containing the active_id
|
||||
// This makes the url shorter in most cases.
|
||||
state["active_ids"] = this.inner_action.context.active_ids.join(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!this.dialog) {
|
||||
this.getParent().do_push_state(state);
|
||||
|
@ -212,8 +224,22 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
|||
} else {
|
||||
var run_action = (!this.inner_widget || !this.inner_widget.action) || this.inner_widget.action.id !== state.action;
|
||||
if (run_action) {
|
||||
var add_context = {};
|
||||
if (state.active_id) {
|
||||
add_context.active_id = state.active_id;
|
||||
}
|
||||
if (state.active_ids) {
|
||||
// The jQuery BBQ plugin does some parsing on values that are valid integers.
|
||||
// It means that if there's only one item, it will do parseInt() on it,
|
||||
// otherwise it will keep the comma seperated list as string.
|
||||
add_context.active_ids = state.active_ids.toString().split(',').map(function(id) {
|
||||
return parseInt(id, 10) || id;
|
||||
});
|
||||
} else if (state.active_id) {
|
||||
add_context.active_ids = [state.active_id];
|
||||
}
|
||||
this.null_action();
|
||||
action_loaded = this.do_action(state.action);
|
||||
action_loaded = this.do_action(state.action, { additional_context: add_context });
|
||||
$.when(action_loaded || null).done(function() {
|
||||
instance.webclient.menu.has_been_loaded.done(function() {
|
||||
if (self.inner_action && self.inner_action.id) {
|
||||
|
@ -249,12 +275,25 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
|||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Execute an OpenERP action
|
||||
*
|
||||
* @param {Number|String|Object} Can be either an action id, a client action or an action descriptor.
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.clear_breadcrumbs=false] Clear the breadcrumbs history list
|
||||
* @param {Function} [options.on_reverse_breadcrumb] Callback to be executed whenever an anterior breadcrumb item is clicked on.
|
||||
* @param {Function} [options.on_close] Callback to be executed when the dialog is closed (only relevant for target=new actions)
|
||||
* @param {Function} [options.action_menu_id] Manually set the menu id on the fly.
|
||||
* @param {Object} [options.additional_context] Additional context to be merged with the action's context.
|
||||
* @return {jQuery.Deferred} Action loaded
|
||||
*/
|
||||
do_action: function(action, options) {
|
||||
options = _.defaults(options || {}, {
|
||||
clear_breadcrumbs: false,
|
||||
on_reverse_breadcrumb: function() {},
|
||||
on_close: function() {},
|
||||
action_menu_id: null,
|
||||
additional_context: {},
|
||||
});
|
||||
if (action === false) {
|
||||
action = { type: 'ir.actions.act_window_close' };
|
||||
|
@ -269,9 +308,13 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
|||
}
|
||||
|
||||
// Ensure context & domain are evaluated and can be manipulated/used
|
||||
if (action.context) {
|
||||
action.context = instance.web.pyeval.eval(
|
||||
'context', action.context);
|
||||
var ncontext = new instance.web.CompoundContext(options.additional_context, action.context || {});
|
||||
action.context = instance.web.pyeval.eval('context', ncontext);
|
||||
if (action.context.active_id || action.context.active_ids) {
|
||||
// Here we assume that when an `active_id` or `active_ids` is used
|
||||
// in the context, we are in a `related` action, so we disable the
|
||||
// searchview's default custom filters.
|
||||
action.context.search_disable_custom_filters = true;
|
||||
}
|
||||
if (action.domain) {
|
||||
action.domain = instance.web.pyeval.eval(
|
||||
|
@ -539,7 +582,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
_.each(_.keys(self.views), function(view_name) {
|
||||
var controller = self.views[view_name].controller;
|
||||
if (controller) {
|
||||
var container = self.$el.find(".oe_view_manager_view_" + view_name + ":first");
|
||||
var container = self.$el.find("> .oe_view_manager_body > .oe_view_manager_view_" + view_name);
|
||||
if (view_name === view_type) {
|
||||
container.show();
|
||||
controller.do_show(view_options || {});
|
||||
|
@ -582,7 +625,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
controller.on('switch_mode', self, this.switch_mode);
|
||||
controller.on('previous_view', self, this.prev_view);
|
||||
|
||||
var container = this.$el.find(".oe_view_manager_view_" + view_type);
|
||||
var container = this.$el.find("> .oe_view_manager_body > .oe_view_manager_view_" + view_type);
|
||||
var view_promise = controller.appendTo(container);
|
||||
this.views[view_type].controller = controller;
|
||||
this.views[view_type].deferred.resolve(view_type);
|
||||
|
@ -597,6 +640,12 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
self.trigger("controller_inited",view_type,controller);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @returns {Number|Boolean} the view id of the given type, false if not found
|
||||
*/
|
||||
get_view_id: function(view_type) {
|
||||
return this.views[view_type] && this.views[view_type].view_id || false;
|
||||
},
|
||||
set_title: function(title) {
|
||||
this.$el.find('.oe_view_title_text:first').text(title);
|
||||
},
|
||||
|
@ -953,10 +1002,11 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
|
|||
});
|
||||
},
|
||||
do_create_view: function(view_type) {
|
||||
var r = this._super.apply(this, arguments);
|
||||
var view = this.views[view_type].controller;
|
||||
view.set({ 'title': this.action.name });
|
||||
return r;
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function() {
|
||||
var view = self.views[view_type].controller;
|
||||
view.set({ 'title': self.action.name });
|
||||
});
|
||||
},
|
||||
get_action_manager: function() {
|
||||
var cur = this;
|
||||
|
@ -1270,11 +1320,6 @@ instance.web.View = instance.web.Widget.extend({
|
|||
active_ids: [record_id],
|
||||
active_model: dataset.model
|
||||
});
|
||||
if (("" + action.context).match(/\bactive_id\b/)) {
|
||||
// Special case: when the context is evaluted using
|
||||
// the active_id, we want to disable the custom filters.
|
||||
ncontext.add({ search_disable_custom_filters: true });
|
||||
}
|
||||
}
|
||||
ncontext.add(action.context || {});
|
||||
action.context = ncontext;
|
||||
|
|
|
@ -403,8 +403,9 @@
|
|||
<img class="oe_topbar_avatar" t-att-data-default-src="_s + '/web/static/src/img/user_menu_avatar.png'"/>
|
||||
<span class="oe_topbar_name"/>
|
||||
<ul class="oe_dropdown_menu">
|
||||
<li><a href="#" data-menu="about">About OpenERP</a></li>
|
||||
<li><a href="#" data-menu="settings">Preferences</a></li>
|
||||
<li><a href="#" data-menu="about">About OpenERP</a></li>
|
||||
<li><a href="#" data-menu="help">Help</a></li>
|
||||
<li><a href="#" data-menu="logout">Log out</a></li>
|
||||
</ul>
|
||||
</span>
|
||||
|
|
|
@ -1038,6 +1038,44 @@ openerp.testing.section('saved_filters', {
|
|||
"should not be checked anymore");
|
||||
});
|
||||
});
|
||||
test('toggling', {asserts: 2}, function (instance, $fix, mock) {
|
||||
var view = makeSearchView(instance);
|
||||
mock('ir.filters:get_filters', function () {
|
||||
return [{name: 'filter name', user_id: 42, id: 1}];
|
||||
});
|
||||
|
||||
return view.appendTo($fix)
|
||||
.done(function () {
|
||||
var $row = $fix.find('.oe_searchview_custom li:first').click();
|
||||
equal(view.query.length, 1, "should have one facet");
|
||||
$row.click();
|
||||
equal(view.query.length, 0, "should have removed facet");
|
||||
});
|
||||
});
|
||||
test('replacement', {asserts: 4}, function (instance, $fix, mock) {
|
||||
var view = makeSearchView(instance);
|
||||
mock('ir.filters:get_filters', function () {
|
||||
return [
|
||||
{name: 'f', user_id: 42, id: 1, context: {'private': 1}},
|
||||
{name: 'f', user_id: false, id: 2, context: {'private': 0}}
|
||||
];
|
||||
});
|
||||
return view.appendTo($fix)
|
||||
.done(function () {
|
||||
$fix.find('.oe_searchview_custom li:eq(0)').click();
|
||||
equal(view.query.length, 1, "should have one facet");
|
||||
deepEqual(
|
||||
view.query.at(0).get('field').get_context(),
|
||||
{'private': 1},
|
||||
"should have selected first filter");
|
||||
$fix.find('.oe_searchview_custom li:eq(1)').click();
|
||||
equal(view.query.length, 1, "should have one facet");
|
||||
deepEqual(
|
||||
view.query.at(0).get('field').get_context(),
|
||||
{'private': 0},
|
||||
"should have selected second filter");
|
||||
});
|
||||
});
|
||||
});
|
||||
openerp.testing.section('advanced', {
|
||||
dependencies: ['web.search'],
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
.openerp .oe_calendar .dhx_cal_select_menu .dhx_menu_icon.icon_edit {
|
||||
display: none;
|
||||
}
|
||||
.openerp .oe_calendar .dhx_cal_navline {
|
||||
.openerp .oe_calendar .dhx_cal_navline, .openerp .oe_calendar .dhx_cal_header {
|
||||
z-index: auto;
|
||||
}
|
||||
.openerp .oe_calendar.oe_cal_month .dhx_cal_data table tr td:last-child div.dhx_month_body {
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
// Dhtmlx Scheduler css overrides
|
||||
.dhx_cal_select_menu .dhx_menu_icon.icon_edit
|
||||
display: none
|
||||
.dhx_cal_navline
|
||||
.dhx_cal_navline, .dhx_cal_header
|
||||
// dhtmlx sets the index to 3 (in glossy theme)
|
||||
// I didn't found the reason yet but it overlaps the
|
||||
// dropdown menus and I want to avoid a z-index war.
|
||||
|
|
|
@ -414,6 +414,20 @@ instance.web_calendar.CalendarView = instance.web.View.extend({
|
|||
self.slow_create(event_id, event_obj);
|
||||
});
|
||||
},
|
||||
get_form_popup_infos: function() {
|
||||
var parent = this.getParent();
|
||||
var infos = {
|
||||
view_id: false,
|
||||
title: this.name,
|
||||
};
|
||||
if (parent instanceof instance.web.ViewManager) {
|
||||
infos.view_id = parent.get_view_id('form');
|
||||
if (parent instanceof instance.web.ViewManagerAction && parent.action && parent.action.name) {
|
||||
infos.title = parent.action.name;
|
||||
}
|
||||
}
|
||||
return infos;
|
||||
},
|
||||
slow_create: function(event_id, event_obj) {
|
||||
var self = this;
|
||||
if (this.current_mode() === 'month') {
|
||||
|
@ -431,9 +445,11 @@ instance.web_calendar.CalendarView = instance.web.View.extend({
|
|||
});
|
||||
var something_saved = false;
|
||||
var pop = new instance.web.form.FormOpenPopup(this);
|
||||
var pop_infos = this.get_form_popup_infos();
|
||||
pop.show_element(this.dataset.model, null, this.dataset.get_context(defaults), {
|
||||
title: _t("Create: ") + ' ' + this.name,
|
||||
title: _.str.sprintf(_t("Create: %s"), pop_infos.title),
|
||||
disable_multiple_selection: true,
|
||||
view_id: pop_infos.view_id,
|
||||
});
|
||||
pop.on('closed', self, function() {
|
||||
if (!something_saved) {
|
||||
|
@ -465,9 +481,11 @@ instance.web_calendar.CalendarView = instance.web.View.extend({
|
|||
});
|
||||
} else {
|
||||
var pop = new instance.web.form.FormOpenPopup(this);
|
||||
var pop_infos = this.get_form_popup_infos();
|
||||
var id_from_dataset = this.dataset.ids[index]; // dhtmlx scheduler loses id's type
|
||||
pop.show_element(this.dataset.model, id_from_dataset, this.dataset.get_context(), {
|
||||
title: _t("Edit: ") + this.name
|
||||
title: _.str.sprintf(_t("Edit: %s"), pop_infos.title),
|
||||
view_id: pop_infos.view_id,
|
||||
});
|
||||
pop.on('write_completed', self, function(){
|
||||
self.reload_event(id_from_dataset);
|
||||
|
|
|
@ -92,12 +92,21 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_group_title .oe_kanban_group_length {
|
||||
.openerp .oe_kanban_view .oe_kanban_group_length {
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_group_length .oe_tag {
|
||||
position: relative;
|
||||
top: +8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_fold_column .oe_kanban_group_length {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
right: -14px;
|
||||
text-align: center;
|
||||
float: right;
|
||||
display: block;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_header:hover .oe_kanban_group_length {
|
||||
display: none;
|
||||
|
@ -141,7 +150,8 @@
|
|||
.openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_group_title, .openerp .oe_kanban_view .oe_kanban_group_folded.oe_kanban_column *, .openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_aggregates, .openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_add {
|
||||
display: none;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_group_title_vertical {
|
||||
.openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_group_title_vertical,
|
||||
.openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_group_length {
|
||||
display: block;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_group_folded .oe_dropdown_kanban {
|
||||
|
@ -163,7 +173,7 @@
|
|||
display: none;
|
||||
position: relative;
|
||||
opacity: 0.75;
|
||||
top: 20px;
|
||||
top: 26px;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_add, .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_toggle {
|
||||
margin-left: 4px;
|
||||
|
@ -175,6 +185,7 @@
|
|||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_toggle {
|
||||
top: -2px;
|
||||
height: 14px;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_card, .openerp .oe_kanban_view .oe_dropdown_toggle {
|
||||
cursor: pointer;
|
||||
|
|
|
@ -54,6 +54,13 @@
|
|||
&.oe_kanban_grouped .oe_kanban_dummy_cell
|
||||
background: url(/web/static/src/img/form_sheetbg.png)
|
||||
width: 100%
|
||||
.oe_kanban_group_length
|
||||
text-align: center
|
||||
display: none
|
||||
.oe_tag
|
||||
position: relative
|
||||
top: +8px
|
||||
font-weight: bold
|
||||
.ui-sortable-placeholder
|
||||
border: 1px solid rgba(0,0,0,0.1)
|
||||
visibility: visible !important
|
||||
|
@ -118,11 +125,12 @@
|
|||
white-space: nowrap
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
.oe_fold_column
|
||||
.oe_kanban_group_length
|
||||
position: absolute
|
||||
top: -1px
|
||||
right: -14px
|
||||
text-align: center
|
||||
display: block
|
||||
float: right
|
||||
&.oe_kanban_grouped
|
||||
.oe_kanban_column, .oe_kanban_group_header
|
||||
|
@ -159,7 +167,7 @@
|
|||
.oe_kanban_group_folded
|
||||
.oe_kanban_group_title, &.oe_kanban_column *, .oe_kanban_aggregates, .oe_kanban_add
|
||||
display: none
|
||||
.oe_kanban_group_title_vertical
|
||||
.oe_kanban_group_title_vertical, .oe_kanban_group_length
|
||||
display: block
|
||||
.oe_dropdown_kanban
|
||||
left: -5px
|
||||
|
@ -178,7 +186,7 @@
|
|||
display: none
|
||||
position: relative
|
||||
opacity: 0.75
|
||||
top: 20px
|
||||
top: 26px
|
||||
// }}}
|
||||
// KanbanQuickCreate {{{
|
||||
.oe_kanban_add, .oe_kanban_header .oe_dropdown_toggle
|
||||
|
@ -189,6 +197,7 @@
|
|||
top: -8px
|
||||
.oe_kanban_header .oe_dropdown_toggle
|
||||
top: -2px
|
||||
height: 14px;
|
||||
.oe_kanban_card, .oe_dropdown_toggle
|
||||
cursor: pointer
|
||||
display: inline-block
|
||||
|
|
|
@ -134,7 +134,9 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
|
|||
switch (node.tag) {
|
||||
case 'field':
|
||||
if (this.fields_view.fields[node.attrs.name].type === 'many2many') {
|
||||
this.many2manys.push(node.attrs.name);
|
||||
if (_.indexOf(this.many2manys, node.attrs.name) < 0) {
|
||||
this.many2manys.push(node.attrs.name);
|
||||
}
|
||||
node.tag = 'div';
|
||||
node.attrs['class'] = (node.attrs['class'] || '') + ' oe_form_field oe_tags';
|
||||
} else {
|
||||
|
@ -227,14 +229,15 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
|
|||
this.search_domain = domain;
|
||||
this.search_context = context;
|
||||
this.search_group_by = group_by;
|
||||
$.when(this.has_been_loaded).done(function() {
|
||||
return $.when(this.has_been_loaded).then(function() {
|
||||
self.group_by = group_by.length ? group_by[0] : self.fields_view.arch.attrs.default_group_by;
|
||||
self.group_by_field = self.fields_view.fields[self.group_by] || {};
|
||||
self.grouped_by_m2o = (self.group_by_field.type === 'many2one');
|
||||
self.$buttons.find('.oe_alternative').toggle(self.grouped_by_m2o);
|
||||
self.$el.toggleClass('oe_kanban_grouped_by_m2o', self.grouped_by_m2o);
|
||||
var grouping = new instance.web.Model(self.dataset.model, context, domain).query().group_by(self.group_by);
|
||||
$.when(grouping).done(function(groups) {
|
||||
var grouping_fields = self.group_by ? [self.group_by].concat(_.keys(self.aggregates)) : undefined;
|
||||
var grouping = new instance.web.Model(self.dataset.model, context, domain).query().group_by(grouping_fields);
|
||||
return $.when(grouping).done(function(groups) {
|
||||
if (groups) {
|
||||
self.do_process_groups(groups);
|
||||
} else {
|
||||
|
@ -635,6 +638,7 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
|
|||
self.view.dataset.ids = ids.concat(self.dataset.ids);
|
||||
self.do_add_records(records);
|
||||
self.compute_cards_auto_height();
|
||||
self.view.postprocess_m2m_tags();
|
||||
return records;
|
||||
});
|
||||
},
|
||||
|
|
|
@ -34,16 +34,21 @@
|
|||
<t t-if="widget.view._is_quick_create_enabled()">
|
||||
<div class="oe_kanban_add oe_e" title="Quick create">]</div>
|
||||
</t>
|
||||
<div class="oe_dropdown_toggle oe_dropdown_kanban">
|
||||
<span class="oe_e">í</span>
|
||||
<ul class="oe_dropdown_menu oe_kanban_group_dropdown">
|
||||
<li><a data-action="toggle_fold" href="#">Fold</a></li>
|
||||
<t t-if="widget.view.grouped_by_m2o and widget.value">
|
||||
<li><a data-action="edit" href="#">Edit</a></li>
|
||||
<li><a data-action="delete" href="#">Delete</a></li>
|
||||
</t>
|
||||
</ul>
|
||||
<div class="oe_dropdown_toggle oe_dropdown_kanban">
|
||||
<div class="oe_kanban_group_length">
|
||||
<span class="oe_tag">
|
||||
<t t-if="widget.group.get('length') > 99"> 99+ </t><t t-if="widget.group.get('length') <= 99"> <t t-esc="widget.group.get('length')"/> </t>
|
||||
</span>
|
||||
</div>
|
||||
<span class="oe_e">í</span>
|
||||
<ul class="oe_dropdown_menu oe_kanban_group_dropdown">
|
||||
<li><a data-action="toggle_fold" href="#">Fold</a></li>
|
||||
<t t-if="widget.view.grouped_by_m2o and widget.value">
|
||||
<li><a data-action="edit" href="#">Edit</a></li>
|
||||
<li><a data-action="delete" href="#">Delete</a></li>
|
||||
</t>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="oe_fold_column">
|
||||
<div t-attf-class="oe_kanban_group_title #{widget.undefined_title ? 'oe_kanban_group_title_undefined' : ''}">
|
||||
<div class="oe_kanban_group_length oe_tag">
|
||||
|
@ -57,7 +62,9 @@
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<span t-if="widget.title" class="oe_kanban_group_title_vertical"><t t-esc="widget.title"/></span>
|
||||
<span t-if="widget.title" class="oe_kanban_group_title_vertical">
|
||||
<t t-esc="widget.title"/>
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="! widget.view.group_by && widget.view._is_quick_create_enabled()">
|
||||
|
|
Loading…
Reference in New Issue