[MERGE] Merge lp:~openerp-web.

bzr revid: bth@tinyerp.com-20120510055102-yoo8pbim46q5fp6d
bzr revid: bth@tinyerp.com-20120514092121-7uz6efn91hkfuz93
This commit is contained in:
Bhumi Thakkar (Open ERP)
2012-05-14 14:51:21 +05:30
10 changed files with 5372 additions and 99 deletions
+1
View File
@@ -29,6 +29,7 @@
"static/lib/jquery.deferred-queue/jquery.deferred-queue.js",
"static/lib/jquery.scrollTo/jquery.scrollTo-min.js",
"static/lib/jquery.tipsy/jquery.tipsy.js",
"static/lib/jquery.textext/jquery.textext.js",
"static/lib/json/json2.js",
"static/lib/qweb/qweb2.js",
"static/lib/underscore/underscore.js",
+7 -1
View File
@@ -1818,9 +1818,15 @@ class Reports(View):
report = zlib.decompress(report)
report_mimetype = self.TYPES_MAPPING.get(
report_struct['format'], 'octet-stream')
if 'name' not in action:
reports = req.session.model('ir.actions.report.xml')
res_id = reports.search([('report_name', '=',action['report_name']),],
0, False, False, context)
action['name'] = reports.read(res_id, ['name'], context)[0]['name']
return req.make_response(report,
headers=[
('Content-Disposition', 'attachment; filename="%s.%s"' % (action['report_name'], report_struct['format'])),
('Content-Disposition', 'attachment; filename="%s.%s"' % (action['name'], report_struct['format'])),
('Content-Type', report_mimetype),
('Content-Length', len(report))],
cookies={'fileToken': int(token)})
File diff suppressed because it is too large Load Diff
+24
View File
@@ -1461,6 +1461,30 @@
.openerp .oe_form .oe_form_field_datetime input {
min-width: 11em;
}
.openerp .oe_form .oe_form_field_many2manytags .text-wrap {
width: 100% !important;
}
.openerp .oe_form .oe_form_field_many2manytags .text-wrap textarea {
width: 100% !important;
}
.openerp .oe_form .oe_form_field_many2manytags .oe_form_field_many2manytags_box {
border-radius: 2px;
box-sizing: border-box;
position: relative;
float: left;
border: 1px solid #9daccc;
background: #e2e6f0;
color: black;
padding: 0px 3px 0px 3px;
margin: 0 2px 2px 0;
cursor: pointer;
height: 16px;
font: 11px "lucida grande", tahoma, verdana, arial, sans-serif;
}
.openerp .oe_form .oe_form_field_many2manytags .text-core .text-wrap .text-dropdown .text-list .text-suggestion em {
font-style: italic;
text-decoration: none;
}
.openerp .oe_form .oe_datepicker_container {
display: none;
}
+21
View File
@@ -1211,6 +1211,27 @@ $colour4: #8a89ba
padding-top: 4px
.oe_form_field_datetime input
min-width: 11em
.oe_form_field_many2manytags
.text-wrap
width: 100% !important
textarea
width: 100% !important
.oe_form_field_many2manytags_box
border-radius: 2px
box-sizing: border-box
position: relative
float: left
border: 1px solid #9DACCC
background: #E2E6F0
color: black
padding: 0px 3px 0px 3px
margin: 0 2px 2px 0
cursor: pointer
height: 16px
font: 11px "lucida grande", tahoma, verdana, arial, sans-serif
.text-core .text-wrap .text-dropdown .text-list .text-suggestion em
font-style: italic
text-decoration: none
.oe_datepicker_container
display: none
.oe_datepicker_root
+270 -94
View File
@@ -1118,8 +1118,6 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
// invisibility transfer
var field_modifiers = JSON.parse($child.attr('modifiers') || '{}');
var invisible = field_modifiers.invisible;
field_modifiers.invisible = undefined;
$child.attr('modifiers', JSON.stringify(field_modifiers));
self.handle_common_properties($td, $("<dummy>").attr("modifiers", JSON.stringify({invisible: invisible})));
$tr.append($td.append($child));
@@ -1371,17 +1369,27 @@ instance.web.form.InvisibilityChangerMixin = {
instance.web.form.compute_domain(this._ic_invisible_modifier, this._ic_field_manager.fields);
this.set({"invisible": result});
});
this.set({invisible: this._ic_invisible_modifier === true});
this.set({invisible: this._ic_invisible_modifier === true, force_invisible: false});
var check = function() {
if (this.get("invisible") || this.get('force_invisible')) {
this.set({"effective_invisible": true});
} else {
this.set({"effective_invisible": false});
}
};
this.on('change:invisible', this, check);
this.on('change:force_invisible', this, check);
_.bind(check, this)();
},
start: function() {
var check_visibility = function() {
if (this.get("invisible")) {
if (this.get("effective_invisible")) {
this.$element.hide();
} else {
this.$element.show();
}
};
this.on("change:invisible", this, check_visibility);
this.on("change:effective_invisible", this, check_visibility);
_.bind(check_visibility, this)();
},
};
@@ -1827,7 +1835,6 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(_.extend({}
init: function (field_manager, node) {
this._super(field_manager, node);
this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1';
this.placeholder = this.node.attrs.placeholder || '';
},
initialize_content: function() {
var self = this;
@@ -2305,17 +2312,113 @@ instance.web.form.dialog = function(content, options) {
return dialog.$element;
};
instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, {
/**
* A mixin containing some useful methods to handle completion inputs.
*/
instance.web.form.CompletionFieldMixin = {
init: function() {
this.limit = 7;
this.orderer = new instance.web.DropMisordered();
},
/**
* Call this method to search using a string.
*/
get_search_result: function(search_val) {
var self = this;
var dataset = new instance.web.DataSet(this, this.field.relation, self.build_context());
return this.orderer.add(dataset.name_search(
search_val, self.build_domain(), 'ilike', this.limit + 1)).pipe(function(data) {
self.last_search = data;
// possible selections for the m2o
var values = _.map(data, function(x) {
return {
label: _.str.escapeHTML(x[1]),
value:x[1],
name:x[1],
id:x[0]
};
});
// search more... if more results that max
if (values.length > self.limit) {
values = values.slice(0, self.limit);
values.push({label: _t("<em>   Search More...</em>"), action: function() {
dataset.name_search(search_val, self.build_domain(), 'ilike'
, false, function(data) {
self._search_create_popup("search", data);
});
}});
}
// quick create
var raw_result = _(data.result).map(function(x) {return x[1];});
if (search_val.length > 0 && !_.include(raw_result, search_val)) {
values.push({label: _.str.sprintf(_t('<em>   Create "<strong>%s</strong>"</em>'),
$('<span />').text(search_val).html()), action: function() {
self._quick_create(search_val);
}});
}
// create...
values.push({label: _t("<em>   Create and Edit...</em>"), action: function() {
self._search_create_popup("form", undefined, {"default_name": search_val});
}});
return values;
});
},
_quick_create: function(name) {
var self = this;
var slow_create = function () {
self._search_create_popup("form", undefined, {"default_name": name});
};
if (self.get_definition_options().quick_create === undefined || self.get_definition_options().quick_create) {
new instance.web.DataSet(this, this.field.relation, self.build_context())
.name_create(name, function(data) {
self.add_id(data[0]);
}).fail(function(error, event) {
event.preventDefault();
slow_create();
});
} else
slow_create();
},
// all search/create popup handling
_search_create_popup: function(view, ids, context) {
var self = this;
var pop = new instance.web.form.SelectCreatePopup(this);
pop.select_element(
self.field.relation,
{
title: (view === 'search' ? _t("Search: ") : _t("Create: ")) + (this.string || this.name),
initial_ids: ids ? _.map(ids, function(x) {return x[0]}) : undefined,
initial_view: view,
disable_multiple_selection: true
},
self.build_domain(),
new instance.web.CompoundContext(self.build_context(), context || {})
);
pop.on_select_elements.add(function(element_ids) {
self.add_id(element_ids[0]);
});
},
/**
* To implement.
*/
add_id: function(id) {},
};
instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin,
instance.web.form.CompletionFieldMixin, {
template: "FieldMany2One",
init: function(field_manager, node) {
this._super(field_manager, node);
this.limit = 7;
instance.web.form.CompletionFieldMixin.init.call(this);
this.set({'value': false});
this.display_value = {};
this.last_search = [];
this.floating = false;
this.inhibit_on_change = false;
this.orderer = new instance.web.DropMisordered();
},
start: function() {
this._super();
@@ -2427,7 +2530,11 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten
var isSelecting = false;
// autocomplete
this.$input.autocomplete({
source: function(req, resp) { self.get_search_result(req, resp); },
source: function(req, resp) {
self.get_search_result(req.term).then(function(result) {
resp(result);
});
},
select: function(event, ui) {
isSelecting = true;
var item = ui.item;
@@ -2459,91 +2566,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten
isSelecting = false;
});
},
// autocomplete component content handling
get_search_result: function(request, response) {
var search_val = request.term;
var self = this;
var dataset = new instance.web.DataSet(this, this.field.relation, self.build_context());
this.orderer.add(dataset.name_search(
search_val, self.build_domain(), 'ilike', this.limit + 1)).then(function(data) {
self.last_search = data;
// possible selections for the m2o
var values = _.map(data, function(x) {
return {
label: _.str.escapeHTML(x[1]),
value:x[1],
name:x[1],
id:x[0]
};
});
// search more... if more results that max
if (values.length > self.limit) {
values = values.slice(0, self.limit);
values.push({label: _t("<em>   Search More...</em>"), action: function() {
dataset.name_search(search_val, self.build_domain(), 'ilike'
, false, function(data) {
self._search_create_popup("search", data);
});
}});
}
// quick create
var raw_result = _(data.result).map(function(x) {return x[1];});
if (search_val.length > 0 &&
!_.include(raw_result, search_val) &&
(!self.get("value") || self.floating)) {
values.push({label: _.str.sprintf(_t('<em>   Create "<strong>%s</strong>"</em>'),
$('<span />').text(search_val).html()), action: function() {
self._quick_create(search_val);
}});
}
// create...
values.push({label: _t("<em>   Create and Edit...</em>"), action: function() {
self._search_create_popup("form", undefined, {"default_name": search_val});
}});
response(values);
});
},
_quick_create: function(name) {
var self = this;
var slow_create = function () {
self._search_create_popup("form", undefined, {"default_name": name});
};
if (self.get_definition_options().quick_create === undefined || self.get_definition_options().quick_create) {
new instance.web.DataSet(this, this.field.relation, self.build_context())
.name_create(name, function(data) {
self.display_value = {};
self.display_value["" + data[0]] = data[1];
self.set({value: data[0]});
}).fail(function(error, event) {
event.preventDefault();
slow_create();
});
} else
slow_create();
},
// all search/create popup handling
_search_create_popup: function(view, ids, context) {
var self = this;
var pop = new instance.web.form.SelectCreatePopup(this);
pop.select_element(
self.field.relation,
{
title: (view === 'search' ? _t("Search: ") : _t("Create: ")) + (this.string || this.name),
initial_ids: ids ? _.map(ids, function(x) {return x[0]}) : undefined,
initial_view: view,
disable_multiple_selection: true
},
self.build_domain(),
new instance.web.CompoundContext(self.build_context(), context || {})
);
pop.on_select_elements.add(function(element_ids) {
self.set({value: element_ids[0]});
});
},
render_value: function(no_recurse) {
var self = this;
if (! this.get("value")) {
@@ -2596,6 +2619,10 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten
this._super(value_);
this.inhibit_on_change = false;
},
add_id: function(id) {
this.display_value = {};
this.set({value: id});
},
is_false: function() {
return ! this.get("value");
},
@@ -3037,6 +3064,154 @@ instance.web.form.One2ManyKanbanView = instance.web_kanban.KanbanView.extend({
});
}
instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.CompletionFieldMixin,
instance.web.form.ReinitializeFieldMixin, {
template: "FieldMany2ManyTags",
init: function() {
this._super.apply(this, arguments);
instance.web.form.CompletionFieldMixin.init.call(this);
this.set({"value": []});
this._display_orderer = new instance.web.DropMisordered();
this._drop_shown = false;
},
start: function() {
this._super();
instance.web.form.ReinitializeFieldMixin.start.call(this);
this.on("change:value", this, this.render_value);
},
initialize_content: function() {
if (this.get("effective_readonly"))
return;
var self = this;
self. $text = $("textarea", this.$element);
self.$text.textext({
plugins : 'tags arrow autocomplete',
autocomplete: {
render: function(suggestion) {
return $('<span class="text-label"/>').
data('index', suggestion['index']).html(suggestion['label']);
}
},
ext: {
autocomplete: {
selectFromDropdown: function() {
$(this).trigger('hideDropdown');
var index = Number(this.selectedSuggestionElement().children().children().data('index'));
var data = self.search_result[index];
if (data.id) {
self.add_id(data.id);
} else {
data.action();
}
},
},
tags: {
isTagAllowed: function(tag) {
if (! tag.name)
return false;
return true;
},
removeTag: function(tag) {
var id = tag.data("id");
self.set({"value": _.without(self.get("value"), id)});
},
renderTag: function(stuff) {
return $.fn.textext.TextExtTags.prototype.renderTag.
call(this, stuff).data("id", stuff.id);
},
},
itemManager: {
itemToString: function(item) {
return item.name;
},
},
},
}).bind('getSuggestions', function(e, data) {
var _this = this;
var str = !!data ? data.query || '' : '';
self.get_search_result(str).then(function(result) {
self.search_result = result;
$(_this).trigger('setSuggestions', {result : _.map(result, function(el, i) {
return _.extend(el, {index:i});
})});
});
}).bind('tagClick', function(e, tag, value, callback) {
var pop = new instance.web.form.FormOpenPopup(self.view);
pop.show_element(
self.field.relation,
value.id,
self.build_context(),
{
title: _t("Open: ") + (self.string || self.name)
}
);
pop.on_write_completed.add_last(function() {
self.render_value();
});
}).bind('hideDropdown', function() {
self._drop_shown = false;
}).bind('hideDropdown', function() {
self._drop_shown = true;
});
self.tags = self.$text.textext()[0].tags();
$("textarea", this.$element).focusout(function() {
$("textarea", this.$element).val("");
}).keydown(function(e) {
if (event.keyCode === 9 && self._drop_shown) {
self.$text.textext()[0].autocomplete().selectFromDropdown();
}
});
},
set_value: function(value_) {
value_ = value_ || [];
if (value_.length >= 1 && value_[0] instanceof Array) {
value_ = value_[0][2];
}
this._super(value_);
},
get_value: function() {
var tmp = [commands.replace_with(this.get("value"))];
return tmp;
},
render_value: function() {
var self = this;
var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.view.dataset.get_context());
var handle_names = function(data) {
var indexed = {};
_.each(data, function(el) {
indexed[el[0]] = el;
});
data = _.map(self.get("value"), function(el) { return indexed[el]; });
if (! self.get("effective_readonly")) {
self.tags.containerElement().children().remove();
$("textarea", self.$element).css("padding-left", "3px");
self.tags.addTags(_.map(data, function(el) {return {name: el[1], id:el[0]};}));
} else {
self.$element.html(QWeb.render("FieldMany2ManyTags.box", {elements: data}));
$(".oe_form_field_many2manytags_box", self.$element).click(function() {
var index = Number($(this).data("index"));
self.do_action({
type: 'ir.actions.act_window',
res_model: self.field.relation,
res_id: self.get("value")[index],
context: self.build_context(),
views: [[false, 'form']],
target: 'current'
});
});
}
};
if (! self.get('values') || self.get('values').length > 0) {
this._display_orderer.add(dataset.name_get(self.get("value"))).then(handle_names);
} else {
handle_names([]);
}
},
add_id: function(id) {
this.set({'value': _.uniq(this.get('value').concat([id]))});
},
}));
/*
* TODO niv: clean those deferred stuff, it could be better
*/
@@ -3824,6 +3999,7 @@ instance.web.form.widgets = new instance.web.Registry({
'selection' : 'instance.web.form.FieldSelection',
'many2one' : 'instance.web.form.FieldMany2One',
'many2many' : 'instance.web.form.FieldMany2Many',
'many2manytags' : 'instance.web.form.FieldMany2ManyTags',
'one2many' : 'instance.web.form.FieldOne2Many',
'one2many_list' : 'instance.web.form.FieldOne2Many',
'reference' : 'instance.web.form.FieldReference',
+22 -1
View File
@@ -917,7 +917,7 @@
t-att-tabindex="widget.node.attrs.tabindex"
t-att-autofocus="widget.node.attrs.autofocus"
t-attf-class="field_char #{widget.is_field_number ? 'oe-number' : ''}"
t-att-placeholder="widget.placeholder"
t-att-placeholder="widget.node.attrs.placeholder"
/><img class="oe_field_translate oe_input_icon" t-if="widget.field.translate" t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"/>
</div>
</t>
@@ -934,6 +934,7 @@
t-att-id="widget.id_for_label"
t-att-tabindex="widget.node.attrs.tabindex"
t-att-autofocus="widget.node.attrs.autofocus"
t-att-placeholder="widget.node.attrs.placeholder"
/>
</td>
<td width="16">
@@ -959,6 +960,7 @@
t-att-id="widget.id_for_label"
t-att-tabindex="widget.node.attrs.tabindex"
t-att-autofocus="widget.node.attrs.autofocus"
t-att-placeholder="widget.node.attrs.placeholder"
/>
</td>
<td width="16">
@@ -982,6 +984,7 @@
class="field_text"
t-att-tabindex="widget.node.attrs.tabindex"
t-att-autofocus="widget.node.attrs.autofocus"
t-att-placeholder="widget.node.attrs.placeholder"
></textarea>
<img class="oe_field_translate oe_input_icon" t-if="widget.field.translate"
t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"/>
@@ -992,6 +995,7 @@
<input type="text" class="oe_datepicker_container" disabled="disabled" style="display: none;"/>
<input type="text"
t-att-name="widget.name"
t-att-placeholder="widget.getParent().node.attrs.placeholder"
t-attf-class="oe_datepicker_master field_#{widget.type_of_date} oe_form_field_#{widget.type_of_date}"
/><img class="oe_input_icon oe_datepicker_trigger" t-att-src='_s + "/web/static/src/img/ui/field_calendar.png"'
title="Select date" width="16" height="16" border="0"/>
@@ -1024,6 +1028,7 @@
t-att-id="widget.id_for_label"
t-att-tabindex="widget.node.attrs.tabindex"
t-att-autofocus="widget.node.attrs.autofocus"
t-att-placeholder="widget.node.attrs.placeholder"
/>
<span class="oe-m2o-drop-down-button">
<img t-att-src='_s + "/web/static/src/img/down-arrow.png"' /></span>
@@ -1042,6 +1047,22 @@
</span>
</t>
</t>
<t t-name="FieldMany2ManyTags">
<div class="oe_form_field_many2manytags">
<t t-if="! widget.get('effective_readonly')">
<textarea rows="1" style="width: 100%"></textarea>
</t>
</div>
</t>
<t t-name="FieldMany2ManyTags.box">
<t t-set="i" t-value="0"/>
<t t-foreach="elements" t-as="el">
<span class="oe_form_field_many2manytags_box" t-att-data-index="i">
<t t-esc="el[1]"/>
</span>
<t t-set="i" t-value="i + 1"/>
</t>
</t>
<t t-name="FieldReference">
<table class="oe_form_field_reference oe_frame oe_forms" border="0" cellpadding="0" cellspacing="0">
<tr>
@@ -224,6 +224,30 @@
line-height: 1em;
}
.openerp .oe_kanban_add {
cursor: pointer;
position: absolute;
top: 6px;
right: 6px;
width: 16px;
height: 16px;
background: url(/web_kanban/static/src/img/plus-icon.png) no-repeat;
}
.openerp .oe_kanban_quick_create {
/* apply block formatting context */
overflow: hidden;
}
.openerp .oe_kanban_quick_create input {
display: block;
/* margins within width */
box-sizing: border-box;
width: 100%;
}
.openerp .oe_kanban_quick_create button {
float: right;
}
/* Custom colors are also present in kanban.js */
/* Custom color#0 */
.openerp .oe_kanban_color_0 .oe_kanban_color_bglight {
+68 -3
View File
@@ -348,6 +348,12 @@ instance.web_kanban.KanbanGroup = instance.web.OldWidget.extend({
self.view.compute_groups_width();
return false;
});
this.$element.find('.oe_kanban_add').click(function () {
if (self.quick) { return; }
self.quick = new instance.web_kanban.QuickCreate(this)
.on('added', self, self.proxy('quick_add'));
self.quick.appendTo(self.$element.find('.oe_kanban_group_header'));
});
this.$records.find('.oe_kanban_show_more').click(this.do_show_more);
if (this.state.folded) {
this.do_toggle_fold();
@@ -370,12 +376,17 @@ instance.web_kanban.KanbanGroup = instance.web.OldWidget.extend({
'offset': self.dataset_offset += self.view.limit
}).then(this.do_add_records);
},
do_add_records: function(records) {
do_add_records: function(records, prepend) {
var self = this;
_.each(records, function(record) {
var rec = new instance.web_kanban.KanbanRecord(self, record);
rec.insertBefore(self.$records.find('.oe_kanban_show_more'));
self.records.push(rec);
if (!prepend) {
rec.insertBefore(self.$records.find('.oe_kanban_show_more'));
self.records.push(rec);
} else {
rec.prependTo(self.$records);
self.records.unshift(rec);
}
});
this.$records.find('.oe_kanban_show_more').toggle(this.records.length < this.dataset.size())
.find('.oe_kanban_remaining').text(this.dataset.size() - this.records.length);
@@ -398,6 +409,36 @@ instance.web_kanban.KanbanGroup = instance.web.OldWidget.extend({
self.view.dataset.write(record.id, { sequence : index });
});
}
},
/**
* Handles user event from nested quick creation view
*
* @param {String} name name to give to the new record
*/
quick_add: function (name) {
var context = {};
context['default_' + this.view.group_by] = this.value;
// FIXME: what if name_create fails?
new instance.web.Model(this.dataset.model).call(
'name_create', [name], {context: new instance.web.CompoundContext(
this.dataset.get_context(), context)})
.then(this.proxy('quick_created'))
},
/**
* Handles a non-erroneous response from name_create
*
* @param {(Id, String)} record name_get format for the newly created record
*/
quick_created: function (record) {
var id = record[0], self = this;
this.quick.destroy();
delete this.quick;
new instance.web.Model(this.dataset.model).call(
'read', [[id], this.view.fields_keys], {})
.then(function (records) {
self.view.dataset.ids.push(id);
self.do_add_records(records, 'prepend');
});
}
});
@@ -603,6 +644,30 @@ instance.web_kanban.KanbanRecord = instance.web.OldWidget.extend({
}
}
});
/**
* Quick creation view.
*
* Triggers a single event "added" with a single parameter "name", which is the
* name entered by the user
*
* @class
* @type {*}
*/
instance.web_kanban.QuickCreate = instance.web.Widget.extend({
template: 'KanbanView.quick_create',
start: function () {
var self = this;
var $input = this.$element.find('input');
$input.focus();
this.$element.on('submit', function () {
self.trigger('added', $input.val());
return false;
});
return this._super();
}
});
};
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
@@ -20,6 +20,7 @@
<t t-if="widget.view.group_by">
<div class="oe_kanban_group_header">
<div class="oe_kanban_fold_icon"></div>
<div class="oe_kanban_add"></div>
<div class="oe_fold_column">
<div t-attf-class="oe_kanban_group_title #{widget.undefined_title ? 'oe_kanban_group_title_undefined' : ''}">
<t t-esc="widget.title"/>
@@ -62,4 +63,8 @@
</tr>
</table>
</t>
<form t-name="KanbanView.quick_create" class="oe_kanban_quick_create">
<input/>
<button type="submit">Add</button>
</form>
</template>