Lter
Lter

Reputation: 55

Flask-Admin: How can I dynamically pre-populate a form field based on another field's selection?

In flask admin, I have an inline model with a Select2 field named 'Type' and a text input field named 'URL'.

enter image description here

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...?)

enter image description here

Upvotes: 0

Views: 558

Answers (1)

Lter
Lter

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:

enter image description here

N.B. : I use Flask-Admin==1.6.1

Upvotes: 1

Related Questions