Multiple form on a page validation errors WTForms - python

I have multiple form on a page in modals: 1-st to create a new user address and n-forms with addresses which user created (created with loop). When i enter invalid data in fields with validators (e.g. datarequired), i have error messages in each form.
Here is the field render example which i use in every form:
{{ address_form.street.label(class_="form-label", for="InputStreet") }}
{{ address_form.street(class_="form-control", id="InputStreet") }}
{% for error in address_form.street.errors %}
<span style="color: red;">{{ error }}</span>
{% endfor %}
Part of the code from view.py:
#bp.route('/profile/address', methods=['GET', 'POST'])
#login_required
def address():
address_form = AddressForm()
if address_form.submit_address.data and address_form.validate():
address_to_add = Address(
street=address_form.street.data,
house=address_form.house.data,
building=address_form.building.data,
entrance=address_form.entrance.data,
floor=address_form.floor.data,
apartment=address_form.apartment.data,
additional_info=address_form.additional_info.data,
user=current_user)
db.session.add(address_to_add)
db.session.commit()
return redirect(url_for('profile.address'))
if address_form.edit_address.data and address_form.validate():
address_to_edit = Address.query.get(address_form.address_id.data) # Here is data from hidden field
# Editing data in DB
db.session.commit()
return redirect(url_for('profile.address'))
return render_template('profile/address.html', title='Адрес доставки', address_form=address_form)
Forms work fine with adding, editing and deleting data, but work incorrect with validation errors.
I think i need one more condition in if statenent related with hidden field or change something in my html file.
I've tried add an action attr in form like:
<form action="{{ url_for('profile.address', form_id=address.id) }}" method="post" novalidate>
And smth like this in view func but it doesn't work:
form_id = request.args.get('form_id', type=int)
if address_form.edit_address.data and address_form.validate() and form_id == address_form.address_id.data:
pass

Finally i found a very bad solution:
address_form = AddressForm()
form_id = request.args.get('form_id', 0, type=int)
For main form form_id always is 0.
Form tag for main form:
<form action="{{ url_for('profile.address', form_id=form_id) }}" method="post" novalidate>
And a field render for main form:
{{ address_form.street.label(class_="form-label", for="InputStreet") }}
{{ address_form.street(class_="form-control", id="InputStreet") }}
{% if form_id == 0 %}
{% for error in address_form.street.errors %}
<span style="color: red;">{{ error }}</span>
{% endfor %}
{% endif %}
If someone has a better solution about my problem it'd good. Now it's time to learn some JS and solve this problem with AJAX.

Related

django redirect to form view and autofill with previously entered values

