WTForms render_field() method reference for-loop variables - python

I'm using Flask to create a web app. I'm trying to create a form using Flask-WTForms by iterating through a list passed in the render_template() method. However, I can't reference the variable in the for-loop inside the template.
View
class FormExample(Form):
category1 = StringField("Category 1")
category2 = StringField("Category 2")
categories = ['category1', 'category2']
def form():
form = FormExample(request.form)
return_template("form.html", categories=categories, form=form)
_formhelpers.html (suggested to use under the docs)
{% macro render_field(field) %}
<dt>{{ field.label }}
<dd>{{ field(**kwargs)|safe }}
{% if field.errors %}
{% for error in field.errors %}
{{ error }}
{% endfor %}
{% endif %}
</dd>
{% endmacro %}
Template (form.html)
<form method="POST">
{% for category in categories %}
{{render_field(form.category)}}
{% endfor %}
</form>
When trying to reference form.category in form.html I'm given the following error through the Flask debugger:
jinja2.exceptions.UndefinedError: '__main__.EvaluateCaseForm object' has no attribute 'category'
I've already looked at the official documentation here and couldn't find the answer. I've also tried referencing {{render_field({{ form.category }})}}, {{render_field(form.{{category}})}}, and {{render_field({% form.category %})}}
Is there a way to reference the for-loop variable category inside the render_field() method?

WTForms uses the __getitem__ protocol to allow fields can be accessed like dictionary values, for example form[fieldname].
So in your code, replace form.category with form[category]:
<form method="POST">
{% for category in categories %}
{{ render_field(form[category]) }}
{% endfor %}
</form>

Is there a way to reference the for-loop variable category inside the render_field() method?
Yup:
{% for category in categories %}
{{render_field(category)}}
{% endfor %}

Related

Django include template, overwrite 'self' variable?

I've been tasked to fix a template structure in a Django app. I am not very familiar with Django as I deal mostly with front end projects built in React.
The Django app is using Wagtail as a backend CMS. I am looping through it's pages, and including a template block and trying to pass variables with the with statement on the include. I can successfully pass variables to the template block, but I need to overwrite the 'self' variable as the template block is looking for self.something.
Here is what my template looks like.
{% load static wagtailimages_tags wagtailcore_tags %}
<div class="c-card {% if self.variant %}c-card--{{ self.variant }}{% endif %}">
{% if self.image %}
{% image self.image original class="c-card__image" loading="lazy" %}
{% endif %}
{% if self.title %}
<h3 class="c-card__title">{{ self.title }}</h3>
{% endif %}
{% if self.text %}
<p class="c-card__content">{{ self.text }}</p>
{% endif %}
{% if self.button.title and self.button.url %}
{% include './button_block.html' with button=self.button %}
{% endif %}
</div>
and then there is the loop and include snippet...
{% for chapter_menu in page.chapters %}
<div class="medium-6 large-4 columns">
{{ chapter_menu.value.title }}
{% include './blocks/card_block.html' with self=chapter_menu.value %}
</div>
{% endfor %}
Doing this self=chapter_menu.value results in a TypeError, which is expected as I am defining self twice. push() got multiple values for argument 'self'
How can I go about doing this? Any docs I can read besides the basic django templating docs? https://docs.djangoproject.com/en/3.0/ref/templates/builtins/#with
add the only attribute to include.... Did not find this in any documentation...
{% include './blocks/card_block.html' with self=chapter_menu.value only %}

Flask-security and Bootstrap

