I am working on a page in my application where a user submits a review on a review page using WTForms, and upon clicking the submit button, the text "Success" should display. This is currently not happening, as upon clicking submit, the review page simply reloads itself but with an error.
I have created other pages requiring form validation which has worked, except for this one and I can't seem to figure out why, despite replicating the code from other pages.
Any insights are much appreciated!
Screenshot of error image
Here is my HTML code
<html>
<body>
{% for books in books %}
{{books.title}}
{% endfor %}
<form action = "{{ url_for('review', isbn=books.isbn)}}", method = "POST">
<div>
<br>{{ form.review(class_='form-control',placeholder ='Leave a review here')}}</br>
<ul>
{% for error in form.review.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
<br>{{ form.rating(class_='form-control',placeholder ='Leave a rating here')}}</br>
<ul>
{% for error in form.rating.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{{form.csrf_token}}
{{form.submit_button }}
</div>
</form>
</body>
</html>
Python Code
#app.route("/review/<string:isbn>", methods = ['GET', 'POST'])
#login_required
def review(isbn):
review = Books.query.filter_by(isbn=isbn).all()
review_form = ReviewForm()
if review_form.validate_on_submit():
return "Success"
return render_template("review.html", books = review, form = review_form)
WTForms fields
class ReviewForm(FlaskForm):
""" Review """
review = StringField('review_label', widget=TextArea(), validators = [InputRequired(message = "Review can't be empty")])
rating = StringField('rating_label', validators= [InputRequired(message="Please input a rating")])
submit_button = SubmitField('Submit')
I would suggest the following. Let me know if this helps! :)
from markupsafe import escape
#app.route("/review/<string:isbn>", methods = ['GET', 'POST'])
#login_required
def review(isbn):
review = Books.query.filter_by(isbn=isbn).all()
review_form = ReviewForm()
if review_form.validate_on_submit():
return "Success %s" % escape(isbn)
return render_template("review.html", books = review, form = review_form)
reference flask documentation
Related
I am running the latest Flask, Python 3.10. The app is installed on a Windows IIS Server. I have three forms on a single page. There is one submit button. Each form has a unique ID.
When I fill out one or all of the forms and hit the Submit button there is a view Class handler for the POST request. It is being called but the form data is missing.
One of the form elements looks like the following; the other two are similar but without the render_submit_field call:
<form id="add_session_form" action="{{ url_for('session_api') }}" method="POST" novalidate
role="form">
{{ session_form.hidden_tag() }}
{% for field in session_form %}
{% if not field.flags.hidden %}
{% if field.type == 'BooleanField' %}
{{ render_checkbox_field(field, tabindex=loop.index * 300) }}
{% elif field.type == 'SelectField' or field.type == 'SelectMultipleField' %}
{{ render_select_field(field, tabindex=loop.index * 300) }}
{% elif field.type == 'SubmitField' %}
{{ render_submit_field(field, tabindex=loop.index * 300) }}
{% else %}
{{ render_field(field, tabindex=loop.index * 300) }}
{% endif %}
{% endif %}
{% endfor %}
</form>
The POST method of the view Class looks like:
#staticmethod
def post():
try:
db_session = db()
school_form = SchoolForm(obj=current_user)
class_form = ClassForm(obj=current_user)
session_form = SessionForm(obj=current_user)
school_list = SchoolAPI.get_all_schools()
if school_form.validate_on_submit():
# Copy form fields to user_profile fields
school = School()
school_form.populate_obj(school)
db_session.add(school)
db_session.commit()
school_form = SchoolForm()
if class_form.validate_on_submit():
class_ = Class()
class_form.populate_obj(class_)
db_session.add(class_)
db_session.commit()
classes_form = ClassForm()
if session_form.validate_on_submit():
session = Session()
session_form.populate_obj(session)
db_session.add(session)
db_session.commit()
session_form = ClassForm()
return render_template(
'main/user_page.html',
current_user=current_user,
school_form=school_form,
class_form=class_form,
session_form=session_form,
school_list=school_list
)
except Exception as e:
return Response(repr(e), status=HTTPStatus.BAD_REQUEST)
The curious part is that this was working at the beginning of 2022. I was pulled onto another project and have recently been put back on this one. I updated all dependencies and am not seeing any errors.
I am not sure what to check at this point.
Ideas?
I am trying to create a way to edit individual blog posts from their individual html. Here are the relevant files and trace back. I am somewhat understanding that the issue lies in blog_post.id being due to the fact that blog_post has not carried over from the for loop on blog_posts.html. I have read up on others having this issue and they all structured their pages to have the edit button being inside the original for loop, which makes sense in hindsight. BUT now that I have run into this issue, I'm determined to understand how I can solve it without going back and restructuring my pages to align with the others I saw.
urls.py
from django.urls import path
from . import views
app_name = 'blogs'
urlpatterns = [
# Home page
path('', views.index, name='index'),
path('blog_posts/', views.blog_posts, name='blog_posts'),
path('blog_posts/<int:blog_post_id>/', views.blog_post, name='blog_post'),
path('new_blog_post/', views.new_blog_post, name='new_blog_post'),
path('edit_blog_post/<int:blog_post_id>/', views.edit_blog_post, name='edit_blog_post'),
]
views.py
from .models import BlogPost
from .forms import BlogPostForm
def index(request):
"""Home page for Blog."""
return render(request, 'blogs/index.html')
def blog_posts(request):
"""Show all Blog Posts."""
blog_posts = BlogPost.objects.order_by('date_added')
context = {'blog_posts': blog_posts}
return render(request, 'blogs/blog_posts.html', context)
def blog_post(request, blog_post_id):
"""Show details of an individual blog post."""
blog_post = BlogPost.objects.get(id=blog_post_id)
title = blog_post.title
id = blog_post_id
date = blog_post.date_added
text = blog_post.text
context = {'title': title, 'text': text, 'date': date}
return render(request, 'blogs/blog_post.html', context)
def new_blog_post(request):
"""Add a new blog post"""
if request.method != 'POST':
# No data submitted, create a blank form.
form = BlogPostForm()
else:
# POST data submitted, process data.
form = BlogPostForm(data=request.POST)
if form.is_valid():
form.save()
return redirect('blogs:blog_posts')
# Display a blank or invalid form.
context = {'form': form}
return render(request, 'blogs/new_blog_post.html', context)
def edit_blog_post(request, blog_post_id):
"""Edit an existing blog post's title or text."""
blog_post = BlogPost.objects.get(id=blog_post_id)
if request.method != 'POST':
# Initial request, prefill with the current data.
form = BlogPostForm(instance=blog_post)
else:
# POST data submitted; process new data.
form = BlogPostForm(instance=blog_post, data=request.POST)
if form.is_valid():
form.save()
return redirect('blogs:blog_post', blog_post_id=blog_post.id)
context = {'blog_post': blog_post, 'form': form}
return render(request, 'blogs/edit_blog_post.html', context)
models.py
from django.db import models
class BlogPost(models.Model):
"""A post the user is posting on their blog."""
title = models.CharField(max_length=200)
text = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
"""Return a string representation of the model"""
return f"{self.title.title()}"
blog_posts.html
{% extends 'blogs/base.html' %}
{% block content %}
<p>Blog Posts</p>
<ul>
{% for blog_post in blog_posts %}
<li>
{{ blog_post }}
</li>
{% empty %}
<li>No posts have been made yet.</li>
{% endfor %}
</ul>
Add a new blog post
{% endblock content %}
blog_post.html
{% extends 'blogs/base.html' %}
{% block content %}
<p>Blog Post: {{ title }}</p>
<p>Entry:</p>
<p>{{ text }}</p>
<p>{{ date }}</p>
<p>
Edit Blog Post
</p>
{% endblock content %}
edit_blog_post.html
{% extends "blogs/base.html" %}
{% block content %}
<p>
{{ blog_post }}
</p>
<p>Edit Blog Post</p>
<form action="{% url 'blogs:edit_blog_post' blog_post.id %}" method='post'>
{% csrf_token %}
{{ form.as_p }}
<button name="submit">Save Changes</button>
</form>
{% endblock content %}
Reverse for 'edit_blog_post' with arguments '('',)' not found. 1 pattern(s) tried: ['edit_blog_post/(?P<blog_post_id>[0-9]+)/\Z']
3 {% block content %}
4
5 <p>Blog Post: {{ title }}</p>
6
7 <p>Entry:</p>
8
9 <p>{{ text }}</p>
10 <p>{{ date }}</p>
11
12 <p>
13 Edit Blog Post
14 </p>
15
16 {% endblock content %}
If I've read the question correctly, You're getting the error becuase you are not providing the necessary ID to the URL construction part of your template.
You're separating out the elements (date, content etc) to send to the template, but not passing the ID at the same time. You could send the ID in as a separate context variable, but that's extra typing for no real reward.
It's easiest to pass in the post itself via context and refer to its attributes in the template - I think it makes it easier to read also. That way the ID is there when you need to contruct the edit link, and if you change the model to possess extra fields, you don't need to convert and add to the context as the whole post is already there.
views.py
def blog_post(request, blog_post_id):
"""Show details of an individual blog post."""
blog_post = BlogPost.objects.get(id=blog_post_id) #this is all we need
context = {"blog_post_context": blog_post}
return render(request, 'blogs/blog_post.html', context)
blog_post.html
{% extends 'blogs/base.html' %}
{% block content %}
<p>Blog Post: {{ blog_post_context.title }}</p>
<p>Entry:</p>
<p>{{ blog_post_context.text }}</p>
<p>{{ blog_post_context.date }}</p>
<p>
Edit Blog Post
</p>
{% endblock content %}
If that all works, look into using get_object_or_404 rather than Post.objects.get for some additional robustness.
I assume you got the error when you try visiting the blog_post.html page. If I'm correct, then here's an approach you could take...
In your views.py
def blog_post(request, blog_post_id):
"""Show details of an individual blog post."""
# blog_post = BlogPost.objects.get(id=blog_post_id)
blog_post = get_object_or_404(id=blog_post_id) # Recommended
# Commented lines below are somewhat not necessary...
# title = blog_post.title
# id = blog_post_id
# date = blog_post.date_added
# text = blog_post.text
context = {'blog_post': blog_post}
return render(request, 'blogs/blog_post.html', context)
edit_blog_post.html is expecting an object called blog_post to be able to access the blog_post.id for {% url 'blogs:edit_blog_post' blog_post.id %}.
Now within the edit_blog_post.html file.
{% block content %}
<p>Blog Post: {{ blog_post.title }}</p>
<p>Entry:</p>
<p>{{ blog_post.text }}</p>
<p>{{ blog_post.date_added }}</p>
<p>
Edit Blog Post
</p>
{% endblock content %}
I am working on building a web application using Flask framework and Python.
Using one of the html pages, i am getting inputs from the user and processing them once the user clicks on the Submit button. The requirement is that, once the user clicks on the Submit button, i would like to show a modal notification (or any notification) that the data is being processed.
The code for the Submit button in process_data.html is -
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
I tried adding modal code to it as follows -
<div class="form-group">
{{ form.submit(class="btn btn-outline-info", data-toggle="modal", data-target="#exampleModal") }}
</div>
but it failed with jinja2.exceptions.TemplateSyntaxError: invalid syntax for function call expression.
routes.py code -
#app.route("/process_data", methods=['GET','POST'])
def process_data():
form = ProcessDataForm()
if form.validate_on_submit():
posts = get_data('process_data', version=form.version.data, cn=form.cn.data, ip=form.ip.data)
if posts:
flash(f'Processing Complete!','success')
else:
flash(f'Processing failed','warning')
return render_template('process_data.html', title='Process Data', form=form, posts=posts)
return render_template('process_data.html', title='Process Data', form=form)
Can someone please help? Thanks!
You can do it using Message Flash.
A sample code is provided here:
from flask import flash
#app.route("/process_data", methods=['GET','POST'])
def process_data():
form = ProcessDataForm()
if form.validate_on_submit():
flash('Data is being processed')
posts = get_data('process_data', version=form.version.data, cn=form.cn.data, ip=form.ip.data)
return render_template('process_data.html', title='Process Data', form=form, posts=posts)
return render_template('process_data.html', title='Process Data', form=form)
In you html :
<!doctype html>
<title>My Application</title>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block body %}{% endblock %}
Please find the documentation for more explaination.
I follow Tango with Django's tutorial for adding the like button. I thought it worked, but it only seems to be working for the very first model instance in a list. You see, I have a view where all the objects in a list is displayed. My code looks like this:
views.py
def content_list(request):
posts = Post.objects.all()
return render(request, 'app/content_list.html', {'posts' : posts})
models.py
class Post(models.Model):
user = models.ForeignKey(User, related_name = 'user_posts')
title = models.CharField(max_length = 140)
votes = models.IntegerField(default = 0)
content_list.html - the template
{% for post in posts %}
<h1>{{post.title}}</h1>
<p><strong id="like_count">{{post.votes}}</strong> points</p>
{% if request.user.is_authenticated %}
<button id="likes" data-post-id="{{post.id}}" class="btn btn-purple" type="button">
<span>Upvote</span>
</button>
{% endif %}
{% endfor %}
In views.py, I have another function which handles the post-liking:
#login_required
def like_post(request):
post_id = None
# Getting the id of the post from the template
if request.method == 'GET':
post_id = request.GET['post_id']
likes = 0
if post_id:
post = Post.objects.get(id = int(post_id))
if post:
likes = post.votes + 1
post.votes = likes
post.save()
return HttpResponse(likes)
And finally, my ajax file which does the actual work:
$('#likes').click(function(){
var p_id;
p_id = $(this).attr("data-post-id");
$.get('/like_post/', {post_id: p_id}, function(data){
$('#like_count').html(data);
$('#likes').hide();
});
});
My urls.py in my app folder has the following line in the url patterns:
# Liking a post
url(r'^like_post/$', views.like_post, name='like_post'),
What am I missing? The uproot button for the other posts don't hide either, which they should because of my ajax code. How can I fix this problem?
Thanks!
By applying what was mentionned in comments, your server code could become:
{% for post in posts %}
<h1>{{post.title}}</h1>
<p><strong id="like_count">{{post.votes}}</strong> points</p>
{% if request.user.is_authenticated %}
<button data-post-id="{{post.id}}" class="likes btn btn-purple" type="button">
<span>Upvote</span>
</button>
{% endif %}
{% endfor %}
And the javascript part is now:
$('.likes').click(function(){
var p_id;
p_id = $(this).attr("data-post-id");
$.get('/like_post/', {post_id: p_id}, function(data){
$('#like_count').html(data);
$(this).hide();
});
});
I'm working with flask and have a html page that contains of user name(user.html) which took from table, Now How can I see more detail by clicking on each of users(which route to profile)?
I don't use login for app So I don't want to use g
app.py
# am I doing it right?
#app.route('/profile/<int:id>')
def profile(id=None):
detail = Contacts.query.get(id)
return render_template('profile.html', detail= detail , id=id)
user.html
{% extends "layout.html" %}
{% block content %}
<h2>show user</h2>
{% for contact in contact %}
# I got error when I click on each user name to see their 'profile'
#I guess because of id How can Solve it?
#error BuildError: ('profile', {}, None)
<strong>name:</strong><a href={{url_for('profile')}}>
{{ contact.name}}</a><br>
{% endfor %}
{% endblock %}
profile.html
{% extends "layout.html" %}
{% block content %}
<h2>show user profile</h2>
# how can I make it specific for each row of table(each user)?
{% for detail in Contacts %}
<strong>name:</strong> {{ detail.name}} <br>
<strong>email:</strong> {{ detail.email }} <br>
<strong>age:</strong> {{ detail.age}} <br>
<br>
{% endfor %}
{% endblock %}
model.py
class Contacts(db.Model):
__tablename__ = "Contacts"
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(50))
email = db.Column(db.String(50))
age = db.Column(db.Integer)
submit = SubmitField("Submit")
I noticed two things in your code:
# this
<a href={{url_for('profile')}}>
# should be
<a href={{url_for('profile', id=contact.id)}}>
# otherwise Flask can't find the route, because it needs an id
And the other one:
{% for detail in Contacts %}
There is no such thing as a Contacts variable in your template, because your view function does not send it. Just get rid of the loop and use detail directly, because it's what you sent to the template.