Problem Description
Hi all, likely a simple solution to this issue. Basically, I have a SelectField where for one class of users it is not viewable so I'm not sending the parameter in the API to create the user. Now form.validate() is marking this as an invalid choice even though I'm adding None to the list of allowable choices. Not sure how to make heads or tails of this and I haven't seen this type of issue mentioned anywhere else.
Goal
Get WTForms to correctly check for None in the request so I can get the validation where it matters for other users and this "non-validation" where it doesn't apply.
Relevant code
class CustomerForm(Form):
customer_name = StringField('Customer Name', [InputRequired()])
groups = SelectField('Group')
#app.route("/Customers", methods=["POST"])
def customers_post():
logging.debug(request.form)
form = CustomerForm(request.form)
# Adding the various valid options for the group input
with dbConfig.Session() as session:
choices = session.execute(select(Group)).all()
form.groups.choices = [(choice[0].id, choice[0].name) for choice in choices]
form.groups.choices.append(('-1', ''))
form.groups.choices.append((None, '1'))
if request.method == 'POST' and form.validate():
...
return Response(status=201)
elif request.method == 'POST':
jsonErrors = json.dumps(form.errors, ensure_ascii=False)
return Response(jsonErrors, status=400, mimetype='application/json')
Request payload
Just in case this helps
customer_name=austin-test-4
another valid payload where I want to check the selected choice is a valid option
customer_name=austin-test-4&groups=4
Related
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 want to save a form in session so that a user doesn't have to reapply filters when going to a different url. Here is the error I'm getting:
'unicode' object has no attribute 'get'
This is the code I have:
views.py
def someview(request):
if request.method == 'POST':
form = Form(request.POST, initial=request.session.get('save_form', None))
if form.is_valid():
clean_form = json.dumps(form.cleaned_data, indent=4, sort_keys=True, default=str)
request.session['save_form'] = clean_form
else:
pass
else:
form = Form()
forms.py
class Form(forms.Form):
startdate = forms.DateField(
label='Start date',
initial= '2018-01-01',
widget=forms.TextInput(attrs={'class':'datepicker'})
)
enddate = forms.DateField(
label='End date',
initial= datetime.date.today() - datetime.timedelta(days=1),
widget=forms.TextInput(attrs={'class':'datepicker'})
)
manager = forms.MultipleChoiceField(
required=False,
choices=managers,
widget=forms.SelectMultiple(),
)
Being a beginner in Django, I'm not sure where to go from here. I'm pretty sure the error is in the initial argument. But, printing it, it seems print what I would expect.
TIA
My best guess is that you JSONfify the form values (json.dumps(form.cleaned_data, ...)), but you do not un-JSONify it when reloading from the session.
When reading the session you need to do json.loads(...) at some point:
saved_form = json.loads(request.session.get('save_form', ''))
The default value '' might not be the best, I haven't tried. If that's giving you an error, you might want to try replacing it with {}.
Hope that helps.
EDIT:
Note that loading content from the session should probably be done in the last else branch. You generally don't want to provide an initial value when the user is sending data, like in a POST request:
def someview(request):
if request.method == 'POST':
form = Form(request.POST)
if form.is_valid():
clean_form = json.dumps(form.cleaned_data, default=str)
request.session['save_form'] = clean_form
else:
pass
else:
saved_form = json.loads(request.session.get('save_form', ''))
form = Form(initial=saved_form)
EDIT 2:
Generally, what you want to do in a form view is often very similar, and you might find yourself repeating the same boilerplate. Luckily, Django has something to help with that, and it called class-based views, and I recommend ccbv.co.uk if you're interested to dive into that, for example FormView.
However, they can be a bit confusing and hard to get your head around when you're just starting. Maybe better to stick with function-based views (as you do) for now, but I thought I linked to these 2 resources which I wish I had when I started learning class-based views.
If you dynamically add a field to a WTForms form, the newly-added fields are not validated (even if you add validators).
For instance, in Flask:
#app.route('/add', methods=['GET', 'POST'])
def add():
if 'state' not in session:
session['state'] = 1
form = MyForm()
if request.method == 'POST' and form.validate():
if session['state'] == 1:
setattr(
MyForm,
'asd',
StringField(
'asdfield',
validators = [
DataRequired(),
Length(min=1)
]
)
)
form = MyForm()
session['state'] = 2
return render_template(
'add.html',
form=form
)
print(len(form.asd.data)) # can equal 0
session['state'] = 1
return redirect('/add')
return render_template(
'add.html',
form=form
)
I believe this is due to the fact that form = MyForm() is run every time you go to /add, so even if session['state'] == 2 you run form.validate() on a default form which does not have the dynamically-added field. Therefore, this field cannot be part of the form validation process.
How can one properly address this behaviour ? If it's not possible, then how can one dynamically add fields to an existing form in such a way that all fields get properly validated upon submission ?
Since you call validate() before adding the field, naturally, you can't validate a field which doesn't exist yet. That said, you don't want to add a field to an instance instead of a class. This is because since WTForms processes its input data at construction, adding fields to the instance is mostly a meaningless thing.
If your field name is static, you can use the del trick detailed here
If it's dynamic, you could instead follow the dynamic form composition pattern from the docs.
Since I've gone over this in detail, I'll link my previous example here:
Wtfforms dynamic generation
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
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.