How can I style my Flask-security login site with Bootstrap? The html form looks like this:
<form action="{{ url_for_security('login') }}" method="POST" name="login_form">
{{ login_user_form.hidden_tag() }}
{{ render_field_with_errors(login_user_form.email) }}
{{ render_field_with_errors(login_user_form.password) }}
{{ render_field_with_errors(login_user_form.remember) }}
{{ render_field(login_user_form.next) }}
{{ render_field(login_user_form.submit) }}
</form>
Bootstrap is implemented, but I dont know how to edit the fields and the submit button..
The render_field_* functions accepts a class_ parameter, which will add HTML classes to the field. Add in bootstrap styling classes as you want.
render_field_with_errors(login_user_form.email, class_="form-control") }}
{{ render_field(login_user_form.submit, class_="btn btn-default") }}
And so on.
Flask-Security uses Flask-WTForms to render and validate forms. In Flask-Security 1.7.5, the default render_field_with_errors and render_field macros defined in "security/_macros.html" are
{% macro render_field_with_errors(field) %}
<p>
{{ field.label }} {{ field(**kwargs)|safe }}
{% if field.errors %}
<ul>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</p>
{% endmacro %}
{% macro render_field(field) %}
<p>{{ field(**kwargs)|safe }}</p>
{% endmacro %}
According to the Flask-WTForms 0.10 docs, both of the above macro functions accept ...
... keyword arguments that are forwarded to WTForm’s field function that renders the field for us. The keyword arguments will be inserted as HTML attributes.
Specifically, the lines {{ field(**kwargs)|safe }} pass the HTML escaped keyword arguments to the field function. Therefore, you can add classes,
{{ render_field_with_errors(login_user_form.email, class="form-control") }}
and can also overwrite default HTML attributes,
{{ render_field_with_errors(login_user_form.email,
class="form-control", type="email", placeholder="Enter email") }}
{{ render_field(login_user_form.submit, class="btn btn-default", value="Submit" ) }}
Additionally, you can define your own macros by modifying the macros above. For example, if you wanted to use Bootstrap alerts to render form validation errors, you could define the macro function render_field_with_bootstrap_errors
{% macro render_field_with_bootstrap_errors(field) %}
<p>
{{ field.label }} {{ field(**kwargs)|safe }}
{% if field.errors %}
{% for error in field.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
{% endif %}
</p>
{% endmacro %}
Adding your own macro is pretty simple. For example, you can put custom macros in a "custom_macros.html" file within the templates directory and then load the functions into templates with
{% from "custom_macros.html" import render_field_with_bootstrap_errors %}
This way, it is easy to modify the macros to use different Bootstrap features.

How to use __repr__ method within django template?

For example, there is an object in a nested loop:
{% for fieldset in inline_admin_form %}
{% for line in fieldset %}
{% for field in line %}
{% if field.is_hidden %} {{ field.field }} {% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% endif %}
Now I want to check the class name and some information about field.field, so I use field.field.__repr__() to replace field.field.
However, the django template complains about it after the change:
Variables and attributes may not begin with underscores: 'field.field.__repr__'
Does anyone have idea about this? And is there any better way to debug for a variable in django template? (I tried {% debug %} but found it awful when I want to check a variable in a nested loop..)
{{ value|stringformat:'r' }}
uses the string % operator style formatting with the r format which uses repr()
You could easily write a template filter which allows you to do {{ var|asrepr }}. See the documentation, but it'll look something like this:
#register.filter
def asrepr(value):
return repr(value)

Django related_name

I'm trying to do something like:
{% for property in current_listing %}
{% for property_image in property.property_images.all %}
{% endfor %}
{% endfor %}
But I would like something like:
{% for property in current_listing %}
{% for property_image in property.property_images.**ORDER_BY('-order')[0]** %}
{% endfor %}
{% endfor %}
How can I do this?
If I understand what you want, you can try custom template filter:
from django import template
register = template.Library()
#register.filter
def get_first_ordered_by(queryset, order):
return queryset.order_by(order)[0]
Then on a template:
{% load my_tags %}
{% with image=property.property_images.all|get_first_ordered_by:'-order' %}
{{ image }}
{% endwith %}
Note, that you can not use {% for %} since result of get_first_ordered_by is not iterable.
You can add a method to you Model's class definition that returns the query you want, then cann that method from you template.

Is there a way to hide the csrf label while looping through form using Flask and Flask-WTForms?

I have very simple contact form and I would like to hide the label somehow so that it doesn't show Csrf Token. I am using Flask and Flask-WTForms and am rendering the form like this:
{% for field in form %}
{{ field.label }}
{{ field }}
{% endfor %}
So basically this shows my inputs correctly and the csrf oen is hidden but the label isn't hidden? Should I get over it and implicitly say form.field_name instead of looping through the form or is there a way to handle this "corner case".
I was thinking about doing a logical check in either the for loop declaration or the label declaration but so far I haven't found anything in the documentation that has worked.
Thanks
EDIT: I have "fixed" the problem by doing this but it feels kinda dirty and hacky which I don't like I am still open to a better solution:
{% if not loop.first %}
{{ field.label }}
{% endif %}
If you want a more general solution that works for all hidden fields instead of just the CSRF token:
{{ form.hidden_tag() }}
{% for field in form if field.widget.input_type != 'hidden' %}
{{ field.label }}
{{ field }}
{% endfor %}
form.hidden_tag() is supplied by Flask-WTF.
Just to add to JD's excellent answer...
For those stumbling across this question: You can avoid losing the (csrf) hidden field (and thus protection) by adding the condition "if field.widget.input_type!='hidden' " specifically to the label instead of to the form iterator.
i.e.:
not
{{ form.hidden_tag() }}
{% for field in form if field.widget.input_type != 'hidden' %}
{{ field.label }}
{{ field }}
{% endfor %}
but
{{ form.hidden_tag() }}
{% for field in form %}
{% if field.widget.input_type != 'hidden' %} {{ field.label }} {% endif %}
{{ field }}
{% endfor %}
I think this should work too:
{% for field in form if field.id != 'csrf_token' %}
{{ field.label }}
{{ field }}
{% endfor %}
I have found the way to do it like this:
{% if field.id != 'csrf_token' %}
I believe this to be less hacky. I found this from modifying the example here in the docs.
I made a macro recently to submit forms through ajax in order to not reload the webpage and send it to the api directly.
{% macro render_fields3(form, form_name, method) %}
<form class="ajax" name={{ form_name }} method={{ method }}>
{{ form.hidden_tag() }}
{% for field in form if field.widget.input_type != 'hidden' %}
<dt>{{ field.label }}
<dd>{{field(id=field.name + method)|safe}}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</dd>
{% endfor %}
</form>
{% endmacro %}

Categories

Resources