Pass arbitrary data from a FlaskForm object to the template - python

While building a small Flask based web app, I came across the need to pass arbitrary data from a Form Field object to the template where it's being rendered. However, I can't seem to find a way to do this.
The only place I think I could add such data was to the kwargs property of WTForms Field objects, but then I seem to have no way to access those properties from the template.
In case you're wondering what I'm trying to accomplish, I'm writing a template macro to ease form rendering, and I need to pass some extra data from the Form fields objects - mostly layout related, but which will not be HTML attributes for the fields themselves (which is what kwargs is designed for).

I found an answer here but it's not an answer per se but I comment.
Quoting Crast:
The description keyword argument of WTForms fields is allowed to be set at field construction, and is not inspected, just copied directly onto the field, and thus can be any value, not just a string, even a custom attribute. If you want to carry over your own metadata, you can simply use this to carry over any data you may want: TextField(..., description={'placeholder': foo', 'class': bar} (or even a custom class) then use this attribute in your template for any special metadata you want.
Yes, I know about separating content and presentation and the purpose of the description property isn't really intended for this kind of use, but it's the only way I found to pass data back to the template where I use a macro to render forms.
To access the passed data inside description from the template I did something like this:
{% macro render_create_form(form, form_title, enctype=None) %}
<h2>{{ form_title }}</h2>
<form action="" method="post"{% if enctype %} enctype="{{ enctype }}"{% endif %}>
{{ form.hidden_tag() }}
{% for field in form if not field.name == 'csrf_token' %}
{% set class_name = field.description.class %}
{% if field.type == "StringField" or field.type == "PasswordField" or field.type == "BooleanField" or field.type == "SelectField" %}
<div class="{{ class_name }}">{{ field.label }} {{ field }}</div>
{% elif field.type == "NumberField" %}
<div class="{{ class_name }}">{{ field.label }} {{ field(type='number', min=field.description.min, max=field.description.max, placeholder=field.description.placeholder) }}</div>
{% elif field.type == "HiddenField" %}
{{ field }}
{% elif field.type == "SubmitField" %}
<div class="{{ class_name }}">{{ field }}</div>
{% endif %}
{% endfor %}
</form>
{% endmacro %}

Related

How to add an attribute to the form tag itself and not just a field in Django?

I am having trouble stopping bots filling in spam while letting through legit users. I have a honeypot field with autocomplete="off" attribute but it doesn't seem to be working. From what i've read, the best cross browser solution is to add autocomplete="false" to the main form tag itself, e.g. <form autocomplete="false">...</form>. What is the best way to do this in Django?
Just do that in your template where the form is added.
In a template you'd typically do something like;
<form autocomplete="false">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in form.visible_fields %}
{{ field.label }}
{{ field }}
{{ field.help_text }}
{% endfor %}
So just add whatever you want to the form tag.
You should probably also have a look at including recaptcha if you've got spam problems.
And remember that v3 doesn't require any selecting street lights etc
https://pypi.org/project/django-recaptcha/

WTForms render_field() method reference for-loop variables

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

Is there anything like Generic templates in django like Generic views

Generic view have saved lot of code for me but i still have to write templates of every model. I have same code in all template i.e
<form action="/{{type}}/{{ action }}/" method="post" enctype="multipart/form-data" >
{% csrf_token %}
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</div>
{% endfor %}
<p><input type="submit" value="Submit" /></p>
</form>
i.e basically i want to have all fields from the model to add or edit.
is there any work around to have generic template automatrically
If you have template code that is identical, you can use the include tag:
{% include "foo/bar.html" %}
And the included code can be modified with variables:
{% include "name_snippet.html" with person="Jane" %}
Even if the code is different for each template (I think your example is talking about forms having different fields, not sure), you can still use includes - just make two blocks:
{% include "startform.html with some_action="post" %}
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
{{ field.field2_tag }}: {{ field2 }}
{% include "endform.html %}
There is also template inheritance, where you can define a basic template, and have all your other templates inherit from it. Inheritance is block-based, you can override blocks in the parent template with new code in the child template. It works very well.
In django, templates can be generic itself!!
You can use a diferent form for each model inside the same template using {{ form.attribute }}
Here is the django oficial doc
Look at the ModelForm helper app. It will make a form from any model which can then be used in a simple form template.

Django admin display multiple fields on the same line

I have created a model, it will automatically display all the fields from the model and display it on the admin page.
Now, I have a problem, I would like to have two fields on the same line, to do this I have to specify the fieldsets at ModelAdmin:
fieldsets = (
(None, {
'fields': (('firstname', 'lastname'),)
}),
)
Do I have to specify all the fields? Because there are many fields in the database I need to specify.
Wrap those fields on their own tuple.
class TestAdmin(admin.ModelAdmin):
fields = (
'field1',
('field2', 'field3'),
'field4'
)
In the above example, fields field2 and field3 are shown on one line.
I'm afraid there's not an easy way to do it.
One option is to override the change_form.html template for that ModelAdmin and style the form as you like.
Another alternative is to do custom ModelForm and define a field with a widget that renders two input fields, in the form's .save() method, set the widget resulting value (a tuple) to both fields.
There is an article may be useful
http://amk1.wordpress.com/2010/09/23/a-2-column-django-admin-form/
Article is quote below:
Django is great. The bundled admin interface makes it better. But as the number of items on the form gets bigger, the amount of wasted space increases because the layout is single column. Coupled with left alignment on wide-screen monitors, my users usually end their day with a condition we call “eyeballs misalignment”.
So I improvised and changed the form (and StackedInline) to a 2-up layout. No more “eyeballs misalignment”.
The corresponding template for Django 1.2.1 (/contrib/admin/templates/admin/includes/fieldset.html) looks like this, modified lines highlighted:
<fieldset class="module aligned {{ fieldset.classes }}">
{% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
{% if fieldset.description %}
<div class="description">{{ fieldset.description|safe }}</div>
{% endif %}
<table border=0 width=100%>
{% for line in fieldset %}
{% cycle '<tr>' '' %}
<td width=50%>
<div style="border-bottom:0" class="form-row{% if line.errors %} errors{% endif %}{% for field in line %} {{ field.field.name }}{% endfor %}">
{{ line.errors }}
{% for field in line %}
<div{% if not line.fields|length_is:"1" %} class="field-box"{% endif %}>
{% if field.is_checkbox %}
{{ field.field }}{{ field.label_tag }}
{% else %}
{{ field.label_tag }}
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{{ field.field }}
{% endif %}
{% endif %}
{% if field.field.field.help_text %}
<p class="help">{{ field.field.field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
</div>
</td>
{% cycle '' '</tr>' %}
{% endfor %}
</table>
</fieldset>
this has worked for me
fieldsets=(
("My Group",{"fields": (tuple(['field1','field1']),),}),
)
It's stupid, but yes, if you're going to use the fieldsets tuple-within-a-tuple method, you have to then specify all the fields that should show on your form.
Agreed, that its annoying, but its tuple of tuples from list of fields.
you can use list comprehension and change list to tuple.
Here is an example for skipping some fields, that you want to give some special attention WHILE including rest normal way.
skipped=[]
alist = [field.name for field in <model_name>._meta.fields if field.name not in skipped]
fieldsets = tuple(alist)
*** play with skipped ***
with small tweaking this should work.

Problem comparing keys in Appengine/Python

I'm trying to create a relationship between "tables" with Appengine/Python. Imagine I have a "table" for items, and a table for colors. I save the color of an item by saving the color key as an atribute of the item.
That's working well, but this particular piece of code is not working:
<select id="colorKey" name="colorKey">
{% for color in colors %}
<option value="{{ color.key }}"{% if color.key = item.colorKey %} selected="selected"{% endif %}>
{{ color.name }} - {{ item.colorKey }} - {{ color.key }}
</option>
{% endfor %}
</select>
Since the {{ item.colorKey }} and {{ color.key }} variables are actually the same chain of characters, I only can think in a problem with the types.
{{ item.colorKey }} is a string for sure. But maybe {{ color.key }} is not?
Indeed. color.key probably refers to an instance of the Key class. {% ifequal %} tries to compare a string with a Key object and the condition is never met.
Django automatically casts this object into a string when you're using {{ color.key }} but you have to provide {% if equal %} with an actual string.
You can declare a new property in your Color class that returns the key as string and then use it with {% if equal %}
class Color(db.Model):
...
#property
def keyasstring(self):
return str(self.key())
Then in your Django template:
{% ifequal color.keyasstring item.colorKey %}
{% if color.key = item.colorKey %}
One too few ==?
Django doesn't support arbitrary expressions in 'if' tags (or anything else for that matter). You need to use the 'ifequal' tag - see the docs for details.

Categories

Resources