admin管理员组

文章数量:1323529

I have a fruits form that has one FieldList object for the bananas:

bananas = FieldList(FormField(BananaForm))

In the frontend, initially, I add one of those fields to the FieldList

form.append_entry()

Now with Javascript I managed to create functions, that can dynamically add (plus button) or remove (minus button) the number of BananaForm fields that can be filled with information.

FielstList automatically creates ids for all of its fields. So to do dynamical adding with js, I duplicate the HTML code and set the field id += 1, like:

first field:

<tr>
  <td><input id="bananas-0-originCountry" type="text" /></td>
</tr>

duplicated field with += 1:

<tr>
  <td><input id="bananas-1-originCountry" type="text" /></td>
</tr>

When I name them accordingly like this and submitting the form, WTForms will automatically recognize the added fields in the backend (works fine).

So far so good, but here is my problem: For a form to be valid, I have to add CSRF-fields to every WTForm. In the Jinja template I do this with:

{{ form.hidden_tag() }}

However, when I just copy the HTML with my js function, I'm missing the CSRF-fields (because until submitted, the backend form object doesn't know about the added FormFields). So how can I generate these CSRF-fields dynamically? (An Ajax Request? If yes, how?)

This should be a standard use case with forms and flask. I hope my description was understandable, if not please let me know. Any help appreciated!

UPDATE: Here is my code

JS-functions

function addBanana(){
    // clone and insert banana node
    var node = document.getElementById("fruitTable");
    var trs = node.getElementsByTagName("tr");
    var tr = trs[trs.length-2];
    var tr2 = tr.cloneNode(true);
    tr.parentNode.insertBefore(tr2, tr);

    // in order to increment label and input field ids
    function plusone(str){
        return str.replace(
            new RegExp("-(\\d+)-", "gi"),
            function($0, $1){
                var i = parseInt($1) + 1;
                return "-" + i + "-";
            }
        );
    }

    // change inputs
    var inputs = tr.getElementsByTagName("input");

    for (var i = 0; i < inputs.length; i++){
        inputs[i].setAttribute("id", plusone(inputs[i].getAttribute("id")));
    }

    var minusbutton = 
        ['<td>',
        '<button class="btn" type="button" onClick="removeBanana()"><i class="icon-black icon-minus"></i></button>',
        '</td>'
        ].join('\n');

    // only append at the first add
    // second add automatically copies minus button
    if (trs.length < 6){
        tr.innerHTML += minusbutton
    }
}

function removeBanana(){
    var node = document.getElementById("fruitTable");
    var trs = node.getElementsByTagName("tr");
    var tr = trs[trs.length-2];
    var trParent = tr.parentNode;
    trParent.removeChild(tr);
}

Jinja Template:

<form method="POST" action="newsubmit">
  {{ form.hidden_tag() }}
  <table id="fruitTable" class="table">
    {{ render_field(form.description) }}
    <tr><td><h3>Bananas</h3></td></tr>
    {% set counter = 0 %}
    {% for banana in form.bananas %} 
      <tr>
        {{ banana.hidden_tag() }}
        {% set counter = counter + 1%}
        {% for field in banana if field.widget.input_type != 'hidden' %}
          {{ render_field_oneline(field) }}
        {% endfor %}
        {% if counter > 1 %} 
          <td>
            <button class="btn" type="button" onClick="removeBanana()"><i class="icon-black icon-minus"></i></button>
          </td>
        {% endif  %} 
      </tr>
    {% endfor %}
      <tr><td></td><td><button class="btn" type="button" onClick="addBanana()"><i class="icon-black icon-plus"></i></button></td></tr>
  </table>
<input class="btn btn-primary" style="margin-left:300px;"type="submit" value="Submit" />
</form>

Jinja Template Macros:

{% macro render_field_oneline(field) %}
<td>{{ field.label }}</td>
<td>{{ field(**kwargs)|safe }}
  {% if field.errors %}
  <ul class=errors>
    {% for error in field.errors %}
    <li>{{ error }}</li>
    {% endfor %}
  </ul>
  {% endif %}
</td>
{% endmacro %}

{% macro render_field(field) %}
<tr>
  {{ render_field_oneline(field) }} 
</tr>
{% endmacro %}

I have a fruits form that has one FieldList object for the bananas:

bananas = FieldList(FormField(BananaForm))

In the frontend, initially, I add one of those fields to the FieldList

form.append_entry()

Now with Javascript I managed to create functions, that can dynamically add (plus button) or remove (minus button) the number of BananaForm fields that can be filled with information.

FielstList automatically creates ids for all of its fields. So to do dynamical adding with js, I duplicate the HTML code and set the field id += 1, like:

first field:

<tr>
  <td><input id="bananas-0-originCountry" type="text" /></td>
</tr>

duplicated field with += 1:

