Dynamically generate flask-wtf form - flask

I have to make a lot of RadioFields, and I thought it would be good to dynamically generate them, but I can't get the code working. I'm using Flask and flask-wtf.
Form definition:
from flask_wtf import FlaskForm
from wtforms import RadioField, SubmitField
class GenerateForm(FlaskForm):
def binary_generator(self, label_text, yes_text, no_text):
return RadioField(label_text, choices=[(1, yes_text), (0, no_text)])
submit = SubmitField('submit')
Flask app:
import GeneratorForm
form = GeneratorForm
form.radio_one = form.binary_generator('test label', 'yes', 'no')
render_template('file.html', form=form)
Jinja:
{{ form.radio_one.label }}
{{ form.radio_one(style="list-style: none") }}
The Jinja fails with: wtforms.fields.core.UnboundField object has no attribute label
So it looks like the class binary_generator function is working ok, but not constructing the form properly?

Do you need that binary_generator method in GenerateForm ?
Your GenerateForm could look something like this:
from flask_wtf import FlaskForm
from wtforms import RadioField, SubmitField
class GenerateForm(FlaskForm):
radio_fields = RadioField('', choices=[])
submit = SubmitField('submit')
And in your flask application, you need to instantiate your form like this:
import GeneratorForm
form = GeneratorForm() # Instantiate it
form.radio_fields.label = 'Label Example'
form.radio_fields.choices = [('value_1', 'description'), ('value_2', 'description')]
render_template('file.html', form=form)
To render your form in file.html:
<form method="post">
{{ form.hidden_tag() }}
{{ form.radio_fields.label }}
{{ form.radio_fields(style='list-style: none') }}
{{ form.submit }}
</form>

