Support create record from autocomplete with default values

This commit is contained in:
Cédric Krier
2023-09-26 23:45:07 +02:00
parent 96c2cd16f1
commit ccf653da1d
13 changed files with 118 additions and 50 deletions
+1
View File
@@ -1,3 +1,4 @@
* Support create record from autocomplete
* Use grid layout for form container
* Support scanning code
* Reset search offset when domain has changed
+19 -5
View File
@@ -3740,15 +3740,15 @@
Sao.common.set_overflow(this.input, 'show');
});
},
set_actions: function(action_activated, search, create) {
set_actions: function(action_activated, search=true, create=true) {
if (action_activated !== undefined) {
this.action_activated = action_activated;
}
var actions = [];
if (search || search == undefined) {
if (search) {
actions.push(['search', Sao.i18n.gettext('Search...')]);
}
if (create || create == undefined) {
if (create) {
actions.push(['create', Sao.i18n.gettext('Create...')]);
}
this.menu.find('li.action').remove();
@@ -3840,7 +3840,7 @@
});
Sao.common.get_completion = function(el, source,
match_selected, action_activated, search, create) {
match_selected, action_activated, search=true, create=true) {
var format = function(content) {
return content.name;
};
@@ -3849,6 +3849,7 @@
if (action_activated) {
completion.set_actions(action_activated, search, create);
}
completion._allow_create = create;
return completion;
};
@@ -3867,7 +3868,20 @@
var sao_model = new Sao.Model(model);
return sao_model.execute(
'autocomplete',
[search_text, domain, Sao.config.limit, order], context).fail(function() {
[search_text, domain, Sao.config.limit, order], context)
.then(
function(result) {
return result.filter((value) => {
return value.id || entry.completion._allow_create;
}).map((value) => {
if (value.id === null) {
value.name = Sao.i18n.gettext(
'Create "%1"...', value.name);
}
return value;
});
},
function() {
Sao.Logger.warning(
"Unable to search for completion of %s", model);
});
+21 -5
View File
@@ -2515,6 +2515,7 @@ function eval_pyson(value){
group,
this._update_completion.bind(this),
this._completion_match_selected.bind(this));
this.entry.completion = this.wid_completion;
}
this.el.change(this.focus_out.bind(this));
this._readonly = false;
@@ -2908,9 +2909,13 @@ function eval_pyson(value){
this.entry, record, field, model);
},
_completion_match_selected: function(value) {
this.record.field_set_client(this.field_name,
if (value.id !== null) {
this.record.field_set_client(this.field_name,
this.value_from_id(
value.id, value.rec_name), true);
} else {
this.new_(value.defaults);
}
},
_completion_action_activated: function(action) {
if (action == 'search') {
@@ -3188,6 +3193,7 @@ function eval_pyson(value){
this._completion_match_selected.bind(this),
this._completion_action_activated.bind(this),
this.read_access, this.create_access);
this.wid_text.completion = this.wid_completion;
}
buttons = jQuery('<div/>', {
@@ -3749,8 +3755,12 @@ function eval_pyson(value){
this.wid_text, this.record, this.field, model, domain);
},
_completion_match_selected: function(value) {
this.screen.group.load([value.id], true);
this.wid_text.val('');
if (value.id !== null) {
this.screen.group.load([value.id], true);
this.wid_text.val('');
} else {
this.new_(value.defaults);
}
},
_completion_action_activated: function(action) {
if (action == 'search') {
@@ -3813,6 +3823,7 @@ function eval_pyson(value){
this._completion_match_selected.bind(this),
this._completion_action_activated.bind(this),
this.read_access, this.create_access);
this.entry.completion = this.wid_completion;
}
var buttons = jQuery('<div/>', {
@@ -4106,8 +4117,12 @@ function eval_pyson(value){
this.entry, this.record, this.field, model, domain);
},
_completion_match_selected: function(value) {
this.screen.group.load([value.id], true);
this.entry.val('');
if (value.id !== null) {
this.screen.group.load([value.id], true);
this.entry.val('');
} else {
this.new_(value.defaults);
}
},
_completion_action_activated: function(action) {
if (action == 'search') {
@@ -4754,6 +4769,7 @@ function eval_pyson(value){
group,
this._update_completion.bind(this),
this._completion_match_selected.bind(this));
this.wid_text.completion = this.wid_completion;
}
this.but_add = jQuery('<button/>', {
+1
View File
@@ -1,3 +1,4 @@
* Support create record from autocomplete
* Support scanning code
* Reset search offset when domain has changed
* Add a canonicalize function for domains
+11 -3
View File
@@ -17,13 +17,14 @@ def get_completion(search=True, create=True):
"Return a EntryCompletion"
completion = Gtk.EntryCompletion()
completion.set_match_func(lambda *a: True)
completion.set_model(Gtk.ListStore(str, int))
completion.set_model(Gtk.ListStore(str, object, object))
completion.set_text_column(0)
completion.props.popup_set_width = False
if search:
completion.insert_action_markup(0, _('<i>Search...</i>'))
if create:
completion.insert_action_markup(1, _('<i>Create...</i>'))
completion._allow_create = create
return completion
@@ -34,7 +35,8 @@ def update_completion(entry, record, field, model, domain=None):
return False
if search_text != entry.get_text():
return False
completion_model = entry.get_completion().get_model()
completion = entry.get_completion()
completion_model = completion.get_model()
if not search_text or not model:
completion_model.clear()
completion_model.search_text = search_text
@@ -55,7 +57,13 @@ def update_completion(entry, record, field, model, domain=None):
return False
completion_model.clear()
for result in results:
completion_model.append([result['name'], result['id']])
if result['id'] is not None or completion._allow_create:
if result['id'] is None:
name = _('Create "%s"...') % result['name']
else:
name = result['name']
completion_model.append((
name, result['id'], result['defaults']))
completion_model.search_text = search_text
# Force display of popup
entry.emit('changed')
@@ -307,14 +307,17 @@ class Many2Many(Widget):
return True
def _completion_match_selected(self, completion, model, iter_):
record_id, = model.get(iter_, 1)
self.screen.load([record_id], modified=True)
self.wid_text.set_text('')
self.wid_text.grab_focus()
record_id, defaults = model.get(iter_, 1, 2)
if record_id is not None:
self.screen.load([record_id], modified=True)
self.wid_text.set_text('')
self.wid_text.grab_focus()
completion_model = self.wid_completion.get_model()
completion_model.clear()
completion_model.search_text = self.wid_text.get_text()
completion_model = self.wid_completion.get_model()
completion_model.clear()
completion_model.search_text = self.wid_text.get_text()
else:
self._sig_new(defaults)
return True
def _update_completion(self, widget):
@@ -347,15 +347,18 @@ class Many2One(Widget):
self.wid_text.set_completion(self.wid_completion)
def _completion_match_selected(self, completion, model, iter_):
rec_name, record_id = model.get(iter_, 0, 1)
# GTK on win32 doesn't like synchronous call to set_client
# because it triggers a display which reset the completion
GLib.idle_add(self.field.set_client, self.record,
self.value_from_id(record_id, rec_name), True)
rec_name, record_id, defaults = model.get(iter_, 0, 1, 2)
if record_id is not None:
# GTK on win32 doesn't like synchronous call to set_client
# because it triggers a display which reset the completion
GLib.idle_add(self.field.set_client, self.record,
self.value_from_id(record_id, rec_name), True)
completion_model = self.wid_completion.get_model()
completion_model.clear()
completion_model.search_text = rec_name
completion_model = self.wid_completion.get_model()
completion_model.clear()
completion_model.search_text = rec_name
else:
self.sig_new(defaults)
return True
def _update_completion(self, widget):
@@ -564,14 +564,17 @@ class One2Many(Widget):
return True
def _completion_match_selected(self, completion, model, iter_):
record_id, = model.get(iter_, 1)
self.screen.load([record_id], modified=True)
self.wid_text.set_text('')
self.wid_text.grab_focus()
record_id, defaults = model.get(iter_, 1, 2)
if record_id is not None:
self.screen.load([record_id], modified=True)
self.wid_text.set_text('')
self.wid_text.grab_focus()
completion_model = self.wid_completion.get_model()
completion_model.clear()
completion_model.search_text = self.wid_text.get_text()
completion_model = self.wid_completion.get_model()
completion_model.clear()
completion_model.search_text = self.wid_text.get_text()
else:
self._sig_new(defaults)
return True
def _update_completion(self, widget):
@@ -979,14 +979,25 @@ class M2O(GenericText):
def _completion_match_selected(
self, completion, model, iter_, record, field, model_name):
rec_name, record_id = model.get(iter_, 0, 1)
field.set_client(
record, self.value_from_id(model_name, record_id, rec_name))
rec_name, record_id, defaults = model.get(iter_, 0, 1, 2)
if record_id is not None:
field.set_client(
record, self.value_from_id(model_name, record_id, rec_name))
completion.get_entry().set_text(rec_name)
completion_model = completion.get_model()
completion_model.clear()
completion_model.search_text = rec_name
completion.get_entry().set_text(rec_name)
completion_model = completion.get_model()
completion_model.clear()
completion_model.search_text = rec_name
else:
entry = completion.get_entry()
entry.handler_block(entry.editing_done_id)
def callback():
entry.handler_unblock(entry.editing_done_id)
entry.set_text(field.get_client(record))
self.open_remote(
record, create=True, text=entry.get_text(), defaults=defaults,
callback=callback)
return True
def _update_completion(self, entry, record, field):
+1
View File
@@ -1,3 +1,4 @@
* Support default values for autocomplete
* Support default value from context
* Add autocomplete method on ModelView
* Add likify tools
+5 -1
View File
@@ -245,10 +245,14 @@ Instance methods:
.. method:: ModelView.autocomplete(text[, domain[, limit[, order]]])
Return a list of dictionary for records completing ``text``.
The dictionary is composed of ``id`` and ``name``.
The dictionary is composed of ``id``, ``name`` and ``defaults``.
The client call this method to provide completion.
.. note::
The ``defaults`` is used when ``id`` is empty to get the default values
for the record to create.
ModelStorage
============
+3 -1
View File
@@ -813,7 +813,9 @@ class ModelView(Model):
if issubclass(cls, ModelStorage):
result = cls.search_read(
domain, limit=limit, order=order, fields_names=['rec_name'])
result = [{'id': r['id'], 'name': r['rec_name']} for r in result]
result = [
{'id': r['id'], 'name': r['rec_name'], 'defaults': None}
for r in result]
return result
@property
+7 -6
View File
@@ -734,15 +734,16 @@ class ModelView(unittest.TestCase):
self.assertEqual(Model.autocomplete('test'), [])
self.assertEqual(
Model.autocomplete('foo'),
[{'name': 'foo', 'id': foo.id}])
[{'name': 'foo', 'id': foo.id, 'defaults': None}])
self.assertEqual(
Model.autocomplete(''), [
{'name': 'foo', 'id': foo.id},
{'name': 'bar', 'id': bar.id}])
{'name': 'foo', 'id': foo.id, 'defaults': None},
{'name': 'bar', 'id': bar.id, 'defaults': None}])
self.assertEqual(Model.autocomplete('foo', [('id', '!=', foo.id)]), [])
self.assertEqual(
Model.autocomplete('', limit=1), [{'name': 'foo', 'id': foo.id}])
Model.autocomplete('', limit=1),
[{'name': 'foo', 'id': foo.id, 'defaults': None}])
self.assertEqual(
Model.autocomplete('', order=[('name', 'ASC')]), [
{'name': 'bar', 'id': bar.id},
{'name': 'foo', 'id': foo.id}])
{'name': 'bar', 'id': bar.id, 'defaults': None},
{'name': 'foo', 'id': foo.id, 'defaults': None}])