<tr>
  <td><input id="bananas-1-originCountry" type="text" /></td>
</tr>

When I name them accordingly like this and submitting the form, WTForms will automatically recognize the added fields in the backend (works fine).

So far so good, but here is my problem: For a form to be valid, I have to add CSRF-fields to every WTForm. In the Jinja template I do this with:

{{ form.hidden_tag() }}

However, when I just copy the HTML with my js function, I'm missing the CSRF-fields (because until submitted, the backend form object doesn't know about the added FormFields). So how can I generate these CSRF-fields dynamically? (An Ajax Request? If yes, how?)

This should be a standard use case with forms and flask. I hope my description was understandable, if not please let me know. Any help appreciated!

UPDATE: Here is my code

JS-functions

function addBanana(){
    // clone and insert banana node
    var node = document.getElementById("fruitTable");
    var trs = node.getElementsByTagName("tr");
    var tr = trs[trs.length-2];
    var tr2 = tr.cloneNode(true);
    tr.parentNode.insertBefore(tr2, tr);

    // in order to increment label and input field ids
    function plusone(str){
        return str.replace(
            new RegExp("-(\\d+)-", "gi"),
            function($0, $1){
                var i = parseInt($1) + 1;
                return "-" + i + "-";
            }
        );
    }

    // change inputs
    var inputs = tr.getElementsByTagName("input");

    for (var i = 0; i < inputs.length; i++){
        inputs[i].setAttribute("id", plusone(inputs[i].getAttribute("id")));
    }

    var minusbutton = 
        ['<td>',
        '<button class="btn" type="button" onClick="removeBanana()"><i class="icon-black icon-minus"></i></button>',
        '</td>'
        ].join('\n');

    // only append at the first add
    // second add automatically copies minus button
    if (trs.length < 6){
        tr.innerHTML += minusbutton
    }
}

function removeBanana(){
    var node = document.getElementById("fruitTable");
    var trs = node.getElementsByTagName("tr");
    var tr = trs[trs.length-2];
    var trParent = tr.parentNode;
    trParent.removeChild(tr);
}

Jinja Template:

<form method="POST" action="newsubmit">
  {{ form.hidden_tag() }}
  <table id="fruitTable" class="table">
    {{ render_field(form.description) }}
    <tr><td><h3>Bananas</h3></td></tr>
    {% set counter = 0 %}
    {% for banana in form.bananas %} 
      <tr>
        {{ banana.hidden_tag() }}
        {% set counter = counter + 1%}
        {% for field in banana if field.widget.input_type != 'hidden' %}
          {{ render_field_oneline(field) }}
        {% endfor %}
        {% if counter > 1 %} 
          <td>
            <button class="btn" type="button" onClick="removeBanana()"><i class="icon-black icon-minus"></i></button>
          </td>
        {% endif  %} 
      </tr>
    {% endfor %}
      <tr><td></td><td><button class="btn" type="button" onClick="addBanana()"><i class="icon-black icon-plus"></i></button></td></tr>
  </table>
<input class="btn btn-primary" style="margin-left:300px;"type="submit" value="Submit" />
</form>

Jinja Template Macros:

{% macro render_field_oneline(field) %}
<td>{{ field.label }}</td>
<td>{{ field(**kwargs)|safe }}
  {% if field.errors %}
  <ul class=errors>
    {% for error in field.errors %}
    <li>{{ error }}</li>
    {% endfor %}
  </ul>
  {% endif %}
</td>
{% endmacro %}

{% macro render_field(field) %}
<tr>
  {{ render_field_oneline(field) }} 
</tr>
{% endmacro %}
Share Improve this question edited Aug 2, 2012 at 13:26 kadrian asked Aug 1, 2012 at 12:56 kadriankadrian 5,0098 gold badges42 silver badges63 bronze badges 2
  • 1 By the sound of things the issue is with your HTML or your JavaScript - could you post those so we can take a deeper look? (My first guess is that you are replacing the entire form's inner HTML with your regenerated code thus wiping out the CSFR tag). – Sean Vieira Commented Aug 1, 2012 at 19:17
  • Updated my question with the code. I discovered that despite the hidden_tag() should render the CSRF-tag inside the copied <tr> and be copied as well (which is probably not the way to go, because it would have the same hash then), but actually it isn't even copied! I really don't understand the magic going on here. However, what I can definitely say is that the CSRF-Tag is not wiped out or replaced! – kadrian Commented Aug 2, 2012 at 13:31
Add a ment  | 

1 Answer 1

Reset to default 7

I discovered how it works:

The CSRF-Tag can simply be copied. The id must be changed and incremented accordingly, but the hash may stay the same.

I didn't think it was possible to have many Fields with the same CSRF-Tag hash, but it actually does!

本文标签: javascriptHowto Dynamically generate CSRFToken in WTForms with FlaskStack Overflow