DB based Dynamic Form Generation in Flask - 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?

Related

Flask: dynamically populated form from databse content

basically I want to do something quite simple: I want to create a form for deleting entries in a database.
the template is creating a html table with all entries without any trouble. My problem now is: how to convert this to a form with a link in every row.
Of course I could do the manual way of writing html code with a link. But is there a more "flaskish" way? I'm already using wtforms and sqlalchemy
My route:
#app.route('/admin', methods=['GET', 'POST'])
#htpasswd.required
def admin(user):
orders = Order.query.all()
return render_template(
'admin.html', title="AdminPanel", orders=orders
)
The model:
class Order(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), index=True, unique=True)
The template:
{% for order in orders %}
<tr>
<td>{{order.email}}</td>
<td><i class="fa fa-trash" aria-hidden="true"></i></td>
</tr>
{% endfor %}
You should use a different route for deletion and not the same one you are using to render the template. Also you do not need a form for the deletion task. You can use get parameters for that and links like you have tried
add this to your routes:
from flask import redirect, url_for
from app import db # you should import your db or db session instance so you can commit the deletion change this line to wherever your db or db session instance is
#app.route('/delete/<order_id>', methods=['GET', 'POST'])
#htpasswd.required
def delete(order_id):
orders = Order.query.filter(Order.id == order_id).delete()
db.commit()
return redirect(url_for('admin'))
Basically you will perform a delete and then redirect back to the admin route with the above code
Your template file should be changed to:
{% for order in orders %}
<tr>
<td>{{order.email}}</td>
<td><i class="fa fa-trash" aria-hidden="true"></i></td>
</tr>
{% endfor %}

Flask WTForms: Field Values Not Being Sent

I am giving my first go at bringing up a small website with flask, bootstrap, and wtforms. I am running into an issue where my wtforms fields are not sending values when submitted. I have a very basic wtform defined as follows:
class GeneralForm(Form):
boolean_val = BooleanField('Boolean')
a_float = FloatField('Severity')
submit = SubmitField('Submit')
I also have an html template which I render the form in:
{% block content %}
<div class="col-md-12">
{{form|render_form()}}
</div>
{%- endblock %}
Everything renders fine. When the form is submitted, I check it like so:
#app.route('/form', methods=['GET', 'POST'])
def do_form():
general_form = GeneralForm()
if general_form.validate_on_submit():
return "Value {}".format(general_form.boolean_val.data)
return render_template('symptomsform.html', form=general_form)
What I find is that the value for the boolean field is always the default value (false). I also notice that only a default value is provided when I check the float field. I checked the html for the page, I found that the input fields looked like:
<label for="boolean_val">
<input type="checkbox">Boolean
</label>
What stood out to me is the input field was missing a name in its tag. So, I manually stuck the name in and my test app was receiving the actual value of the checkbox.
My question is: what am I doing wrong with creating the input fields such that the values of the fields are not being sent with the form submission? I suspect the input fields should have names. So, why are names not being generated on the input fields?
Below is a sample script with the fixes,
app.py
from flask import Flask, render_template
from flask_wtf import Form
from wtforms import SubmitField, BooleanField, FloatField
from flask import request
from jinja2 import filters
app = Flask(__name__)
app.config['SECRET_KEY'] = 'catchmeifyoucan'
class GeneralForm(Form):
boolean_val = BooleanField('Boolean')
a_float = FloatField('Severity')
submit = SubmitField('Submit')
#app.route('/wtforms', methods=['GET', 'POST'])
def debug_wtforms():
form = GeneralForm()
if request.method == 'POST' and form.validate_on_submit():
print(form.boolean_val.data)
print(form.a_float.data)
return render_template('index.html', form=form)
# This is a jinja2 custom filter for rendering a form
#app.template_filter()
def render_form(form, action='/', method='post'):
temp = ''
start_form = "<form action=" + action + " method=" + method + ">"
end_form = "</form>"
temp += start_form
for el in form:
temp += str(el())
temp += end_form
return filters.do_mark_safe(temp)
if __name__ == "__main__":
app.run(debug=True)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Wtforms debug</title>
</head>
<body>
{{ form | render_form(action=url_for('debug_wtforms')) }}
</body>
</html>
The custom jinja2 filter given below helps you to render the form with the name attribute,
# This is a jinja2 custom filter for rendering a form
#app.template_filter()
def render_form(form, action='/', method='post'):
temp = ''
start_form = "<form action=" + action + " method=" + method + ">"
end_form = "</form>"
temp += start_form
for el in form:
temp += str(el())
temp += end_form
return filters.do_mark_safe(temp)
This filter has two default arguments, action and method which could be passed if you want to modify the form method and action.
The current filter won't display the form field label, but if you want to display form field label, you can access it using str(el.label()) and append it to the temp variable in the custom filter.
Note : You can make necessary tweaks to the custom filter to modify how the form must be displayed
I hope this helps.

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

Creating flask form with selects from more than one table

I have seen a large number of tutorials that show login forms with flask and flash-wtf but none where multiple select boxes are populated from database table values.
This is what I am trying to do:
A simple registration form:
First name
Last name
Address Line 1
Address Line 2
City
State Id (populated from states library query of Id,state)
Country Id (populated from countries library query of country, id)
Sample code or a link to a walk through would be greatly appreciated.
I tried to find a explanation for how to do this and couldn't find one. So I'm going to write one here. This is how I do things, there's probably better ways to go about it.
Source Code
You can download the full source code for this tutorial on my github account. I'm pretty much copying and pasting from the source code, but just in case github dies some day here we go.
Configuration
Need to configure our application and the database connection. In most cases
you probably want to load all of this from a configuration file.
In this tutorial we're just going to use a basic sqlalchemy test database.
app = Flask(__name__)
app.config['SECRET_KEY'] = 'Insert_random_string_here'
Set this configuration to True if you want to see all of the SQL generated.
app.config['SQLALCHEMY_ECHO'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
WTForms configuration strings
app.config['WTF_CSRF_ENABLED'] = True
CSRF tokens are important. Read more about them here,
https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet
app.config['WTF_CSRF_SECRET_KEY'] = 'Insert_random_string_here'
db = SQLAlchemy(app)
SQLALchemy Model
Next we need to create our model classes that will be used during the creation
of the database and also when we want to manipulate the database. This should
normally be it's own seperate file.
I'm importanting from here as if this was it's own seperate file.
Normally you'd have to import doing something like
from application import db
class RegisteredUser(db.Model):
"""
loads and pushes registered user data after they have signed up.
SQLalchemy ORM table object which is used to load, and push, data from the
server memory scope to, and from, the database scope.
"""
__tablename__ = "RegisteredUser"
#all of the columns in the database.
registered_id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(70))
last_name = db.Column(db.String(70))
address_line_one = db.Column(db.String(256))
address_line_two = db.Column(db.String(256))
city = db.Column(db.String(50))
"""
Now we're going to create all of the foreign keys for the RegisteredUser
table. The db.relationship section allows us to easily and automatically
join the other tables with registeredUser. The Join will only take place
if you attempt to access columns from the State or country table.
For more on Foreign keys using SQLAlchemy go to
"""
state_id = db.Column(
db.Integer,
db.ForeignKey('State.state_id'),
nullable=False)
#retrives the users name for display purposes.
state_by = db.relationship(
'State',
foreign_keys=[state_id],
backref=db.backref('State', lazy='dynamic'))
country_id = db.Column(
db.Integer,
db.ForeignKey('Country.country_id'),
nullable=False)
#retrives the users name for display purposes.
country_by = db.relationship(
'Country',
foreign_keys=[country_id],)
#this is the method and function style I've chosen when lines are too long
def __init__(
self,
first_name,
last_name,
address_line_one,
address_line_two,
city,
state_id,
country_id):
"""
Used to create a RegisteredUser object in the python server scope
We will be calling these init functions every time we use
RegisteredUser() as a 'function' call. It will create a SQLalchemy ORM
object for us.
"""
self.first_name = first_name
self.last_name = last_name
self.address_line_one = address_line_one
self.address_line_two = address_line_two
self.city = city
self.state_id = state_id
self.country_id = country_id
class State(db.Model): # pylint: disable-msg=R0903
"""
Holds State names for the database to load during the registration page.
SQLalchemy ORM table object which is used to load, and push, data from the
server memory scope to, and from, the database scope.
"""
__tablename__ = "State"
state_id = db.Column(db.Integer, primary_key=True)
state_name = db.Column(db.String(10), unique=True)
def __init__(self, state_name):
"""
Used to create a State object in the python server scope
"""
self.state_name = state_name
class Country(db.Model): # pylint: disable-msg=R0903
"""
Holds Country names for the database to load during the registration page.
SQLalchemy ORM table object which is used to load, and push, data from the
server memory scope to, and from, the database scope.
"""
__tablename__ = "Country"
country_id = db.Column(db.Integer, primary_key=True)
#longest country length is currently 163 letters
country_name = db.Column(db.String(256), unique=True)
def __init__(self, country_name):
"""
Used to create a Country object in the python server scope
"""
self.country_name = country_name
def create_example_data():
"""
Generates all of the demo data to be used later in the tutorial. This is
how we can use our ORM objects to push data to the database.
NOTE: create_example_data is called at the very bottom of the file.
"""
#Create a bunch of state models and add them to the current session.
#Note, this does not add rows to the database. We'll commit them later.
state_model = State(state_name="WA")
db.session.add(state_model)
state_model = State(state_name="AK")
db.session.add(state_model)
state_model = State(state_name="LA")
db.session.add(state_model)
#Normally I load this data from very large CVS or json files and run This
#sort of thing through a for loop.
country_model = Country("USA")
db.session.add(country_model)
country_model = Country("Some_Made_Up_Place")
db.session.add(country_model)
# Interesting Note: things will be commited in reverse order from when they
# were added.
try:
db.session.commit()
except IntegrityError as e:
print("attempted to push data to database. Not first run. continuing\
as normal")
WTForm
Now we're going to make our WTForms objects. These will have the data aquired
from the database placed on them, then we will pass them to our template files
where we will render them.
I'm importanting from here as if this was it's own seperate file.
import wtforms
import wtforms.validators as validators
from flask.ext.wtf import Form
class RegistrationForm(Form):
"""
This Form class contains all of the fileds that make up our registration
Form.
"""
#Get all of the text fields out of the way.
first_name_field = wtforms.TextField(
label="First Name",
validators=[validators.Length(max=70), validators.Required()])
last_name_field = wtforms.TextField(
label="Last Name",
validators=[validators.Length(max=70), validators.Required()])
address_line_one_field = wtforms.TextField(
label="Address",
validators=[validators.Length(max=256), validators.Required()])
address_line_two_field = wtforms.TextField(
label="Second Address",
validators=[validators.Length(max=256), ])
city_field = wtforms.TextField(
label="City",
validators=[validators.Length(max=50), validators.Required()])
# Now let's set all of our select fields.
state_select_field = wtforms.SelectField(label="State", coerce=int)
country_select_field = wtforms.SelectField(label="Country", coerce=int)
Views
import flask
def populate_form_choices(registration_form):
"""
Pulls choices from the database to populate our select fields.
"""
states = State.query.all()
countries = Country.query.all()
state_names = []
for state in states:
state_names.append(state.state_name)
#choices need to come in the form of a list comprised of enumerated lists
#example [('cpp', 'C++'), ('py', 'Python'), ('text', 'Plain Text')]
state_choices = list(enumerate(state_names))
country_names = []
for country in countries:
country_names.append(country.country_name)
country_choices = list(enumerate(country_names))
#now that we've built our choices, we need to set them.
registration_form.state_select_field.choices = state_choices
registration_form.country_select_field.choices = country_choices
#app.route('/', methods=['GET', 'POST'])
def demonstration():
"""
This will render a template that displays all of the form objects if it's
a Get request. If the use is attempting to Post then this view will push
the data to the database.
"""
#this parts a little hard to understand. flask-wtforms does an implicit
#call each time you create a form object. It attempts to see if there's a
#request.form object in this session and if there is it adds the data from
#the request to the form object.
registration_form = RegistrationForm()
#Before we attempt to validate our form data we have to set our select
#field choices. This is just something you need to do if you're going to
#use WTForms, even if it seems silly.
populate_form_choices(registration_form)
#This means that if we're not sending a post request then this if statement
#will always fail. So then we just move on to render the template normally.
if flask.request.method == 'POST' and registration_form.validate():
#If we're making a post request and we passed all the validators then
#create a registered user model and push that model to the database.
registered_user = RegisteredUser(
first_name=registration_form.data['first_name_field'],
last_name=registration_form.data['last_name_field'],
address_line_one=registration_form.data['address_line_one_field'],
address_line_two=registration_form.data['address_line_two_field'],
city=registration_form.data['city_field'],
state_id=registration_form.data['state_select_field'],
country_id=registration_form.data['country_select_field'],)
db.session.add(registered_user)
db.session.commit()
return flask.render_template(
template_name_or_list='success.html',
registration_form=registration_form,)
return flask.render_template(
template_name_or_list='registration.html',
registration_form=registration_form,)
runserver.py
Finally, this is for development purposes only. I normally have this in a
file called RunServer.py. For actually delivering your application you should
run behind a web server of some kind (Apache, Nginix, Heroku).
if __name__ == '__main__':
db.create_all()
create_example_data()
app.run(debug=True)
Templates
in macros.html
{% macro render_field(field) %}
<dt>{{ field.label }}
<dd>{{ field(**kwargs)|safe }}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</dd>
{% endmacro %}
{% macro render_data(field) %}
<dt>{{ field.label }}
<dd>{{ field.data|safe }}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</dd>
{% endmacro %}
In registration.html
{% from "macros.html" import render_field %}
<form method=post action="/">
{{registration_form.hidden_tag()}}
<dl>
{{ render_field(registration_form.first_name_field) }}
{{ render_field(registration_form.last_name_field) }}
{{ render_field(registration_form.address_line_one_field) }}
{{ render_field(registration_form.address_line_two_field) }}
{{ render_field(registration_form.city_field) }}
{{ render_field(registration_form.state_select_field) }}
{{ render_field(registration_form.country_select_field) }}
</dl>
<p><input type=submit value=Register>
</form>
Finally, in success.html
{% from "macros.html" import render_data %}
<h1> This data was saved to the database! </h1>
<form method=post action="/">
{{registration_form.hidden_tag()}}
<dl>
{{ render_data(registration_form.first_name_field) }}
{{ render_data(registration_form.last_name_field) }}
{{ render_data(registration_form.address_line_one_field) }}
{{ render_data(registration_form.address_line_two_field) }}
{{ render_data(registration_form.city_field) }}
{{ render_data(registration_form.state_select_field) }}
{{ render_data(registration_form.country_select_field) }}
</dl>
<p><input type=submit value=Register>
</form>

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.