Flask + WTForms, dynamically generated list of fields

I am making a Flask application that is essentially form based and therefore I am using WTForms and Flask-wtf.

I am currently refactoring my code so my entire form uses WTForms and there is a very dynamic part of one of the forms that I cannot implement with WTForms. I have no idea how to do this, my initial ideas did not work, I cannot find links or guides dedicated to my problem and therefore I am asking for help.

So, the form in question allows users to submit objects consisting of:

  • Shortcut (StringField, easy)
  • A Description (TextAreaField, also easy, although I found it hard to make the default work)
  • A list of properties of the form (predicate, object), where the predicate is taken from a previously created list and object, can in principle be anything, but each predicate will generate a specific object (for example, the predicate associated with "will wait for another object (which comes from the dropdown list), and the predicate "resource" will expect some kind of http link.) This list can be empty.

As you can guess, I am having problems with the list. The way the code is working right now, I get the label and description using wtforms and the property list is generated using a config constant (which is used throughout the code, so I only have one place to edit if I want to add new properties) and a dynamic menu in javascript that creates (here for predicates) fields that I can then use using the flask.request.form object in the view function. All hidden fields for predicates have the same name attribute, and all hidden fields for objects have the same name attribute.

This is what the form view looks like, initialized with several properties:

http://i.imgur.com/bfMG95s.png

Under the label "Propriétés" you have a drop down list to select a predicate, the second field is shown or hidden depending on the selected predicate (could be a drop down list or a text box), and only when you click on "Ajouter propriété" ("Add property"), that a new line is added in the tab below and the fields are generated.

I wouldn't want to change anything on this side because it works really well, makes the form very intuitive and basically exactly what I want it to be from the user.

This is what my custom form looks like right now (it doesn't work and the properties remain empty no matter how many fields I submit on the form):

class PropertyForm(Form):
    property_predicate = HiddenField(
        validators=[AnyOf(values=app.config["PROPERTY_LIST"].keys())]
    )
    property_object = HiddenField(
        validators=[DataRequired()]
    )

class CategoryForm(Form):
    """
        Custom form class for creating a category with the absolute minimal
        attributes (label and description)
    """
    label = StringField(
        "Nom de la categorie (obligatoire)",
        validators=[DataRequired()]
    )
    description = TextAreaField(
        "Description de la categorie (obligatoire)",
        validators=[DataRequired()]
    )
    properties = FieldList(FormField(PropertyForm),validators=[Optional()])

      

And here's what I would like to do in my view.py code (which I am currently refactoring):

def cat_editor():
    cat_form = CategoryForm()
    if request.method == "GET":
        # Do GET stuff and display the form
        return render_template("cateditor.html", form=cat_form, varlist=template_var_list)
    else if request.method == "POST":
        if cat_form.validate_on_submit():
            # Get values from form
            category_label = cat_form.label.data
            category_description = cat_form.description.data
            category_properties = cat_form.properties.data
            # Do POST stuff and compute things
            return redirect(url_for("index"))
        else:
            # form didn't validate so we return the form so the template can display the errors
            return render_template("cateditor.html", form=cat_form,
                                    template_var_list = template_var_list)

      

The basic structure works great, it's just this damn dynamic list that I can't seem to work with properly.

Getting the label and description from a WFForms CategoryForm instance works great, but the properties always return an empty list. Ideally I would like to get a list of the form [(predicate1, property1), (predicate2, object2) ...] when calling cat_form.properties.data (which is why I have a FieldList from FormFields with two HiddenFields in each), but I don’t would have to create such a list from two lists if it uses WTForms. Any ideas? Many thanks:)

+3


source to share


2 answers


I figured out what the problem was by playing around with FieldList and append_entry () objects to see what HTML code Flask-wtf would generate if I had to create a list of pre-filled properties.

My Javascript was generating hidden fields with all the same name as from what I understood WTForms can combine fields with the same name to create lists. The problem is that the same similar fields were part of the FormField itself, nested in the name properties of the FieldList object.

To make a WTForms form object recognize a set of hidden fields from another, when you insert a FormField inside a field, it prefixes the FormFields field names with "FieldList_name-index-". This means the expected WTForms were something like

<input type="hidden", name="properties-0-property_predicate" value=...>
<input type="hidden", name="properties-0-property_object" value=...>

<input type="hidden", name="properties-1-property_predicate" value=...>
<input type="hidden", name="properties-1-property_object" value=...>

<input type="hidden", name="properties-2-property_predicate" value=...>
<input type="hidden", name="properties-2-property_object" value=...>

      



I changed my javascript so that it generates the appropriate names. Now when I call cat_form.properties.data I have something similar:

[{"property_predicate": "comment", "property_object":"bleh"},
 {"property_predicate": "comment", "property_object": "bleh2"}]

      

And that's exactly what I need. For some reason the form is not validated, but at least I know how to get WTForms to fetch data from hidden fields generated by javascript, which was the problem.

Edit: Form validation happens because you need to insert a hidden CSRF input with your csrf into every subform you create with a FormField.

+1


source


Use jQuery for more dynamic elements / behavior in your form. Note that form fields have a hidden property (or method, depending on, for example, if you are using upload), allowing you to display whatever you need, but only show the fields when needed and hide them otherwise. Dynamically added fields are a little more complicated, but not really possible. Is there a limit to the number of fields associated with properties? if so, i would just display the maximum number of fields (if that's reasonable, up to 5 seems OK, when you get double-digit numbers as the maximum number of properties the user can add, creating a bunch of fields, I will never use it to be inelegant) ...

Here's a good place to see how it works. Of course you have another selection problem when to hide or show the corresponding fields, but this can also be handled by a javascript / jQuery script using the jQuery.change () event. Something like that:



$("#dropdown").change(function () {
    var chosen_val = $(this).val();
    if (chosen_val == 'banana'){$('#property1').show();} else {$('#property1').hide();}
   });

      

This code probably won't work and is definitely devoid of proper logic, but should give you an idea of ​​how to approach this problem with jQuery. Note that the "property1" field is always present, waiting to be displayed if the user chooses the correct dropdown value.

0


source







All Articles