Jinja2 macros argument doesn't expand - python

I have a macro that's defined like this:
{% macro render_row (class_suffix, form, field) %}
<div class="form-{{ class_suffix }}__row">{{ form.field.label }} {{ form.field() }}</div>
{% endmacro %}
I want to pass a form object and a field parameter to render a specific row, like this:
{% import "macros.html" as macros %}
...
<div class="form-container">
<h2>Sign In</h2>
<form class="form-login" action="{{ url_for('signin') }}" method="post">
{{ macros.render_row ('login', form, email) }}
{{ macros.render_row ('login', form, password) }}
{{ macros.render_submit ('login', 'Sign In') }}
{{ form.csrf_token }}
</form>
</div>
For example, macros.render_row ('login', form, email) should be expanded to:
<div class="form-login__row">{{ form.email.label }} {{ form.email() }}</div>
Instead, I get this error:
jinja2.exceptions.UndefinedError: 'blog.forms.SignInForm object' has no attribute 'field'
Form's defined like this:
class SignInForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Email(), Length(max=64)])
password = PasswordField('Password', validators=[DataRequired(), Length(min=8, max=64)])
I've also found a solution, but I'm curious why previous method doesn't work.
{% macro render_row (class_suffix, form_field) %}
<div class="form-{{ class_suffix }}__row">{{ form_field.label }} {{ form_field() }}</div>
{% endmacro %}
...
{{ macros.render_row ('login', form.email) }}
Is it possible to do? What am I doing wrong? Is it even a good practice to do something like this?

That's because Jinja2 tries to get the property named field from the form object passed, not the field which name equals to the variable field passed.
It tries to do
{{ foo.field }}
{{ foo['field'] }}
But for your case, it will work only using this syntax, of course.
{{ foo[field] }}
Where field param for the macros should be a string I believe, now I think that's None.
So it should be
{{ macros.render_row ('login', form, 'email') }}
and the definition of the macro like
{% macro render_row (class_suffix, form, field) %}
<div class="form-{{ class_suffix }}__row">{{ form[field].label }} {{ form[field]() }}</div>
{% endmacro %}
Check more details on the variable's syntax page https://jinja.palletsprojects.com/en/2.10.x/templates/#variables.
I haven't tested it in real files, not have Jinja2 installed in my hands. But it should be working like that.

Related

How to display the field "label" in django's builtin password validation errors?

I am using Django's builtin authentication class views and need to customize the error message displayed when password validation fails.
For example, in the builtin PasswordResetView, if I try to change my password to test, the following errors will display in my template:
new_password2
This password is too short. It must contain at least 8 characters.
This password is too common.
I would like to change new_password2 to New Password.
Here is the relevant part of my template for the PasswordResetView:
{% extends 'registration/base.html' %}
{% block card_body %}
<div class="form-group">
<label for="old_password">
Old Password:
{{ form.old_password }}
</label>
</div>
<div class="form-group">
<label for="new_password1">
New Password:
{{ form.new_password1 }}
</label>
</div>
<div class="form-group">
<label for="new_password2">
Confirm Password:
{{ form.new_password2 }}
</label>
</div>
<div class="form-group">
<input type="submit" value="Change" class="btn float-right login_btn">
</div>
{% endblock card_body %}
{% block card_footer %}
{% if form.errors %}
<p class="d-flex justify-content-center links">
{{ form.errors }}
</p>
{% endif %}
{% endblock card_footer %}
Supply some dict to the template that will map form field names to labels you want like:
fields_mapping = {
'old_password': 'Old password',
'new_password1': 'New password',
'new_password2': 'Confirm password'
}
Just manually iterate over errors and use the the mapping dic to convert field names to labels you want:
{% for field_name in form.errors: %}
{{ fields_mapping[field_name] }}:
{% for err_message in form.errors[field_name]: %}
* {{ err_message }}
{% endfor %}
{% endfor %}
Customize HTML/CSS there as you want

Django label_tag required asterisk

