Skip to content

Instantly share code, notes, and snippets.

@slint
Created March 21, 2019 15:03
Show Gist options
  • Save slint/81b2be2a9fa78b20e6c186f130192348 to your computer and use it in GitHub Desktop.
Save slint/81b2be2a9fa78b20e6c186f130192348 to your computer and use it in GitHub Desktop.
nested record example
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://zenodo.org/schemas/records/record-v1.0.0.json",
"properties": {
"formats": {
"type": "array",
"items": {
"type": "object",
"properties": {
"width": {
"type": "number"
},
"height": {
"type": "number"
},
"thickness": {
"type": "number"
}
},
"required": [
"width", "height"
]
},
"minLength": 1
}
}
}
from invenio_records.api import Record
data = {
"$schema": "http://my-site.org/schemas/records/record-v1.0.0.json",
"formats": [
{'width': 10, 'height': 5},
{'width': 12, 'height': 5},
{'width': 13, 'height': 5},
{'width': 20, 'height': 5},
]
}
rec = Record.create(data)
# if not valid you get ValidationException
@BAA5809
Copy link

BAA5809 commented Apr 3, 2019

now I used the above json example for making exactly that nested field I needed.
it could all work fine but unfortunately there is a flask bug (csrf_token) which I am unable to fix..

here what happens:

  1. in the the form.py I made my nested class and the reference to it

class FormatForm(FlaskForm):
"""This is the nested format form with 3 fields."""

width = IntegerField('width', [validators.optional()])
height = IntegerField('height', [validators.optional()])
thickness = IntegerField('thickness', [validators.optional()])


(NB: I made an IntegerField but it is always gerendert as type="text", so this is not working but never mind)

and then in the main form class the reference to the "format" class:

format = (FieldList(FormField(FormatForm), min_entries=1))

this is working fine.

on the Jinja template page I tried out TWO ways of doing it:

      <div class="form-group {{ 'has-error' if form.format.errors }}">
        <label for="format">Format(s) </label>
          <br>
            <!-- {{ form.format|safe}}  I don't want this because I can't customize the form -->
              {% for subformfield in form.format %}  <!-- this is MUCH better but the whole form is simply not submitted! -->
                   {{ subformfield.form.height.label }}:{{ subformfield.form.height(size=6)}} cm,
                   &nbsp;{{ subformfield.form.width.label }}:{{ subformfield.form.width(size=6) }} cm,
                   &nbsp;{{ subformfield.form.thickness.label }}:{{ subformfield.form.thickness(size=6) }} cm
                   <br>
              {% endfor %}
            <input type="hidden" name="format-0-csrf_token" value="" />
      </div>

--> this means: I have tried both ways, the first one (commented out) is the WTForms way; it is then rendered as a table and works but I have no possibility to control how it looks like.
Therefore I had to make the second manual iteration. That way the form is rendered exactly as I want it.
Unfortunately there is another problem (a WTForms bug..) I will explain it below and for that reason I have added manually the hidden field.

here is the page source for the jinja snippet:


  <div class="form-group ">
            <label for="format">Format(s) </label>
              <br>
                <!-- <ul id="format"><li><label for="format-0">Format-0</label> <table id="format-0"><tr><th><label for="format-0-width">width</label></th><td><input id="format-0-width" name="format-0-width" type="text" value=""></td></tr><tr><th><label for="format-0-height">height</label></th><td><input id="format-0-height" name="format-0-height" type="text" value=""></td></tr><tr><th><label for="format-0-thickness">thickness</label></th><td><input id="format-0-thickness" name="format-0-thickness" type="text" value=""></td></tr></table><input id="format-0-csrf_token" name="format-0-csrf_token" type="hidden" value="IjY5M2RmOTkzOGFmNGMyYWIzMDRlYWZhMGRkZjM2NGQwYzZmY2RmNDYi.XKR8Mw.8UsFgizVBcE16EPxxmPDNWk2dOU"></li></ul>  I don't want this because I can't customize the form -->

                    <!-- this is MUCH better but the whole form is simply not submitted! -->
                       <label for="format-0-height">height</label>:<input id="format-0-height" name="format-0-height" size="6" type="text" value=""> cm,
                       &nbsp;<label for="format-0-width">width</label>:<input id="format-0-width" name="format-0-width" size="6" type="text" value=""> cm,
                       &nbsp;<label for="format-0-thickness">thickness</label>:<input id="format-0-thickness" name="format-0-thickness" size="6" type="text" value=""> cm
                       <br>
                  
                <input type="hidden" name="format-0-csrf_token" value="" />
          </div>
now the problem:

I need the second way, the manual iteration for controlling it.
There is a problem with the manual iteration: the form does never validate.
validate_on_submit() is never true.
This means I never get to the success page, the data is never sent.

I read on stackoverflow that this is a known bug and that the reason is the missing hidden field with csrf_token.
Quite a mess!
I have tested it and yes that's it: If I enter manually in the value="" of the hidden field a valid token, for example taking it from the other rendered form in the commented out table, I am able to submit the form! It validates. But only once, the token is then not valid any more, of course.

And I don'T know how to handle this hidden field to generate a csrsf_token dynamically.

I also read this in the documentation but it fails for me:

"
When using a FlaskForm, render the form's CSRF field like normal.

<form method="post">
    {{ form.csrf_token }}
</form>  

If the template doesn't use a FlaskForm, render a hidden input with the token in the form.

<form method="post">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
</form>   

"

--> the first one is not my case (and does not work anyway), my case is the second one, but I get "jinja2.exceptions.UndefinedError: 'csrf_token' is undefined ". I would write a function for that, but how to generate such a token? and there must be one already which I can reference to...

@BAA5809
Copy link

BAA5809 commented Apr 8, 2019

this solved it:

         {% for subformfield in form.format %} 
                    &nbsp;&nbsp;&nbsp;{{ subformfield.form.height.label }}:{{ subformfield.form.height(size=6)}} cm,
                    &nbsp;&nbsp;{{ subformfield.form.width.label }}:{{ subformfield.form.width(size=6) }} cm,
                    &nbsp;&nbsp;{{ subformfield.form.thickness.label }}:{{subformfield.form.thickness(size=6) }} cm
          		{{ subformfield.form.hidden_tag() }}
         {% endfor %}

NB: form.hidden_tag() creates automatically a csrf_token! (how am I supposed to know.. :D).
and since the ID of the hidden field is also important, I had to create it inside the loop.

this way:

  • hidden field is created
  • it has the correct ID (when adding another set of fields it will surely increase)
  • and it has a csrf_token value

my output now:

<label for="format-0-height">height</label>:<input id="format-0-height" name="format-0-height" size="6" type="text" value=""> cm,
<label for="format-0-width">width</label>:<input id="format-0-width" name="format-0-width" size="6" type="text" value=""> cm,
<label for="format-0-thickness">thickness</label>:<input id="format-0-thickness" name="format-0-thickness" size="6" type="text" value=""> cm
<input id="format-0-csrf_token" name="format-0-csrf_token" type="hidden"  
value="IjY5M2RmOTkzOGFmNGMyYWIzMDRlYWZhMGRkZjM2NGQwYzZmY2RmNDYi.XKtKXQ.yBspzlO14p4ylp0mooU2pk_IEH4">

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment