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.
Related
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?
As you can tell, i'm new to Django, but already love it. I have a functional scraping server that scrapes 7 selects from a diagnostics page on one server. All environments I want to use this will have many of these servers with the same data to monitor performance. I would like to use this function to scrape this data from all entered target servers by input from the html template. Adding each server to monitor that will show up below the input text field from the main html page. I have completed this with a static url, but have been unsuccessful passing the different urls to scrape from the html template to the views url variable I have for the static address.
I've attempted to create forms and pass that to the html template without success, including editing the views file. Reverted the code back to the original working code to not cause more confusion.
html template:
<form method="POST">
{% csrf_token %}
<div class="field has-addons">
<div class="control is-expanded">
<input type="text" class="input"
placeholder="Relay Name">
</div>
<div class="control">
<button type="submit" class="button is-info">
Add Relay
</button>
</div>
</div>
</form>
Views.py:
import requests, bs4
from django.shortcuts import render
from django.http import HttpResponse
from bs4 import BeautifulSoup
from urllib.request import urlopen
from .models import Relay
def index(request):
url = 'hardcoded server url'
page = urlopen(url)
soup = BeautifulSoup(page, 'html.parser')
relay = 'Relay'
dic = requests.get(url.format(relay))
elema = soup.select('body > div:nth-child(13) > div.forminput')
elem1 = elema[0].text.strip()
elemb = soup.select('body > div:nth-child(14) > div.forminput')
elem2 = elemb[0].text.strip()
elemc = soup.select('body > div:nth-child(15) > div.forminput')
elem3 = elemc[0].text.strip()
elemd = soup.select('body > div:nth-child(16) > div.forminput')
elem4 = elemd[0].text.strip()
eleme = soup.select('body > div:nth-child(17) > div.forminput')
elem5 = eleme[0].text.strip()
elemf = soup.select('body > div:nth-child(18) > div.forminput')
elem6 = elemf[0].text.strip()
elemg = soup.select('body > div.versioninfo')
elem7 = elemg[0].text.strip()
#creating dictionary object
dic = {}
dic['relay'] = relay
dic['FFSL'] = elem1
dic['FFCL'] = elem2
dic['FBFQFSL'] = elem3
dic['FBQFCL'] = elem4
dic['TQQ'] = elem5
dic['SQQ'] = elem6
dic['RV'] = elem7
print(dic)
context = {'dic' : dic}
return render(request, 'relchchk/relchck.html', context)
forms.py:
from django import forms
from django.forms import ModelForm, TextInput
from .models import Relay
class RelayForm(ModelForm):
class Meta:
model = Relay
fields = ['Name', 'Relay Version', ]
widgets = {'name' : TextInput(attrs={'class' : 'input',
'placeholder' : 'url'})}
models.py:
from django.db import models
class Relay(models.Model):
name = models.CharField(max_length=45)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = 'Relays'
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index),
]
The desired result would be to manually enter any of the target servers that could accumulate to all and save in database that exists (but not important now) and have the main page show all selected. I was moving along pretty well and thought this should be simple step and probably is, but I must be missing something. Any guidance would be much appreciated.
You haven't created an instance of your form. In your views.py file, it should look something like this.
if request.method="POST":
#code to handle the form
else:
form = RelayForm()
context = {
"form": form
}
You render the form like this:
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
</form>
Read the docs to learn more about form handling and rendering.
i created a view + form that creates two widgets + a button for a user. One to select a choice and another to type something in. Now i want to redirect the user after the clicking the button to another webpage displaying his input. (Generally i want to know how to access the userinput and further use it).
This is my form:
class Eingabefeld(forms.Form):
eingabefeld = forms.CharField(label="Flight Number",max_length=20)
a = Auswahlmoeglichkeiten.objects.all()
flughafenname = forms.ModelChoiceField(label="Target Airport",queryset=a,empty_label="-------")
source = forms.CharField(
max_length=50,
widget=forms.HiddenInput(),
required=False
)
This is my views.py:
def get_eingabe(request):
log = logging.getLogger(__name__)
if request.method =="POST":
eingabe = Eingabefeld(request.POST)
log.warn(eingabe)
if eingabe.is_valid():
return HttpResponseRedirect("answerrequest")
else:
eingabe = Eingabefeld()
return render(request, "app_one/labels.html", {"eingabe": eingabe})
def answerrequestseite(request):
return render(request, "app_one/answerrequest.html")
and this is my html ( the included html in this one is just for layout):
<head>
<title>Home</title>
</head>
<form method="post" novalidate>
{% csrf_token %}
{% include "app_one/bootstrap_layout2.html" with form=eingabe %}
<div class="row">
<div class="col-sm-5"></div>
<div class="col-sm-2">
<button type="submit" class="btn btn-primary btn-block">Let's Go!</button>
</div>
<div class="col-sm-5"></div>
</div>
</form>
So basically when opening my webpage "get_eingabe" gets called, and the template gets rendered, now when clicking the button the input is validated and after successfull validation a different URL is opened which will trigger the method "answerrequestseite". Now how do i pass the userinput (eingabefeld and flughafenname) into the other method which will render the template for the second URL?
I read alot about using "request.GET" but i am not quite sure where exactly to place it and how.
After if eingabe.is_valid(): create some variable containing the values you want.
then in you redirect you need to pass those values as get argument like:
your_url/?id=123
Then you can retrieve your variable in your views.py via
request.GET.get('id')
But in your case, you don't want to pass simple id, you want to pass user_input.
One way will be to sanitize this input to make it url compatible.
Otherwise the more flexible solution is to store the values in the session.
Session (via cookie)
# views.py
# Set the session variable
request.session['you_variable_name_here'] = 'the value'
# Retrieve the session variable
var = request.session.get['you_variable_name_here']
https://docs.djangoproject.com/en/2.2/topics/http/sessions/
For your exemple in the first view:
if eingabe.is_valid():
eingabefeld = eingabe.cleaned_data.get('eingabefeld')
flughafenname = eingabe.cleaned_data.get('flughafenname')
request.session['eingabefeld'] = eingabefeld
request.session['flughafenname'] = flughafenname.pk
return HttpResponseRedirect("answerrequest")
In the second view:
def answerrequestseite(request):
eingabefeld = request.session.get('eingabefeld')
flughafenname_pk = request.session.get('flughafenname')
flughafenname = YourFlughafennameModel.objects.get(pk=flughafenname_pk)
return render(request, "app_one/answerrequest.html",{'eingabefeld':eingabefeld,'flughafenname':flughafenname})
I'm sure the answer is right there and I'm not seeing it. How can I render a RichTextBlock to remove the wrapping <div class="rich-text">?
{% include_block block %} and {{ block.value }} both give the wrapping div.
Unfortunately this is hard-coded and can't currently be overridden - see https://github.com/wagtail/wagtail/issues/1214.
I solved this by creating a custom template tag
In your project create a file in your templatetags directory (e.g. templatetags/wagtailcustom_tags.py) with content along the following.
from django import template
from django.utils.safestring import mark_safe
from wagtail.core.rich_text import RichText, expand_db_html
register = template.Library()
#register.filter
def richtext_withclasses(value, classes):
if isinstance(value, RichText):
html = expand_db_html(value.source)
elif isinstance(value, str):
html = expand_db_html(value)
elif value is None:
html = ""
else:
raise TypeError(
"'richtext_withclasses' template filter received an invalid value; expected string, got {}.".format(
type(value)
)
)
return mark_safe('<div class="' + classes + '">' + html + "</div>")
Then in your templates load the template tag
{% load wagtailcustom_tags %}
and render richtext fields with the custom classes (or no classes at all)
{{ myfield | richtext_withclasses:"my custom class" }}
I'm working on a simple app that act similar to wikipedia using the tutorial "create a wiki in 20 minutes " from showmedo .
The app works by you create a page e.g dog if it doesn't exist then you add all the info about dogs and it display the dog and details of the dog.
When I try to edit the same page . the page doesn't get updated on the main page either does it get updated on my admin page.
I think the problem is with my save_page function and edit_page function.
So when I create a page , it works
but when I try to retrieve the page and save it . It doesn't save .
The only method that works is editing the page by admin.
I think the problem is here
def save_page(request, page_name):
content = request.POST.get('content', 'this is the default')
try:
page = Page.objects.get(pk=page_name)
page.content = content
This is the website
http://tafe.pythonanywhere.com/wikicamp/Dogs/
My views.py
from wiki.models import Page
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
from django.template import RequestContext
def view_page(request,page_name):
try:
page = Page.objects.get(pk=page_name)
except Page.DoesNotExist:
return render_to_response("create.html",{"page_name":page_name})
content = page.content
return render_to_response("view.html",{"page_name":page_name , "content":content}, context_instance=RequestContext(request))
def edit_page(request,page_name):
try:
page = Page.objects.get(pk=page_name)
content = page.content
except Page.DoesNotExist:
content = ""
return render_to_response("edit.html",{"page_name":page_name, "content":content}, context_instance=RequestContext(request))
def save_page(request, page_name):
content = request.POST.get('content', 'this is the default')
try:
page = Page.objects.get(pk=page_name)
page.content = content
except Page.DoesNotExist:
page = Page(name=page_name, content=content)
page.save()
return HttpResponseRedirect("/wikicamp/" + page_name + "/")
My create.html
<html>
<head>
<title>{{page.name}} - Create </title>
</head>
<body>
<h1>{{page_name}} </h1>
This page does not exist. Create?
</body>
</html>
My edit.html
<html>
<head>
<title>{{page_name - Editing</title>
</head>
<body>
<h1>Editing {{page_name}} </h1>
<form method = "post" action="{% url wiki:save page_name %}"> {% csrf_token %}
<textarea name="content" rows="20" cols="60"> {{content}}
</textarea><br/>
<input type="submit" value="Save Page"/>
</form>
</body>
</html>
My view.html
<html>
<head>
<title>{{page_name}}</title>
</head>
<body>
<h1>{{page_name}} </h1>
{{content}}
<hr/>
Edit this page ?
</body>
</html>
try:
page = Page.objects.get(pk=page_name)
page.content = content
except Page.DoesNotExist:
page = Page(name=page_name, content=content)
page.save()
Two problems I see here. First, you are trying to retrieve the page by the page_name as the primary key, when you should be searching on the name attribute, and secondly after you have fetched the page successfully and updated its content, you forget to save it.
Since this is a common pattern, there is a shortcut in django get_or_create, it works like this:
page, created = Page.objects.get_or_create(name=page_name)
if created:
# new page was created
else:
# existing page was retrieved
In your scenario, you just want to fetch and update the contents in either scenario. So we don't need to use the created variable:
page, created = Page.objects.get_or_create(name=page_name)
page.content = content
page.save()
You're not saving the page in save_page, only when it doesn't exist. Try something like:
def save_page(request, page_name):
content = request.POST.get('content', 'this is the default')
try:
page = Page.objects.get(pk=page_name)
page.content = content
page.save()
except Page.DoesNotExist:
page = Page(name=page_name, content=content)
page.save()
return HttpResponseRedirect("/wikicamp/" + page_name + "/")
This is a quick ugly fix, I recommend looking into forms and class based views.
Two things I noticed with this:
try:
page = Page.objects.get(pk=page_name)
page.content = content
except Page.DoesNotExist:
page = Page(name=page_name, content=content)
page.save()
1) in the try block you are querying by pk and in the except you are setting the name.
2) you are not saving in the try block.
try this:
try:
page = Page.objects.get(name=page_name)
except Page.DoesNotExist:
page = Page(name=page_name)
page.content = content
page.save()
you forgot to put save()
def save_page(request, page_name):
content = request.POST.get('content', 'this is the default')
try:
page = Page.objects.get(pk=page_name)
page.content = content
page.save()
except Page.DoesNotExist:
page = Page(name=page_name, content=content)
page.save()
return HttpResponseRedirect("/wikicamp/" + page_name + "/")