I am currently working on some Django templates and would like to put an asterisk after the label of required fields.
First of all I have found this syntax which works just fine but it makes me type a lot of code that is not required without the required asterisk.
<div>
{{ field.errors }}
<label for="{{ field.auto_id }}">
{% if field.field.required %}<span class="required">{{ field.label }}</span>
{% else %}{{ field.label }}{% endif %}
</label>
{{ field }}
</div>
This is the code I currently have in my template:
<div>
{{ field.errors }}
{{ field.label_tag }}{{ field }}
</div>
This way it saves a lot of code to write, but I can't figure out how to get the asterisk after the label with a required field.
Could someone help me with this?
If you need some more info feel free to ask.
Have you tried setting the required_css_class = 'something' on the form (cf here) Then using css you could add a red asterisk on the label
Consider writing a template tag that takes in field and returns field.label suffixed with an asterisk if the field is required, and field.label if not. And you can use like so:
<div>
{{ field.errors }}
{{ field | append_ast_if_req }}
{{ field }}
</div>
Your template tag can be:
#register.filter
def append_ast_if_req (field):
if field.field.required:
return field.label + '*'
else:
return field.label

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.

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 %}

WTForms Can I add a placeholder attribute when I init a field?

I want to add a placeholder attribute on to the field in WTForms. How can I do it?
abc = TextField('abc', validators=[Required(), Length(min=3, max=30)], placeholder="test")
The above code is not valid
How can I add a placeholder attribute with value?
Updated for WTForms 2.1
You can now as of WTForms 2.1 (December 2015) set rendering keywords by using the render_kw= parameter to the field constructor.
So the field would look like:
abc = StringField('abc', [InputRequired()], render_kw={"placeholder": "test"})
Note while this is possible; it does start to bridge the line between code and presentation; so use it wisely!
(Old answer, still true for versions older than WTForms 2.1)
placeholder is not supported in the Python constructor in WTforms 2.0.x and below.
However, you can do this easily in your template:
{{ form.abc(placeholder="test") }}
Correct answer is as follows:
abc = TextField('abc', validators=[Required(), Length(min=3, max=30)], description="test")
As one can read in documenatation:
description – A description for the field, typically used for help text.
Then in your template:
{% import 'forms.html' as forms %}
{% for field in form %}
{{ forms.render_field(field) }}
{% endfor %}
Where render_field is a macro that is defined in forms.html:
{% macro render_field(field) -%}
{% if field.type == 'CSRFTokenField' %}
{{ field }}
{% if field.errors %}
<div class="warning">You have submitted an invalid CSRF token</div>
{% endif %}
{% elif field.type == 'HiddenField' %}
{{ field }}
{# any other special case you may need #}
{% else %}
<div class="form-group">
<label for="{{ field.label.field_id }}" class="col-sm-2 control-label">{{ field.label.text }}</label>
<div class="col-sm-10">
{{ field(placeholder=field.description) }}
{% if field.errors %}
<div class="alert alert-danger" role="alert">
{% for err in field.errors %}
<p>{{ err|e }}</p>
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endif %}
{%- endmacro %}
{{ form.username(class="input", placeholder="Please enter your username") }}
My solution is use a custom widget:
from flask.ext.wtf import Form
from wtforms import StringField, validators
from wtforms.widgets import Input
class CustomInput(Input):
input_type = None
def __init__(self, input_type=None, **kwargs):
self.params = kwargs
super(CustomInput, self).__init__(input_type=input_type)
def __call__(self, field, **kwargs):
for param, value in self.params.iteritems():
kwargs.setdefault(param, value)
return super(CustomInput, self).__call__(field, **kwargs)
class CustomTextInput(CustomInput):
input_type = 'text'
class EditProfileForm(Form):
first_name = StringField('First name',
validators=[validators.DataRequired()],
widget=CustomTextInput(placeholder='Enter first name'))
Maybe it's not ellegant, but it allows to use Flask-Bootstrap and define your forms in the forms code, not in the template

Categories

Resources