The base class FlaskForm is fairly particular about its construction. To define a dynamic form add the parameters after the base class has instantiated with super(). I.e.
class GenerateForm(FlaskForm):
radio_fields = RadioField('', choices=[])
submit = SubmitField('submit')
def __init__(self, label, choices):
super().__init__()
self.radio_fields.choices = label
self.radio_fields.choices = choices
Then you can instantiate the form with:
GenerateForm('My Label', [('val', 'desc'), ('val2', 'desc2')])]
You can also create a Form Factory in the following way:
def Form(n, *args):
class FormGenerator(FlaskForm):
submit = SubmitField('submit')
for i in range(n):
setattr(FormGenerator, RadioField(args[i][0], choices=args[i][1])
return FormGenerator()
Then you can instantiate the form with:
Form(2, *(('rad1', [('v1', 'd1'), ('v2', 'd2')]), ('rad2', [('v1', 'd1'), ('v2', 'd2')])))

Related

Django ModelForm submit button not working

I am trying to make a Django ModelForm that retrieves data from my database using the GET method. When I click the submit button nothing happens. What am I doing wrong?
HTML doc
<form role="form" action="" method="GET" id="form-map" class="form-map form-search">
<h2>Search Properties</h2>
{% csrf_token %}
{{ form.as_p }}
<input type="submit" action= "" class="btn btn-default" value="Submit">
<input type="reset" class="btn btn-default" value="Reset">
</form><!-- /#form-map -->
forms.py
from django import forms
from .models import StLouisCitySale208
from django.forms import ModelForm, ModelMultipleChoiceField
class StLouisCitySale208Form(ModelForm):
required_css_class = 'form-group'
landuse = forms.ModelMultipleChoiceField(label='Land use', widget=forms.SelectMultiple, queryset=StLouisCitySale208.objects.values_list('landuse', flat=True).distinct())
neighborho =forms.ModelMultipleChoiceField(label='Neighborhood',widget=forms.SelectMultiple, queryset=StLouisCitySale208.objects.values_list('neighborho', flat=True).distinct())
policedist = forms.ModelMultipleChoiceField(label='Police district',widget=forms.SelectMultiple,queryset=StLouisCitySale208.objects.values_list('policedist', flat=True).distinct())
class Meta:
model = StLouisCitySale208
fields = ['landuse', 'neighborho', 'policedist', 'precinct20','vacantland', 'ward20', 'zip', 'zoning','asmtimprov', 'asmtland', 'asmttotal', 'frontage', 'landarea','numbldgs', 'numunits']
views.py
from django.views.generic import FormView, TemplateView
from .forms import StLouisCitySale208Form
class StLouisCitySale208View(FormView):
form_class = StLouisCitySale208Form
template_name = 'maps/StlouiscitySale208.html'
maps/urls.py
from django.urls import path
from .views import StLouisCitySale208View, ComingSoonView
app_name = 'maps'
urlpatterns = [
path("maps/stlouiscitysale208",StLouisCitySale208View.as_view(),name="stlouiscitysale208"),
path('maps/coming_soon', ComingSoonView.as_view(), name="coming_soon")
]
You need a get method in your class to tell the button what to do.
class MyView(View):
def get(self, request):
# <view logic>
return HttpResponse('result')
https://docs.djangoproject.com/en/4.0/topics/class-based-views/intro/
Your form currently has method="GET" which is something you'd use for search, or some other operation which doesn't change the state of the application.
If that's what you want to do, and you're using existing data, through a form, to allow users to query the database, then you'll need to implement a get method on the view in order to implement the logic for this. The following should help with the get() method for FormView;
http://ccbv.co.uk/projects/Django/4.0/django.views.generic.edit/FormView/#get
It sounds like you're hoping to create objects using your model form, so change that to method="POST" and you'll at least allow the application to create your object. There may be more to debug at that point, but you need to start by sending data to the server.

DB based Dynamic Form Generation in Flask

Recently I've decide to create form creation form in Flask web app. After searching form creation found Formfield, FieldList classes in flask wtf forms and I can create the form with these classes. but it doesn't provide that I want to.
First- I am going to create a form creation form which will be help me to create form and fields on management interface.
Second- I want to be able to add the fields, not the same type of field, all different kind, such as (booleanField, StringField, IntegerField, DateTimeField etc.) because, in the form there could be different type of fields for specific reason.
Third- I want to retreive this form whenever I want to use in my view
On the DB models side;
class Form(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.StringField)
fields = db.relationship('FormFields', backref='forms', lazy=True)
class FormFields(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.StringField)
field_type = db.Column(db.StringField)
form_id = db.Column(db.Integer, db.ForeignKey('forms.id'), nullable=False)
And othr tables for StrinField, BooleanField, TextField, etc. etc. should be connected this field model, and when I save the data over this created form, these data should be saved int the correct tables
The reason I am searching this, because I don't want to hardcode the Forms and fields in the code, when I need to new form or field I don't want to update code itself, it should be dynamically updated on the database.
And I want to use sqlalchemy based form creation from management page. And this will help to create anytime new form and relate the fields to the form. And on the internet still I didn't find the these style form creation for Flask, almost all of them creating dynamic for with same type of fields
Any ideas?
Last a couple of weeks I was search how to create dynamically flask form based on models
And #nick-shebanov has been redirect me to another approach EAV impelemntation, which is really diffucult to implement. I've tried :)
And decide to create form based on dictionary, and intend to populate the related attributes from the model and pass it to form as dictionary.
What I've done so far;
# app.py file
from flask_wtf import Form
from flask import Flask, render_template, request, flash
from flask_wtf import FlaskForm
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from wtforms import TextField, IntegerField, HiddenField, StringField, TextAreaField, SubmitField, RadioField,SelectField
from wtforms import validators, ValidationError
app = Flask(__name__)
app.secret_key = 'secret123'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sqlite.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
migrate = Migrate(app, db)
# form class with static fields
class DynamicForm(FlaskForm):
form_type = HiddenField(default='FormType', render_kw={ 'type':'hidden' })
# name = StringField()
#app.route('/', methods=['GET', 'POST'])
def index():
fields = {
'username': 'Username',
'first_name': 'Fisrt Name',
'last_name': 'Last Name',
'email': 'Email',
'mobile_phone': 'Mobile Phone'
}
for key, value in fields.items():
setattr(DynamicForm, key, StringField(value))
form = DynamicForm()
if request.method == 'POST':
# print(dir(request.form))
# print(request.form)
dict = request.form.to_dict()
# print(dict.keys())
print(request.form.to_dict())
return render_template('index.html', form=form)
if __name__ == '__main__':
app.run(debug = True)
# index.html template
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<form method="POST">
{{ form.csrf_token }}
{{ form.form_type }}
{% for field in form if field.name != 'csrf_token' %}
{% if field.name != 'form_type' %}
<div>
{{ field.label() }}
{{ field() }}
{% for error in field.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
</div>
{% endif %}
{% endfor %}
<input type="submit" value="Go">
</form>
</body>
</html>
Now I can see my all fields has been rendered including hidden field. When I fill the form and post the data, I can capture it.
But still I didn't achieve to implement save the captured data into database like vertical DB modelling style yet
Here is my simple approach of DB modelling
see image here
Is there suggestions?

How to create a single checkbox in WTForms?

Most of the info I find online is for multiple checkboxes. I just want 1.
I have:
class CategoryForm(FlaskForm):
category = StringField('category',validators=[DataRequired()])
checkbox = BooleanField('Private?')
#app.route('/category/<categoryid>',methods=('GET','POST'))
def category(categoryid):
category = Category.query.get(categoryid)
if request.method == 'POST':
if request.form.get('category'):
category.name = request.form['category']
category.private = request.form['private']
db.session.add(category)
db.session.commit()
return redirect(url_for('index'))
c_form = CategoryForm()
c_form.category.data = category.name
return render_template('category.html',form =c_form,category=category)
And my 'category' template:
<form method="post">
{{ form.hidden_tag() }}
{{ form.checkbox }}
<button type="submit">Go!</button>
</form>
right now my browser renders this:
<peewee.BooleanField object at 0x105122ad0> Go!
Obviously I would like it to render the checkbox instead. How can I do this? Do I need a widget ?
I'm having the impression that you're using the fields from peewee as the fields in your form, that isn't going to work. The most likely case is that you're importing both and one import is overwriting the other.
If you need to have both the model and the form in the same file, use aliases.
from peewee import BooleanField as PeeBool
from wtforms import BooleanField as WTBool

Populate a PasswordField in wtforms

Is it possible to populate a password field in wtforms in flask?
I've tried this:
capform = RECAPTCHA_Form()
capform.username.data = username
capform.password.data = password
The form is defined like:
class RECAPTCHA_Form(Form):
username = TextField('username', validators=[DataRequired()])
password = PasswordField('password', validators=[DataRequired()])
remember_me = BooleanField('Remember me.')
recaptcha = RecaptchaField()
The template looks like this:
<form method="POST" action="">
{{ form.hidden_tag() }}
{{ form.username(size=20) }}
{{ form.password(size=20) }}
{% for error in form.recaptcha.errors %}
<p>{{ error }}</p>
{% endfor %}
{{ form.recaptcha }}
<input type="submit" value="Go">
</form>
I have tried to change the PasswordField to a TextField, and then it works.
Is there some special limitation to populating PasswordFields in wtforms?
Update: After looking through the WTForms docs I found an even better solution. There is a widget arg.
from wtforms import StringField
from wtforms.widgets import PasswordInput
class MyForm(Form):
# ...
password = StringField('Password', widget=PasswordInput(hide_value=False))
As yuji-tomita-tomita pointed out, the PasswordInput class (source) has an hide_value argument, however the constructor of PasswordField (source) does not forward it to the PasswordInput. Here is a PasswordField class that initializes PasswordInput with hide_value=False:
from wtforms import widgets
from wtforms.fields.core import StringField
class PasswordField(StringField):
"""
Original source: https://github.com/wtforms/wtforms/blob/2.0.2/wtforms/fields/simple.py#L35-L42
A StringField, except renders an ``<input type="password">``.
Also, whatever value is accepted by this field is not rendered back
to the browser like normal fields.
"""
widget = widgets.PasswordInput(hide_value=False)
Something I've found with Flask, and Flask apps in general, is that the source is the documentation. Indeed, it looks like by default you cannot populate the field. You can pass an argument hide_value to prevent this behavior.
This is a good call, since if you can populate the field, you have access to the raw password... which could be dangerous.
class PasswordInput(Input):
"""
Render a password input.
For security purposes, this field will not reproduce the value on a form
submit by default. To have the value filled in, set `hide_value` to
`False`.
"""
input_type = 'password'
def __init__(self, hide_value=True):
self.hide_value = hide_value
def __call__(self, field, **kwargs):
if self.hide_value:
kwargs['value'] = ''
return super(
I believe there is an easier way to access the data of the password field, without usinghide_value. In your view, simply add in the request data as an argument to the form's constructor:
from flask import request
capform = RECAPTCHA_Form(request.form)
capform.username.data = username
capform.password.data = password
This should make the password input available for validation, and to be used in testing if desired.

field's verbose_name in templates

Suppose I have a model:
from django.db import models
class Test(models.Model):
name=models.CharField(max_length=255, verbose_name=u'custom name')
How do I get my model's field's verbose name in templates? The following doesn't work:
{{ test_instance.name.verbose_name }}
I would very much appreciate the solution, something on lines as we do when using forms, using label attribute in template:
{{ form_field.label }}
You can use following python code for this
Test._meta.get_field("name").verbose_name.title()
If you want to use this in template then it will be best to register template tag for this. Create a templatetags folder inside your app containing two files (__init__.py and verbose_names.py).Put following code in verbose_names.py:
from django import template
register = template.Library()
#register.simple_tag
def get_verbose_field_name(instance, field_name):
"""
Returns verbose_name for a field.
"""
return instance._meta.get_field(field_name).verbose_name.title()
Now you can use this template tag in your template after loading the library like this:
{% load verbose_names %}
{% get_verbose_field_name test_instance "name" %}
You can read about Custom template tags in official django documentation.
The method in the accepted answer is awesome!
And maybe you'll like this if you want to generate a field list.
Adding an iterable to the class Test makes it convenient to list fields' verbose name and value.
Model
class Test(models.Model):
...
def __iter__(self):
for field in self._meta.fields:
yield (field.verbose_name, field.value_to_string(self))
Template
{% for field, val in test_instance %}
<div>
<label>{{ field }}:</label>
<p>{{ val }}</p>
</div>
{% endfor %}
based on this answer https://stackoverflow.com/a/14498938 .in Django Model i added
class Meta:
app_name = 'myapp'
in listview i have
from django.core import serializers
context['data'] = serializers.serialize( "python", self.get_queryset() )
inside mylist.html i have
{% for field, value in data.0.fields.items %}
<th style="text-align:center;">{% get_verbose_field_name data.0.model field %}</th>
{% endfor %}
in filter:
from django import template
register = template.Library()
from .models import Mymodel
#register.simple_tag
def get_verbose_field_name(instance, field_name):
"""
Returns verbose_name for a field.
"""
myinstance = eval(instance.split('.')[1].title())
return myinstance._meta.get_field(field_name).verbose_name.title()
instance in the abbove filter for the specific example is myapp.mymodel i evalute instance into model object and the i return field verbose name
it works in django 1.9
It's probably too late for an answer but I had the same issue until I realised that I caused the problem by overriding the fields in the form.py (self.fields['fieldname'] = ..). If you do that you also need to set a label otherwise it uses a label derived from the fieldname.
Hope this quick reply makes sense.