I have currently an issue where one of my pages in flask falls into an endless redirect loop:
I have already so many routes and methodes and had never a problem with this. I also have no idea how to solve it, here is the function / route which has this problem (Its a simple mail function, couldnt even test it):
#app.route('/kontakt/', methods=["GET", "POST"])
def kontakt_send():
form = KontaktMailForm()
if form.validate_on_submit():
if form.check.data == 9:
simplemail.Email(
from_address = form.absender.data, #request.form["absender"],
to_address = u"email#email.de",
subject = u"Nachricht - Kontaktformular von " + form.name.data,
message = form.nachricht.data #request.form["nachricht"]
).send()
flash("Nachricht erfolgreich versandt")
return redirect(url_for('kontakt_send'))
else:
flash("Was ist 4+5?")
return redirect(url_for('kontakt_send'))
else:
flash("Alle Felder muessen ausgefuellt werden")
return redirect(url_for('kontakt_send'))
return render_template('kontakt.html', form=form)
I dont even know which data is relevant to solve this issue, but here is the form from the html template:
<form id="kontaktform" method="POST" enctype="multipart/form-data" action="{{ url_for('kontakt_send') }}">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.name.label }} <span class="star"> * </span>
{{ form.name(size=30, class = "form-control", placeholder="z.B. Markus Müller") }}
</div>
<div class="form-group">
{{ form.absender.label }} <span class="star"> * </span>
{{ form.absender(class = "form-control", placeholder="z.B. markus.müller#gmx.de") }}
</div>
<div class="form-group">
{{ form.nachricht.label }} <span class="star"> * </span>
{{ form.nachricht(size=500, class = "form-control", placeholder="z.B. Ihre Nachricht an uns") }}
</div>
<div class="form-group">
{{ form.check.label }} <span class="star"> * </span>
{{ form.check(class = "form-control", placeholder="z.B. 9") }}
</div>
<div class="">
<button type="submit" class="btn mybtn"> Senden </button>
</div>
</form>
EDIT:
Remiving the second return redirect(url_for('kontakt_send')) from the else solved the issue, but everytime I load the page it shows me the flash message eventhough I am not submitting the form, how is this possible? It submits the form by reloading the page
validate_on_submit checks two things:
Is the request a POST?
Does the post body validate as the specified form?
If either of these is false, the else block runs and generates the flash message. Since the first check will be false for all GETs, you'll receive the flash message on every page load that isn't a form submission (this is also why you has the redirect loop). The second one will be false anytime the form doesn't validate, thus leading to the flash message again.
All of your if/else blocks lead to flash messages. That's why you get one on every request.
Related
I have a Django form that receives a text (that I copy from Google Classroom: a bunch of student comments). I use these comments to make student's attendance. What I want to achieve is:
Accessing /insertion/ url via GET user receive the page form as a response, to choose the class (class01, class02, etc) and to past the text
When the user clicks on submit in this form (post method), it is redirect to the same /insertion/ url, but now the form is bound to the data submited, and the page shows a preview page (based on a boolean variable I'm passing through context), showing what students are present and what are absent based on the text informed. At that page, a new submit button will be shown below a text like "if everything's ok, hit the ok button".
After click this ok button, a pdf will be generated and the user will be redirected to /files/ url, to see the generated pdf and previous generated pdf.
views.py
def insertion(request):
context = {}
if request.method == 'GET':
form = AttendanceDataForm()
context.update({"form": form})
if request.method == 'POST':
form = AttendanceDataForm(request.POST)
context.update({"form": form})
if form.is_valid():
lesson = form.cleaned_data['lesson']
raw_text = form.cleaned_data['raw_text']
# Get course students
course_students = md.Student.objects.filter(course_id=lesson.course_id)
# Get present students based on raw text informed
present_students = [s for s in course_students if s.full_name in raw_text]
# Get absent students based on raw text informed
absent_students = [s for s in course_students if s.full_name not in raw_text]
context.update({
"present_students": present_students,
"absent_students": absent_students,
"render_preview": True
})
context.update({"active_freq": True})
return render(request, 'core/insertion.html', context)
def files(request):
context = {}
if request.method == 'POST':
# How can I access all expensive calculation I did in the previous view?
context.update({"active_gen": True})
return render(request, "core/files.html", context)
insertion.html
<div class="row">
<div class="col-12 col-md-6">
<h3>Informar Frequência</h3>
{% crispy form %}
</div>
<div class="col-12 col-md-6">
{% if render_preview %}
<div class="container">
<div class="row p-4 bg-white rounded mt-4">
<div class="col-12 col-sm-6">
<h5>Alunos presentes</h5>
<ul class="previewer-list">
{% for student in present_students %}
<li>{{ student.id }} - {{ student.full_name }}</li>
{% endfor %}
</ul>
</div>
<div class="col-12 col-sm-6">
<h5>Alunos ausentes</h5>
<ul class="previewer-list">
{% for student in absent_students %}
<li>{{ student.id }} - {{ student.full_name }}</li>
{% endfor %}
</ul>
</div>
</div>
<p class="mt-3">If everything's ok, hit the OK button</p>
<form method="post" action="{% url "core:files" %}">
{% csrf_token %}
<button type="submit" class="btn btn-primary">OK</button>
</form>
</div>
{% endif %}
</div>
</div>
I could get to implement 1 and 2, but 3 is a mistery right now. What I couldn't get is how I can access the expensive calculations I did in insertion view in the files view. How can I do that?
Here's a solution using session framework.
We'll save the calculations in the session and access those values in another view later.
For starters, we'll just save the ids (pk) of the students instead of the student instances because they are not JSON serializable [See note below].
def insertion(request):
# do expensive calucations ...
present_ids = [s.pk for s in present_students]
absent_ids = [s.pk for s in absent_students]
request.session['attendance_data'] = {
'present_ids': present_ids,
'absent_ids': absent_ids
}
def files(request):
attendance_data = request.session.get('attendance_data')
if not attendance_data:
# show error or something else ...
pass
present_students = md.Student.objects.filter(
pk__in=attendance_data['present_ids']
)
absent_students = md.Student.objects.filter(
pk__in=attendance_data['absent_ids']
)
# generate the pdf ...
Note: If you wish, you can also save the student instances in the session but you'll have to change the SESSION_SERIALIZER setting to use the PickleSerializer. See notes about session serialization.
You could submit the primary keys as form data in hidden fields. Just choose an appropriate delimiter based on your primary key (for example, don't delimit with a hyphen if you use a GUID primary key).
<form method="post" action="{% url "core:files" %}">
{% csrf_token %}
<input type="hidden"
name="present"
value="{% for s in present_students %}{{ s.pk }},{% endfor %}"
>
<input type="hidden"
name="absent"
value="{% for s in absent_students %}{{ s.pk }},{% endfor %}"
>
<button type="submit" class="btn btn-primary">OK</button>
</form>
Then in the view you can pick up the PKs in the view from the form data then request.
def files(request):
context = {}
if request.method == 'POST':
present_pks = request.POST.pop('present').split(',')[:-1]
absent_pks = request.POST.pop('absent').split(',')[:-1]
# do type conversions if needed
...
# Because we already have the pks separated, we can combine them
# for the query in order to do just 1 query
course_students = md.Student.objects.filter(pk__in=present_pks + absent_pks).all()
absent_students = []
present_students = []
for student in course_students:
if student.pk in absent_pks:
absent_students.append(student)
else:
present_students.append(student)
I'm designing a website with a form that allows the user to input stuff and add more input fields, it sort of works like those inputfields that you enter your skills with, soi you can add more skills if you have more. But I'm struggling to display more than one input fields with FieldList as it only shows the number of min_entries I set it, even when I copy and paste the html code it only accepts the first one.
forms.py (please focus on the contents for now)
class ExerciseForm(FlaskForm):
exercise = StringField(validators=[DataRequired()])
testcase = StringField(validators=[DataRequired()])
class AddTopicForm(FlaskForm):
topicname = StringField('Topic Name', validators=[DataRequired(), Length(min=2, max=100)])
contents = FieldList(StringField(validators=[DataRequired()]), min_entries=1, max_entries=20)
exercises = FieldList(FormField(ExerciseForm), min_entries=0, max_entries=10)
submit = SubmitField('Add Topic')
html (I have no idea how to add one, and how to make it work)
<div class="addtopicform">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset>
<div class="form-group">
{{ form.topicname }}
</div>
<div class="form-group">
{{ form.contents }}
</div>
<button type="button" onclick="addsection()">Add Section</button>
<script>
</script>
<div class="form-group">
{% for field in form.exercises %}
{{ field }}
{% endfor %}
</div>
<div class="form-group form-btn">
{{ form.submit(class="btn") }}
</div>
</fieldset>
</form>
</div>
website.py (The best result is one data in contents, but obviously I want more)
#app.route("/addtopic/", methods=['GET', 'POST'])
def addtopic():
if 'AccountID' in session:
form = AddTopicForm()
if session["Type"] == "teacher":
if request.method == 'POST' and form.validate_on_submit():
print("success")
topicname = request.form['topicname']
contents = form.contents.data
exercises = form.exercises.data
print(topicname)
print(contents)
print(exercises)
return render_template('course.html', user=session['Name'], acctype=session["Type"])
else:
return render_template('addtopic.html', user=session['Name'], acctype=session["Type"], form=form)
else:
flash("Only teachers can access to this page", "failure")
return render_template('course.html', user=session['Name'], acctype=session["Type"])
else:
flash('An account is needed to access courses', 'failure')
return redirect(url_for('login'))
PLease help me, I couldnt find any useful resources online as all of them assumed I already have a list or is using the input element instead of flask element, which doesnt work at all with flask forms
So I'm trying to pass a value from a Jinja2 template back to my Python code. I'm trying to do this with a hidden input. My form class is this:
class TrueOrFalseForm(flask_wtf.FlaskForm):
choice = RadioField(choices=[('True', 'TRUE'), ('False', 'FALSE')], validators=[validators.InputRequired()])
hidden = HiddenField()
submit = SubmitField('Submit')
And my form is this:
<form autocomplete="off" action="" method="post">
{{ form.hidden_tag() }}
<div style="text-align: center">
<div style="display: inline-block">
{{ form.choice }}
{{ form.hidden(value="{{ result }}") }}
{{ form.submit(class_="btn btn-primary btn-lg") }}
</div>
</div>
</form>
result is a string that I'm passing when rendering the template.
When checking the value of form.hidden.data, though, it comes back as ''. The tag also renders as <input id="hidden" name="hidden" type="hidden" value="">.
I've also tried doing value={{ result }} instead of value="{{result}}" but that makes Jinja throw a TemplateSyntaxError.
Any idea on how to do this?
EDIT:
I'm overwriting result every time I call the function.
This is my route function:
#app.route('/', methods=['GET', 'POST'])
def home():
form = forms.TrueOrFalseForm()
x = random.randint(-100, 100)
y = random.randint(-100, 100)
statement_str = generate_statement_string(2)
tree = BinTree.build_tree(statement_str)
statement_result = BinTree.solve_tree(tree, x, y) # result gets overwritten here
if form.validate_on_submit():
if not flask_login.current_user.is_anonymous:
# same as the else, except with some sql, not relevant
else:
if form.choice.data == form.hidden.data:
flask.flash('Correct!')
else:
flask.flash('Incorrect!')
return flask.render_template('home.html', x_value=str(x), y_value=str(y), statement=statement_str,
result=str(statement_result), form=form)
{{ form.hidden(value="{{ result }}") }} is already in templating syntax with the outer double curly brackets. Therefore, you should just be able to plainly write the result variable, like this: {{ form.hidden(value=result) }}
EDIT
Replace {{ form.hidden_tag() }} with {{ form.csrf_token() }} as well as doing what is in my original answer.
You may also have to instantiate the form with form = forms.TrueOrFalseForm(request.form). Some forms behave weirdly if you don't do that.
Since you're using {{ form.hidden_tag() }} in your template, you do not need to explicitly render the hidden form field. It will be included in the hidden_tag() call.
You can set the value of the hidden field in your views before rendering the template.
views.py
form.hidden.data = result
return render_template("index.html",form=form)
index.html
<form autocomplete="off" action="" method="post">
{{ form.hidden_tag() }}
<div style="text-align: center">
<div style="display: inline-block">
{{ form.choice }}
{{ form.submit(class_="btn btn-primary btn-lg") }}
</div>
</div>
</form>
My proposal is:
<input type="hidden" id="locphoto" value="{{ mbrs.photoName|safe }}" />
Previous answer are correct but I think they need some correction putting safe in variable jinja:
So i've looked around and it seems nobody has had the same problem that I am having to cause this seemingly common error. I am rendering some forms in my html as follows:
<form method="post" action="">
{{ tags_formset.management_form }}
<!-- code displaying this formset -->
...
<!-- -->
<form method="post" action="">
{{ add_all_form.management_form }}
{{ add_all_form.addTagsToAll }}
<input type="submit" value="Add To Displayed Applicants" />
</form>
<form method="post" action="">
{{ remove_all_form.management_form }}
{{ remove_all_form.removeTagsFromAll }}
<input type="submit" value="Remove From Displayed Applicants" />
</form>
<input type="submit" value="Save Changes" />
</form>
When i did not have the two inner forms the formset is displayed correctly and the submit button works to submit the form. When i added the 2nd two forms a couple of problems occured:
-The submit button stopped working (though pressing enter while one of the formset's fields is selected still submits the form
-The add_all_form's submit works and it functions propperly (not a problem but interesting concerning the next point...)
-The remove_all_form does not work ad throughs the 'ManagementForm data is missing or has been tampered with' validation error.
Here is the views.py code that creats the forms:
TagsFormSet = formset_factory(TagsForm, formset=TagFormSet, extra=applicantQuery.count())
if request.method == 'POST':
tags_formset = TagsFormSet(request.POST, request.FILES, prefix='tags', applicants=applicantQuery)
add_all_form = TagAddAllForm(request.POST, request.FILES, prefix='addForm', applicants=applicantQuery)
remove_all_form = TagRemoveAllForm(request.POST, request.FILES, prefix='removeForm', applicants=applicantQuery)
redirect = False
if tags_formset.is_valid():
for tagForm in tags_formset.forms:
if 'tags' in tagForm.cleaned_data:
tagForm.saveTags()
if 'removeTags' in tagForm.cleaned_data:
tagForm.deleteTags()
redirect = True
if add_all_form.is_valid():
if 'addTagsToAll' in add_all_form.cleaned_data:
add_all_form.saveTagsToAll()
redirect = True
if remove_all_form.is_valid():
if 'removeTagsFromAll' in remove_all_form.cleaned_data:
remove_all_form.deleteTagsFromAll()
redirect = True
if redirect:
return http.HttpResponseRedirect('')
else:
initForms = []
tags_formset = TagsFormSet(prefix='tags', applicants=applicantQuery)
add_all_form = TagAddAllForm(prefix='addForm', applicants=applicantQuery)
remove_all_form = TagRemoveAllForm(prefix='removeForm', applicants=applicantQuery)
I literally can not figure out what is going wrong. I don't know why the add_all_form works when the remove_all_form does not, as i basically copy and pasted everything involved (if you need i can post the code from the Forms.py file but I don't think the problem is there...)
Please help!
You should use only one <form> tag. You can have as many submit button as you want here and can display as many forms as you want, but all should be inside a single <form> tag.
Then all the management data will be sent properly in form submit and your issue should be fixed.
<form method="post" action="">
{{ tags_formset.management_form }}
<!-- code displaying this formset -->
...
<!-- -->
{{ add_all_form.management_form }}
{{ add_all_form.addTagsToAll }}
<input type="submit" value="Add To Displayed Applicants" />
>
{{ remove_all_form.management_form }}
{{ remove_all_form.removeTagsFromAll }}
<input type="submit" value="Remove From Displayed Applicants" />
<input type="submit" value="Save Changes" />
Your view can remain as it is.
I wrote this contact form with some help from the Django docs:
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
name = form.cleaned_data['name']
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
sender = form.cleaned_data['sender']
cc_myself = form.cleaned_data['cc_myself']
recipients = ['name#e-mail.com']
if cc_myself:
recipients.append(sender)
m = '%s\n\n-----------------\n%s' % (message, name)
from django.core.mail import send_mail
send_mail(subject, m, sender, recipients)
return HttpResponseRedirect('/kontakt/tack/')
else:
form = ContactForm()
return render(request, 'contact/index.html', {
'form': form,
})
The problem is that if the form doesn't validate it returns an empty form. That could be a big annoyance for someone that have written a long message but forgot to put his name in or something.
I've fixed that by change the the code below to last else: to:
try:
form = ContactForm(form.cleaned_data)
except UnboundLocalError:
form = ContactForm()
I needed to try-statement for the first rendering of the page when no form.cleaned_data yet exist. This works, but it seems like a rather ugly hack.
Is there some standard way to use the text from previous fill in when re-rendering the form without my ugly try-except solution?
Template on request
{% block content %}
<div id="contact-generic-container">
<p>Lorem ipsum (not really, but ain't relevant).</p>
</div> <!-- #contact-generic-container -->
<div id="contact-form" class="clearfix">
<br/>
<h2>Kontakta oss!</h2>
<form action="/kontakt/" method="post">
{{ form.non_field_errors }}
<fieldset class="text">
{% csrf_token %}
<div class="field-wrapper">
<label for="id_name">Namn:</label>
{{ form.name }}
{{ form.name.errors }}
</div>
<div class="field-wrapper">
<label for="id_sender">E-post:</label>
{{ form.sender }}
{{ form.sender.errors }}
</div>
<div class="field-wrapper">
<label for="id_subject">Ämne:</label>
{{ form.subject }}
{{ form.subject.errors }}
</div>
</fieldset>
<fieldset class="text">
<div class="field-wrapper">
<label for="id_message">Meddelande:</label><br/>
{{ form.message }}
{{ form.message.errors }}
</div>
<div class="field-wrapper">
<label for="id_cc_myself">Kopia till mig själv:</label>
{{ form.cc_myself }}
{{ form.cc_myself.errors }}
</div>
<input type="submit" value="Skicka" />
<fieldset class="text">
</form>
</div>
{% endblock %}
Here's a simple way to always either init the form with the POST data, or nothing:
def contact(request):
form = ContactForm(request.POST or None)
if request.POST and form.is_valid():
Then remove your else block.
The form will always have data in it, and won't throw an error if it's just a GET.
Since I'm using some Python trickery, allow me to explain.
Python supports and/or operator in expressions.
When evaluting an or python will stop when the first value returns a truthy value.
When evaluating an and, Python will stop if the first condition returns false.
So, when we init the form, we ask Python to assign either the value of request.POST (if it's true), or None, if request.POST is false (which it will be if it's empty). This inits the form with the values from request.POST correctly when there's a POST, but None otherwise.
In our if statement, if request.POST is false (which it will be if request.POST is empty), Python stops there and never calls form.is_valid(), so we don't have to worry about validation running if the POST isn't present.
Neat huh?