I have following scenario.
User fills out a form
If the user clicks the "continue" button and the form is valid the user will be redirected to a summary view
In the summary view the user checks the input again. He can either continue or go back.
If he continues the data will be saved in the database, if he goes back he can edit the form.
Since in step 4 the user is at the view summary I have to redirect him to the home view. I don´t want the user to fill out the entire form again, the previously entered data should be autofilled if he goes back.
Something special: I am using django-tagify2 for one input in the form to get tags rather then text. If the user goes back the tags should be rendered correctly in the tagify specific form.
So here are my files:
home.html
{% extends "messenger/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="message-container">
<form method="POST" autocomplete="off">
{% csrf_token %}
{{ form|crispy }}
<button name="sendmessage" class="btn btn-outline-info" type="submit">Continue</button>
</form>
</div>
{% endblock content %}
summary.html
{% extends "messenger/base.html" %}
{% block content %}
<h4>Zusammenfassung</h4>
<p><b>Empfänger: </b>{{ receiver }}</p>
<br>
<p><b>Betreff: </b>{{ subject }}</p>
<br>
<p><b>Nachricht: </b>{{ message }}</p>
<button>Edit</button>
<button>Continue</button>
{% endblock content %}
home view
#login_required(login_url='login')
def home(request):
if request.method == 'POST' and 'sendmessage' in request.POST:
message_form = MessageForm(request.POST)
if message_form.is_valid():
receiver_list = message_form['receiver'].value().split(';')
subject = message_form['subject'].value()
message = message_form['textmessage'].value()
#create sessions and send data to next view
session_receiver = receiver_list
request.session['session_receiver'] = session_receiver
session_subject = subject
request.session['session_subject'] = session_subject
session_message = message
request.session['session_message'] = session_message
return redirect('summary')
else:
message_form = MessageForm()
return render(request, 'messenger/home.html', {'form': message_form})
summary view
def summary(request):
receiver = request.session.get('session_receiver')
subject = request.session.get('session_subject')
message = request.session.get('session_message')
return render(request, 'messenger/summary.html', {'receiver':receiver,
'subject':subject,
'message':message})
So what is the best way to do this?
Can I use the session variables to set the fields in the form?
I don´t want to change the logic in the app. I want a home/summary/success view/template where I can loop as long is I want between home and summary until the user is happy with his entered form data
How about checking request.session when there is get request to home view? Then you can bind message_form = MessageForm() to session data.
You can check out htmx and django-htmx. You can do what you want easily without session by swapping HTML with context.
I played around with the session values and views and finally got a way to redirect to other views with prefilled form fields based on session values.
#login_required(login_url='login')
def home(request):
if request.method == 'POST' and 'sendmessage' in request.POST:
message_form = MessageForm(request.POST)
if message_form.is_valid():
ad_group = message_form['ad_group'].value().split(';')
ad_user = message_form['ad_user'].value().split(';')
subject = message_form['subject'].value()
message = message_form['textmessage'].value()
#create sessions and send data to next view
session_ad_group = ad_group
request.session['session_ad_group'] = session_ad_group
session_ad_user = ad_user
request.session['session_ad_user'] = session_ad_user
session_subject = subject
request.session['session_subject'] = session_subject
session_message = message
request.session['session_message'] = session_message
return redirect('summary')
else:
if request.session.get('session_subject'):
message_form = MessageForm(initial={'ad_group': request.session.get('session_ad_group'),
'ad_user': request.session.get('session_ad_user'),
'subject': request.session.get('session_subject'),
'textmessage': request.session.get('session_message')})
return render(request, 'messenger/home.html', {'form': message_form})
else:
message_form = MessageForm()
return render(request, 'messenger/home.html', {'form': message_form})
def summary(request):
ad_group = request.session.get('session_ad_group')
ad_user = request.session.get('session_ad_user')
subject = request.session.get('session_subject')
message = request.session.get('session_message')
if request.method == 'POST' and 'edit' in request.POST:
message_form = MessageForm(initial={'ad_group':ad_group, 'ad_user': ad_user,
'subject':subject, 'textmessage':message})
return render(request, 'messenger/home.html', {'form': message_form})
return render(request, 'messenger/summary.html', {'ad_group':ad_group,
'ad_user': ad_user,
'subject':subject,
'message':message})
Template
{% extends "messenger/base.html" %}
{% block content %}
<h2>Zusammenfassung</h2>
<div class="border-top pt-3">
<p><b>AD-Gruppe: </b>{{ ad_group }}</p>
<p><b>AD-User: </b>{{ ad_user }}</p>
<br>
<p><b>Betreff: </b>{{ subject }}</p>
<br>
<p><b>Nachricht: </b>{{ message }}</p>
<div class="buttoncontainer">
<form name="edit" action="" method="post">
{% csrf_token %}
<button class="btn edit_btn" formaction="{% url 'messenger-home' %}">Zurück</button>
</form>
<form name="senden" action="" method="post">
{% csrf_token %}
<button class="btn continue_btn" formaction="{% url 'send_messages' %}">Nachricht senden</button>
</form>
</div>
</div>
{% endblock content %}

Validating GET Params with WTForms in Flask

I have spent a couple of days trying to get WTForms to validate my request.args, but I just can not get form.validate() to return True.
The idea is that I have a simple text field for user input in a WTForm as shown below.
form.py
class SearchForm(FlaskForm):
q = StringField('q',
validators=[])
search = SubmitField('Search')
def validate_q(self, q):
if q.data not in allowed_values: #"allowed_values" is just a list I want to check against
raise ValidationError('')
search.html
<form method="GET" action="{{ url_for('finance.search') }}">
<div class="col-9 col-md-5 p-0 m-0">
{% if form.q.errors %} {{ form.q(class="form-control form-control-md is-invalid") }}
<div class="invalid-feedback">
{% for error in form.q.errors %}
<span>{{ error }}</span> {% endfor %}
</div>
{% else %} {{ form.q(class="form-control form-control-md") }} {% endif %}
</div>
<div class="col-2 col-md-2 p-0">
{{ form.search(class="btn btn-md btn-dark") }}
</div>
</form>
routes.py
#finance.route('/finance/search')
def search():
form = SearchForm(request.args)
print(form.validate()) #always gives false
The HTML code for the form is included on several templates and submitting the form always directs to the search route that is shown below. I tried following WTForms documentation and passed in request.args into the form. When I ran the .validate() on the object, the validate function for the q parameter also executed, but for some reason .validate() always returns False.
Can anyone please elaborate on why that might be? I know I can use post request, or add a custom validation function inside the route, but I want to avoid workarounds if possible.
(stack-overflow seems to use a similar type of architecture for their search http://127.0.0.1:8000/finance/search?q=aapl&search=Search vs https://stackoverflow.com/search?q=aapl and I want to follow that if possible.)
Thanks!
I actually just figured out that the error was happening because I was not including a crsf_token in the form. The token is not needed since the its a get request, but this needs to be explicitly stated with meta = {'csrf': False}.
#finance.route('/finance/search')
def search():
form = SearchForm(request.args, meta={'csrf': False})
print(form.validate()) #Now gives True if validation function does not raise error

Flask-WTForms form.validate() fails on dynamic choices

I'm creating a survey application that displays the survey question and choices and allows the user to pick a choice through the Flask-WTForms package. The form uses a RadioField and seems to fail form.validate() when populating the choices attribute dynamically.
When I manually enter in the choices as such:
class SurveyAnswerForm(FlaskForm):
answers = RadioField('Answers',
coerce=str,
choices=[('18-25', '18-25'), ('26-35', '26-35')])
form.validate() returns True and there are no errors in form.error.
When I decide to populate the choices attribute dynamically (see below), form.validate() returns False and form.error returns:
{'answers': ['Not a valid choice']}.
I've been working at this for hours and am not sure why form.validate() returns False.
forms.py:
from flask_wtf import FlaskForm
from wtforms import RadioField
class SurveyAnswerForm(FlaskForm):
answers = RadioField('Answers',
coerce=str,
choices=[])
app.py:
#app.route('/survey/<int:survey_id>/questions', methods=['GET', 'POST'])
def survey_questions(survey_id):
survey = Survey.query.filter_by(id=survey_id).first()
page = request.args.get('page', 1, type=int)
questions = SurveyQuestion.query.filter_by(survey_id=survey_id)\
.order_by(SurveyQuestion.id)\
.paginate(page, 1, True)
for question in questions.items:
question_id = question.id
choices = QuestionChoices.query\
.join(SurveyQuestion,
and_(QuestionChoices.question_id==question_id,
SurveyQuestion.survey_id==survey_id)).all()
form = SurveyAnswerForm(request.form)
form.answers.choices = [(choice.choice, choice.choice)\
for choice in choices]
if request.method =='POST' and form.validate():
print('Successful POST')
next_url = url_for('survey_questions', survey_id=survey.id,
page=questions.next_num)\
if questions.has_next else None
prev_url = url_for('survey_questions', survey_id=survey.id,
page=questions.prev_num)\
if questions.has_prev else None
return render_template('survey_question.html',
survey=survey,
questions=questions.items,
choices=choices,
form=form,
next_url=next_url, prev_url=prev_url)
survey_question.html:
{% extends "layout.html" %}
{% block body %}
<h2>{{ survey.survey_title }}</h2>
{% for question in questions %}
<h3>{{ question.question }}</h3>
{% endfor %}
<form action="{{ next_url }}" method="POST">
{{ form.csrf_token }}
{{ form.answers(name='answer') }}
{% if prev_url %}
Back
{% endif %}
{% if next_url %}
<input type="submit" value="Continue">
{% else %}
Finish
{% endif %}
</form>
{% endblock %}
The problem was submitting a POST request with pagination. if the current link is /survey/1/question?page=2 the form will submit to /submit/1/question?page=3. To remedy this, I just created a separate route for submission and handled logic there.

Trying to save my Django Model Formset, keep getting ManagementForm error?

So, a total Django Model Formset Newb question. I'm trying to save my form and keep getting this error:
['ManagementForm data is missing or has been tampered with']
Here is what I have for my TemplateView:
class AttendanceTemplate(TemplateView):
template_name = 'attendance/index.html'
def get_context_data(self, **kwargs):
context = super(AttendanceTemplate, self).get_context_data(**kwargs)
instruction = Instruction(self.request.user.username)
sections_list = self.request.GET.getlist('sections_list')
term = self.request.GET.get('term', instruction.term)
enrollments = Enrollment.objects.using('wisp').prefetch_related('profile').filter(section_id__in=['111111'], term=term)
attendanceQuery = Enrollment.objects.using('wisp').prefetch_related('student').filter(section_id__in=['111111'], term=term)
for enrollment in attendanceQuery:
attendance, created = Attendance.objects.update_or_create(
section_id=enrollment.section_id,
term=enrollment.term,
first_name=enrollment.student.first_name,
last_name=enrollment.student.last_name,
email_address=enrollment.student.email_address,
)
something = Attendance.objects.filter(section_id__in=['111111'], term=term)
formset = AttendanceFormSet(queryset=something)
combined = zip(enrollments, formset)
context['combined'] = combined
return context
Here is how I'm trying to save the form:
def post(self, request):
formset = AttendanceFormSet(request.POST)
if formset.is_valid():
for thing in formset:
formset = thing.save()
return render_to_response("template/index.html",{'formset': formset}, RequestContext(request))
else:
return HttpResponse(error.msg)
Here is what I have in my template:
<form method="POST" action="">
{% csrf_token %}
{% for enrollment, form in combined %}
<div class="wrapper-formset">
<div>
{{ form.first_name.value }}
{{ form.last_name.value }}
{{ form.email_address.value }}
</div>
<div class="clear-all"></div>
</div>
{% endfor %}
<button type="submit" class="save btn btn-default">Save</button>
</form>
Am I saving my form wrong? Maybe my loop is wrong? Also, I'd prefer to print each field out individually, so using the "myform.management_Form" may not work for me? (e.g., myform.management_form.field_name)
If you render the forms separately, then you must include the management form in your template. The fact that you are zipping your forms makes no difference.
Including the management form is easy, just add {% formset.management_form %} to your template.
<form method="POST" action="">
{% csrf_token %}
{{ formset.management_form }}
{% for enrollment, form in combined %}
...
For that to work, you'll need to make sure that formset is in the template context, for example:
context['formset'] = formset
You might find the docs on using model formsets in the template useful. It would be a good idea to start with the simplest option, {{ formset }}, test it, then gradually customize the template. That makes it easier to debug when stuff goes wrong. At the moment it looks like you have missed out {{ form.id }}.

Flask WTF – Forms always redirect to root

I have created a simple Flask WTF form
class SequenceForm(Form):
sequence = StringField('Please enter a sequence in FASTA format', validators=[Required()])
submit = SubmitField('Submit')
and I have set up a route to make it appear on a page
#main.route('/bioinformatics')
def bioinformatics():
form = SequenceForm()
return render_template('bioinformatics.html', form=form)
It all works great (so far). When I point my browser to foo/bioinformatics, I see a page with a SequenceForm rendered. However, when I hit the Submit button, I am always taken back to the root page defined by #main.route('/').
How can I make the Submit button take me somewhere else? I would like to use validate_on_submit() and do stuff with the data entered in the form.
Thanks!
/Michael Knudsen
UPDATE (Code from bioinformatics.html)
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Bioinformatics{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, Bioinformatics!</h1>
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
You need to specify an action in the form in your html.
<form action="/url_which_handles_form_data" method="Post">
your code
</form>
make sure to give the correct path if you are using blueprints
Edit:
From https://github.com/mbr/flask-bootstrap/blob/master/flask_bootstrap/templates/bootstrap/wtf.html I found this part.
{% macro quick_form(form,
action="",
method="post",
extra_classes=None,
role="form",
form_type="basic",
horizontal_columns=('lg', 2, 10),
enctype=None,
button_map={},
id="") %}
So you can probably call
{{ wtf.quick_form(form, action="/fancy_url") }}
or
{{ wtf.quick_form(form, action=url_for("blueprint_name.fancy_url")) }}
Depending on where the view is located.
Thanks to Tim Rijavec and Zyber. I used a combination of your suggestions to come up with the following solution.
I added GET and POST to methods for the route
#main.route('/bioinformatics', methods=['GET', 'POST'])
def bioinformatics():
form = SequenceForm()
return render_template('bioinformatics.html', form=form)
and then I wrapped the wtf.quick_form call inside tags.
<form action="{{ url_for('main.bioinformatics') }}" method="POST">
{{ wtf.quick_form(form) }}
</form>
Now everything works beautifully. Thanks!

Categories

Resources