Referencing the following Form.
CONTROL_CHOICES = Session.query(schema.OfficeType).order_by(schema.OfficeType.descr).all()
CONTROL_CHOICES = [(office.id, office.descr) for office in CONTROL_CHOICES]
class ControlForm(Form):
institution = RadioField('Institution', choices=CONTROL_CHOICES)
date = DateField('Date')
submit = SubmitField('SUBMIT')
Simple HTML
<form action="composition_profile" method="get">
{{control_form.hidden_tag()}}
{{control_form.institution.label}}
{{control_form.institution}}
{{control_form.date.label}}
{{control_form.date}}
{{control_form.submit}}
</form>
The radio fields are successfully printed.
However, when filling out the form, validate_on_submit() prints no errors, but does not execute code within the if
validate() prints the following error.
{'institution': ['Not a valid choice']}
#app.route('/composition_profile', methods=['GET', 'POST'])
def composition_profiles():
if request.method == 'GET':
if request.args.get('institution') and request.args.get('date'):
form = ControlForm(request.args)
print(form.institution.data)
if form.validate():
print('terms')
print(form.errors)
Any idea how it is resulting in an improper choice? Im not really sure what's going on. Changing it to QuerySelectField works, but I'd like the radio functionality
Consider re-factoring:
def my_view():
class F(MyBaseForm):
pass
F.username = TextField('username')
for name in iterate_some_model_dynamically():
setattr(F, name, TextField(name.title()))
form = F(request.POST, ...)
# do view stuff
Source: http://wtforms.simplecodes.com/docs/1.0.1/specific_problems.html#dynamic-form-composition
The Issue appears to have been caused due to the fact that WTForm expects the Value field, to be a String as opposed to an Integer
Changing
CONTROL_CHOICES = [(office.id, office.descr) for office in CONTROL_CHOICES]
To
CONTROL_CHOICES = [(str(office.id), office.descr) for office in CONTROL_CHOICES]
Alleviates the issue
Related
I'm in the process of building my first web application in python using flask and every step of the way is essentially a new lesson for me. All of my coding experience is in Visual Basic and vb.Net, so this is still all quite new for me.
What I am trying to do is actually incredibly simple, but I am struggling with a way to do it, let alone determine the best way to do it. Also, forgive me if my python/flask vocabulary is wrong.. I don't quite speak "the language".
I have a views.route for a journal entry which renders an html template where the user can make a journal entry. On this template, there is also date Picker and I am grabbing the date from that template. I want to send that in the redirect to the next page, which is a survey.
return redirect(url_for('views.survey', date = date))
I grab the date parameter using the request.args
form = Survey1Form(request.form)
date = request.args['date'] #grabbing the date parameter from the redirect in Journal above.
return render_template('survey.html',date = date, form = form,user = current_user)
Survey.html is a survey where the user fills out some fields. clicking submit fires a post method which triggers the first part of the if statement, evaluating for a post method.
def survey():
if request.method == "POST":
energy = request.form.get('Energy')
mood= request.form.get('Mood')
symptom= request.form.get('Symptom')
overall = request.form.get('Overall')
#need to validate if they have already done survey for that date
new_survey = tbl_survey(user_id=current_user.id,date = date, energy = energy, mood = mood, symptom = symptom, overall = overall )
db.session.add(new_survey)
db.session.commit()
flash('Your responses were recorded!', category = 'success')
return redirect(url_for('views.home'))
else:
form = Survey1Form(request.form)
date = request.args['date'] #grabbing the date parameter from the redirect in Journal above.
return render_template('survey.html',date = date, form = form,user = current_user)
My question is simple, how can I maintain that date value/variable. What is the best way to store it. I have tried a class with a getter/setter but it still gets reset when I submit the survey and redirect back to this block of code above. Want to avoid a global variable and tbh, I can't get that quite right either. Can I pass the date value through the html and back to the python code? Or am I missing something very simple and obvious. TIA.
In my opinion, you have two options for preserving the date. So the problem could be solved if you restructure your code.
So you pass on the selected date with a redirect. In my example I use the isoformat for a timestamp.
#views.route('/somewhere')
def somewhere():
dt = datetime.now()
return redirect(url_for('views.survey', date=dt.isoformat()))
The first option is to constantly pass the date within the URL parameters. It is necessary to leave the action parameter within the form empty or to pass on the date using url_for as usual.
However, the query of request.args should go to the beginning of the endpoint before the if-condition.
#views.route('/survey', methods=['GET', 'POST'])
def survey():
dt = request.args.get('date', datetime.now(), type=datetime.fromisoformat)
form = SurveyForm(request.form)
if form.validate_on_submit(): # It is a POST request and all inputs are valid.
print(dt)
return render_template('survey.html', **locals())
Since you're already using this type of functionality to pass the date between endpoints, it seems the most appropriate to me.
The second option is to pass the date to a hidden input field. It remains in this field until it can be checked and used.
from flask_wtf import FlaskForm
from wtforms import DateTimeField, StringField
from wtforms.validators import DataRequired
from wtforms.widgets import HiddenInput
# ...
class SurveyForm(FlaskForm):
date = DateTimeField(
validators=[DataRequired()],
widget=HiddenInput()
)
# ...
#views.route('/survey', methods=['GET', 'POST'])
def survey():
dt = request.args.get('date', datetime.now(), type=datetime.fromisoformat)
form = SurveyForm(request.form, date=dt)
if form.validate_on_submit():
print(form.date.data)
return render_template('survey.html', **locals())
In this case, you no longer need to pass the date within the URL. So you can also reset the action parameter with url_for('views.survey') and omit the url parameter. The date in the input field is retained.
<form action="{{url_for('survey')}}" method="post">
{{ form.hidden_tag() }}
{# additional fields here #}
<input type="submit" />
</form>
Using the first variant, your code might look something like this, depending on which notations you prefer.
def survey():
dt = request.args.get('date') # Optionally convert the date to an object here.
form = SurveyForm(request.form)
if form.validate_on_submit():
# Possibly check the date here.
new_survey = Survey(user_id=current_user.id, date=dt)
form.populate_obj(new_survey) # Transfer the form data to the object.
db.session.add(new_survey)
db.session.commit()
flash('Your responses were recorded!', category='success')
return redirect(url_for('views.home'))
return render_template('survey.html', form=form)
I have been working on building a questionnaire using flask-wtf. my questions has 30 questions and
will likely grow in the future.
From reading the flask-wtf documentation. I see that to the inputted data from a form is accessible like so "form.username.data".
username = StringField('Username')
#app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm(request.form)
if request.method == 'POST' and form.validate():
user = User(form.username.data)
db_session.add(user)
return redirect(url_for('login'))
return render_template('register.html', form=form)
My question:
my qusetionair form has many fields and would be illogical to have to enter form.FIELDNAME.data to get the inputted data for every field. i understand for a user registration form that it would be acceptable as there are only about 3-5 fields. However in my scenario (questionair form) this is not ideal
Solution i tried:
I named my fields question_1 question_2 qusetion_3 , then created a while look like below to dynamically enter the field name. however, flask-wtf complains "AttributeError: 'MyForm' object has no attribute 'question'"
if form.validate_on_submit():
string = 'q'
for i in range(30):
question = "q"+str(i)
field_name = form.question.data
answer_to_add = Answer(answers=field_name)
db.session.add(answer_to_add)
db.session.commit()
For people in the future who run into a similiar issue. The resolution is to simply use "form.data" and not specify a field as this will mean all fields are sent.
Code example:
if form.validate_on_submit():
answer = form.data
"do something"
You can find more useful info in the docs: https://wtforms.readthedocs.io/en/2.3.x/forms/?highlight=form#wtforms.form.Form.data
I'm having big trouble understanding the whole forms business in django. As I understand it the cleaned form data is a dictionary. So all my defined form fields should be in the dictionary like so: {'definedform': userinput, ...}. Is this correct?
I want to create a form in which a user can input data. This data should then be send to a different view, in which the inputted data is rendered with a latex template (and subsequently rendered into a pdf). This works more or less fine if I define the context in the /create_pdf/ view and grab the user input manually. But I suppose there is a nicer way. What I think should work:
def index(request):
if request.method == "POST":
persoform = PersonalForm(request.POST, prefix='personal')
if persoform.is_valid():
content = persoform.cleaned_data()
content = Context(content)
return HttpResponseRedirect('/create_pdf/')
else:
persoform = PersonalForm()
return render(request, 'app/template.html', {'persoform': persoform})
And in my /create_pdf/ view:
def create_pdf(request):
template = get_template('app/latextemplate.tex')
rendered_tpl = template.render(content)
[...]
So, how can I make sure, to pass the data from my index view to my create_pdf view?
EDIT:
Forgot to mention: The error is "'content' not defined". So I understand that the /create_pdf/ view doesn't get content dictionary, but I have no idea how I would make sure that it does.
Put the data in to the session on submit, and pop it out in the second view.
if form.is_valid():
request.session['perso'] = form.cleaned_data
return HttpResponseRedirect('/create_pdf/')
...
def create_pdf(request):
data = request.session.pop('perso'], {})
I've used flask before and I've had working form validation, but for some reason it's not working for my new app. Here is the basic code of the form.
from flask.ext.wtf import Form, TextField, TextAreaField, SubmitField, validators,ValidationError
class subReddit(Form):
subreddit = TextField('subreddit', [validators.Required('enter valid subreddit')])
next = SubmitField('next')
change = SubmitField('change')
user = TextField('user', [validators.Required('enter valid user')])
fetch = SubmitField('fetch comments')
I have subreddit as the validation field, so if it's empty, I want it to throw an error and reload the page.
The HTML:
<form class='sub' action="{{ url_for('sr') }}" method='post'>
{{ form.hidden_tag() }}
<p>
if you want to enter more than one subreddit, use the + symbol, like this:
funny+pics+cringepics
<p>
<br/>
{% for error in form.subreddit.errors %}
<p>{{error}}</p>
{% endfor %}
{{form.subreddit.label}}
{{form.subreddit}}
{{form.change}}
</form>
I have CSRF_ENABLED=True in my routes.py as well. What am I missing? When I leave the subredditfield empty and click change, it just reloads the page, no errors. This is an issue because whatever is in the field will get recorded in my database, and it can't be empty.
EDIT
#app.route('/index',methods=['GET','POST'])
#app.route('/',methods=['GET','POST'])
def index():
form = subReddit()
rand = random.randint(0,99)
sr = g.db.execute('select sr from subreddit')
srr = sr.fetchone()[0]
r = requests.get('http://www.reddit.com/r/{subreddit}.json?limit=100'.format(subreddit=srr))
j = json.loads(r.content)
pic = j['data']['children'][rand]['data']['url']
title = None
if form.validate_on_submit():
g.db.execute("UPDATE subreddit SET sr=(?)", [form.subreddit.data])
print 'validate '
if j['data']['children'][rand]['data']['url']:
print 'pic real'
sr = g.db.execute('select sr from subreddit')
srr = sr.fetchone()[0]
r = requests.get('http://www.reddit.com/r/{subreddit}.json?limit=100'.format(subreddit=srr))
pic = j['data']['children'][rand]['data']['url']
title = str(j['data']['children'][rand]['data']['title']).decode('utf-8')
return render_template('index.html',form=form,srr=srr,pic=pic,title=title)
else:
print 'not valid pic'
return render_template('index.html',form=form,srr=srr,pic=pic)
else:
print 'not valid submit'
return render_template('index.html',form=form,srr=srr,pic=pic)
return render_template('index.html',form=form,srr=srr,pic=pic)
You have a number of problems.
The most important is that validation occurs in the POST request view function. In your example this is function sr. That function should create the form object and validate it before adding stuff to the database.
Another problem in your code (assuming the above problem is fixed) is that after validate fails you redirect. The correct thing to do is to render the template right there without redirecting, because the error messages that resulted from validation are loaded in that form instance. If you redirect you lose the validation results.
Also, use validate_on_submit instead of validate as that saves you from having to check that request.method == 'POST'.
Example:
#app.route('/sr', methods=['POST'])
def sr():
form = subReddit()
if not form.validate_on_submit():
return render_template('index.html',form=form)
g.db.execute("UPDATE subreddit SET sr=(?)", [form.subreddit.data])
return redirect(url_for('index'))
Additional suggestions:
it is common practice to start your class names with an upper case character. SubReddit is better than subReddit.
it is also common to have the GET and POST request handlers for a form based page in the same view function, because that keep the URLs clean when validation fails without having to jump through hoops to get redirects working. Instead of having the sr function separately you can just combine it with index() and have the action in the form go to url_for('index').
Flask-WTF adds a new method onto the form called validate_on_submit(). This is like the WTForms validate() method, but hooks into the Flask framework to access the post data. The example given on the Flask site is:
form = MyForm()
if form.validate_on_submit():
flash("Success")
return redirect(url_for("index"))
return render_template("index.html", form=form)
Because you're just using validate(), the form is trying to validate without any data (which, of course, will fail). Then you're redirecting. Try to use validate_on_submit() as shown above.
I'm brand new to django and fairly new to programming in general. I've done the django tutorial and searched the web for an answer to this question, but to no avail, so now I'm here. I am confused how post works with django. All of the tutorials I've looked at how have a return function in views that displays the webpage. I get that. But then how does a user update data if the page is being rendered from that return statement? After the return there can't be any more updates because the function stops, right? What am I missing here? Any help would be greatly appreciated, I'm getting fairly desperate here.
One pattern for Django views (by no means the only pattern) is to check the request method (GET or POST) at the beginning of the view. If it is POST, then handle the incoming data (before the view returns), and then return either a rendered template, or a redirect.
def view_function(request):
if request.method == 'POST':
if data_is_valid(request.POST):
save_data(request.POST)
return HttpResponseRedirect('/somewhere/good')
else:
return render('template', {'errors': what_went_wrong}
else:
return render('template')
The user updates data in the logic of the view function. That is to say, if the user wishes to update something, you place the update logic in the view function before the return. For example, you would do this:
def update(request):
item = <some model>.objects.get(<something>)
<more code>
return <something>
Usually an edit view function contains two parts -- one for updating data, and the other for displaying the update form. For example,
def user_edit(request):
if request.method == 'POST': # is this a save action?
# save the user data
user_id = request.POST.get('user_id')
username = request.POST.get('username')
description = request.POST.get('description')
user = User.objects.get(id=user_id)
user.username = username
user.description = description
user.save()
return HttpResponseRedirect('/user/') # redirect to index
else:
# show the edit form
user_id = request.GET.get('user_id')
user = User.object.get(id=user_id)
return render_to_response('/user/edit.html', { 'user': user })
There are many different choices for the if request.method == 'POST' line. You can also use if request.POST.get('user_id') to check if specified field is set, to determine if this is a save action.