Reputation: 55
In flask admin, I have an inline model with a Select2 field named 'Type' and a text input field named 'URL'.
When the user selects a value in the 'Type' field, I want the value of the 'URL' field to be dynamically updated with predefined and mapped values.
For example: if the chosen 'Type' is 'Wikidata', I would like to prepopulate the 'URL' field with 'https://www.wikidata.org/wiki/' or if the chosen 'Type' is 'DataBnf', the 'URL' field with 'https://data.bnf.fr/fr/" etc.
I've tried using jQuery to intercept the selection event (select2), but it doesn't seem to work. I'm wondering if there's a simpler method in Flask-Admin to prefill a field based on dropdown list selection.
thanks in advance for your suggestions
[Edit 1]
There is my model view :
views.py
(simplified version):
class PersonView(ModelView):
can_export = True
can_view_details = False
column_display_pk = True
create_template = 'admin/edit.html'
edit_template = 'admin/edit.html'
#...
#... some formaters functions for specific fields ...
extra_js = ['//cdn.ckeditor.com/4.6.0/basic/ckeditor.js']
#...
inline_models = [
(
# This inline model I try to customize
PersonHasKbLinks,
dict(
form_columns=["id", "type_kb", "url"],
column_labels={"type_kb": "Type", "url": "URL"},
)
),
# ... others inline models
]
# ...
form_choices = {
'type_kb': format_enum(KnowledgeBaseLabels),
'relation_type': format_enum(FamilyRelationshipLabels)
}
#...
edit.html
:
{% extends 'admin/model/edit.html' %}
{% block tail_js %}
{{ super() }}
<script type="text/javascript" src="{{ url_for('static', filename='js/_prefill_kb_url.js') }}"></script>
{% endblock %}
master.html
:
{% extends admin_base_template %}
{% block head %}
{{ super() }}
<!-- add jquery -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js" integrity="sha512-3gJwYpMe3QewGELv8k/BX9vcqhryRdzRMxVfq6ngyWXwo03GFEzjsUm8Q7RZcHPHksttq7/GFoxjCVUjkjvPdw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- add select2 dependencies -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js"></script>
{% endblock %}
_prefill_kb_url.js
(at the moment I'm just trying to catch events (change or select) on select2 list but i'm failing and i'm not sure if this is the best strategy to do what I want in flask admin):
// I try this (no output) :
$(document).ready(function() {
$('#s2id_kb_links-0-type_kb').on('change', function() {
console.log('select change');
});
});
// and I try this (no output also) :
$(document).ready(function() {
$('body').on('select2:select', '#s2id_kb_links-0-type_kb', function (e) {
console.log('select change');
}
});
});
I noticed in my browser, that a select element (id="kb_links-0-type_kb"
) was created but it defaults with CSS property display:none
, as if flask-admin overide select2(maybe I'm wrong...?)
Upvotes: 0
Views: 558
Reputation: 55
After some research, I finally found a solution:
First of all, in views.py
, I create a custom SelectField
:
class Select2DynamicField(SelectField):
def __init__(self, label=None, validators=None, coerce=int, choices=None, **kwargs):
super(Select2DynamicField, self).__init__(label, validators, coerce, choices, **kwargs)
choices = [(label, label) for i, label in enumerate(_get_enum_values(KnowledgeBaseLabels))]
coerce = str
kwargs['widget'] = Select2Widget()
kwargs['render_kw'] = {'onchange': 'fetchCorrectUrlStringFromKbSelect(this)'} # link a JQuery function here
return super(Select2DynamicField, self).__init__(label, validators, coerce, choices, **kwargs)
always in views.py
, in my model PersonView
, I link my new custom SelectField
to my field in inline_models
:
class PersonView(ModelView):
edit_template = 'admin/edit.html'
create_template = 'admin/edit.html'
...
inline_models = [
(PersonHasKbLinks, {
'form_columns': ['type_kb', 'url'],
'column_labels': {'type_kb': 'Base de connaissance', 'url': 'URL'},
'form_overrides': dict(type_kb=Select2DynamicField),
'form_args': dict(url=dict(default='www.wikidata.org/entity/<ID>')) # here I set a default value for url,
}), ...
]
...
Then I write a specific function in the JS script person.form.fields.js
to dynamically replace the correct value in the url
field when the user selects a value in the type_kb
field :
// Collections of KB URLs
const KB_URL_MAP = {
"Wikidata": "www.wikidata.org/entity/<ID>",
"Biblissima": "data.biblissima.fr/w/Item:<ID>",
"VIAF": "www.viaf.org/viaf/<ID>/",
"DataBnF": "data.bnf.fr/fr/<ID>/",
"Studium Parisiense": "studium-parisiense.univ-paris1.fr/individus/<ID>",
"Collecta": "www.collecta.fr/p/COL-IMG-<ID>",
};
/**
* Function to fetch the correct URL string from a Select2 input based on the selected option.
* @param {Event} event - The event object triggered by the Select2 input.
*/
function fetchCorrectUrlStringFromKbSelect(event) {
// get id of the select2 input
let UserKbSelected = event.id;
// Extract the number from the ID
let number = UserKbSelected.split('-')[1];
// Get the value of the selected option
let KbType = $(event).select2('val');
// Format the string to get the correct input ID
let formattedString = `#kb_links-${number}-url`;
// Set the value of the input with the correct URL
$(formattedString).val(KB_URL_MAP[KbType]);
}
Finally, I attach my js script to the edit.html
template :
{% extends 'admin/model/edit.html' %}
{% block tail_js %}
{{ super() }}
<script type="text/javascript" src="{{ url_for('static', filename='js/person.form.fields.js') }}"></script>
{% endblock %}
Here is the result in the form:
N.B. : I use Flask-Admin==1.6.1
Upvotes: 1