Asked  4 Months ago    Answers:  5   Viewed   414 times

I'm not quite sure how approach this matter. I hope i get there.

For example I have a table full of addresses on a page. The count of these are dynamic (could be 5 or 10 or any other count). And I want the possibility to edit them on one page.

My approach was to create a Form with wtforms to edit one address and to multiply it in a jinja2 for loop and append to the html propertys name and id the loop.index0 from the itereation, so i can extract each row of data manually and put it back in my form, when I want to evaluate it.

So the Form for this example would be:

class AdressForm(Form):
    name = TextField()

so now my template aproach looks like the following (break down to one input field):

{% for address in addresses %}
    {{ forms.render_field(addressform.name, id = "name_" ~ loop.index0, 
                          name = "name_" ~ loop.index0, value = address.name) }}
{% endfor %}

(forms.render_field is just a macro to specify the right classes to the field function of wtforms. like they use in many tutorials)

So this is not working, since you can't pass the name parameter manually to the field function, since wtforms create the name html-paramter from the variblename of the intial Form.

So is there a way to add a prefix or postfix to the name of a form I want to render. Or is this a XY-Problem and my approach is totaly wrong.

or have I do it all plain by myself (I really try to avoid this)

 Answers

76

WTForms has a meta-field called FormField and another meta-field called FieldList. These two combined together will get you what you want:

class AddressEntryForm(FlaskForm):
    name = StringField()

class AddressesForm(FlaskForm):
    """A form for one or more addresses"""
    addresses = FieldList(FormField(AddressEntryForm), min_entries=1)

To create entries in the AddressesForm, simply use a list of dictionaries:

user_addresses = [{"name": "First Address"},
                  {"name": "Second Address"}]
form = AddressesForm(addresses=user_addresses)
return render_template("edit.html", form=form)

Then, in your template, simply loop over the sub-forms:

{% from 'your_form_template.jinja' import forms %}
{% for address_entry_form in form.addresses %}
    {{ address_entry_form.hidden_tag() }}
    {# Flask-WTF needs `hidden_tag()` so CSRF works for each form #}
    {{ forms.render_field(address_entry_form.name) }}
{% endfor %}

WTForms will automatically prefix the names and the IDs correctly, so when you post the data back you will be able to just get form.addresses.data and get back a list of dictionaries with the updated data.

Saturday, July 10, 2021
 
nfechner
answered 4 Months ago
23

Your function addmore() isn't returning anything because

  1. There is no return "value" in your function
  2. You're making an asynchronous call with $jd.ajax()

You should do like this :

var counter = 0;
function addInput(divName){
      addmore(divName);
}

and :

function addmore(divName){
        $jd.ajax({
          url: "<?php echo JURI::root(); ?>",
          type: "POST",
          data: {'option':'com_joomd', 'view':'itempanel', 'task':'loadfields', 'typeid':<?php echo $this->cparams->typeid; ?>, 'catid[]':checked, 'id':<?php echo (int)$this->item-        id; ?>, "<?php echo jutility::getToken(); ?>":1, 'abase':1},
          beforeSend: function()    {
            $jd(".poploadingbox").show();
          },
          complete: function()  {
            $jd(".poploadingbox").hide();
          },
          success: function(res)    {
             var newdiv = document.createElement('div');
             newdiv.innerHTML = "Member " + (counter + 1) + res;
             document.getElementById(divName).appendChild(newdiv);
             counter++;

          },
          error: function() {
              alert('error');                 
          }
    });
}
Saturday, May 29, 2021
 
tpow
answered 5 Months ago
43

As davidism says in the comments, the default DateField just provides date parsing, it'll just be displayed as a normal text-input.

If you're ready to fully embrace html5 then you can use the DateField from wtforms.fields.html5 which will render a datepicker in any browser that supports it:

from flask import Flask, render_template
from flask_wtf import Form
from wtforms.fields.html5 import DateField
app = Flask(__name__)
app.secret_key = 'SHH!'


class ExampleForm(Form):
    dt = DateField('DatePicker', format='%Y-%m-%d')


@app.route('/', methods=['POST','GET'])
def hello_world():
    form = ExampleForm()
    if form.validate_on_submit():
        return form.dt.data.strftime('%Y-%m-%d')
    return render_template('example.html', form=form)


if __name__ == '__main__':
    app.run(debug=True)

The more usual approach is to find a nice datepicker online, that's using CSS and JS to handle it's display and include that code into your html template, and tell it to apply the datepicker style to any html element that has a class of datepicker. Then when you generate your form you can just do:

<!-- all your CSS and JS code, including the stuff -->
<!-- to handle the datepicker formatting -->

<form action="#" method="post">
    {{ form.dt(class='datepicker') }}
    {{ form.hidden_tag() }}
    <input type="submit"/>
</form>

Any attributes (that aren't reserved for other use) you pass into rendering the form element will by default just add it as an attribute, for example the {{ form.dt(class='datepicker') }} will generate <input class="datepicker" id="dt" name="dt" type="text" value=""> so your CSS/JS can then do whatever it needs to do to provide a good interface for your user.

Sunday, August 1, 2021
 
Mashhadi
answered 3 Months ago
68

Flask returns request.form as a werkzeug MultiDict object. This is kind of like a dictionary, only with traps for the unwary.

http://flask.pocoo.org/docs/api/#flask.request http://werkzeug.pocoo.org/docs/datastructures/#werkzeug.datastructures.MultiDict

MultiDict implements all standard dictionary methods. Internally, it saves all values for a key as a list, but the standard dict access methods will only return the first value for a key. If you want to gain access to the other values, too, you have to use the list methods.

However, I think there's an easier way. Can you do me a favor and try replacing:

language =  request.form['language']

with

language =  form.language.data

and see if that's any different? WTForms should handle the MultiDict object and just return a list for you since you've bound form data to it.

Monday, October 11, 2021
 
AndiDog
answered 6 Days ago
84

WTForms uses a metaclass to handle binding at instantiation time. This metaclass does its work before Form.__init__ is called, thus making it not possible for something in __init__ to create a field that's bound.

The way WTForms is designed is done so as to reduce the amount of work to be done in searching for and finding field classes to only happen the first time a form is instantiated, speeding up your application after the initial request.


Alternately If you're willing to put in the legwork, it is possible to design something similar to Form that supports this behavior, based on BaseForm and using your own metaclass. Be warned, BaseForm is not the same thing as Form, it's purely a low-level way designed for authors of complementary libraries to get access to build similar tools.

Thursday, October 14, 2021
 
ericstumper
answered 3 Days ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :