Passing information using HTML and Flask - python

I'm working on a uni project that involves logging films, sort of like Letterboxd. I've made a search page where users enter a keyword when looking for a movie, this then prints the results supplied by the tmdb api. Below is the code in my routes.py file for the search and results page:
#app.route('/search-movie', methods=['GET', 'POST'])
def m_search():
form = MovieSearch()
if form.validate_on_submit():
user_search = urllib.parse.quote(form.movieName.data)
complete_url = search_url + user_search + "&page=1"
conn = urllib.request.urlopen(complete_url)
json_data = json.loads(conn.read())
return render_template('search_results.html', results=json_data["results"], term=form.movieName.data)
return render_template('movie_search.html', form=form)
The following is the code in the html file for that page:
{% block content %}
<h1> results for "{{term}}"</h1>
<h2> click movie name to log it</h2>
<div class="movie-list-container">
{% for movie in results %}
<div class="movie-card">
<img src="https://image.tmdb.org/t/p/w500/{{movie.poster_path}}" alt="">
<!-- <h3> {{movie.title}} ({{movie.release_date[:4]}})</h3> -->
<h3><a class="log-link" href="{{ url_for('log_movie', movieid=movie.id) }}"> {{movie.title}} ({{movie.release_date[:4]}}) </a></h3>
</div>
{% endfor %}
</div>
{% endblock %}
As you can see in the line I commented out, previously it would just display the movie title and release year. However I wanted to change is so that if the user presses the movie name, they are taken to a page where they add information through a form such as their rating, the date they watched the movie, and a review.
This is how it's done on Letterboxd I wanted mine to be pretty much the same.
I want to be able to show the movie name, release date and poster for the movie they pressed on the logging page, and I tried this in the h3 by passing through movieid=movie.id . From there in my routes.py file I wrote the following code
#app.route('/log-movie', methods=['GET', 'POST'])
def log_movie(movieid):
log_url = info_url + movieid + "?api_key=" + api_key
conn = urllib.request.urlopen(log_url)
json_data = json.loads(conn.read())
form = LogMovie()
if form.validate_on_submit():
log_data = Diary(date_watched=form.dateWatched.data, movie_name=mname, release_date=myear, user_rating=form.movieRating.data,
rewatch=form.movieRewatch.data, review=form.movieReview.data, logger=current_user)
db.session.add(log_data)
db.session.commit()
return redirect(url_for('home'))
return render_template('log_movie.html', form=form, results=json_data["results"])
My idea was to simply get the movieid so that I can then request the information from the api again, so that I can pass it through like I did when displaying the results in the html code I added above.
When storing to the database, I have the variables mname, myear. These were from a previous attempt where I wished to pass in the movie year and release date from the HTML without needing to call upon the api again in routes.py. When I couldn't get the multiple variables to pass, that's when I changed it to just movieid. I forgot to change these back but should I manage to pass the information from the HTML, I may go back to this version.
I keep getting an error TypeError: log_movie() missing 1 required positional argument: 'movieid' and I can't seem to find an answer on google. I was wondering if anyone knew why, or a better way of achieving what I want?
I've never asked a question before so let me know if I need to provide more information.
Many Thanks! :)

#app.route('/log-movie', methods=['GET', 'POST'])
def log_movie(movieid):
I'm just pointing out that. This is where your error is. If you want to get the movieid here, you're route should be
#app.route('/log-movie/<movieid>', methods=['GET', 'POST'])
In that way, if you GET /log-movie/12345, the movieid will be 12345.
Edit :
Just to be clear, i just pointed out where your error is, if you need more help about this, you can still make a comment, i'll edit my answer to other question :)

Related

My delete function isn't working. How do I get it work?

My delete function in Python doesn't delete my user's posts. It only takes the user back to the homepage. After clicking the "delete link" the post is supposed to disappear but it's not.
Here's the delete function in Python:
#app.route('/delete/post/<int:id>')
def delete_post(id):
if 'user_id' not in session:
return redirect('/logout')
data = {
"id": id
}
models_post.Post.delete(data)
return redirect('/home')
Here's the #classmethod code that goes with it:
#classmethod
def delete(cls,data):
query = "DELETE FROM posts WHERE posts.id = %(id)s;"
return connectToMySQL(cls.db_name).query_db(query,data)
Here's the HTML code as well:
<div class="card-body">
{% for post in posts %}
<h3>{{post.title}}</h3>
<h6>Posted by: {{post.user.username}}</h6>
<p>{{post.content}}</p>
Edit
Delete
{% endfor %}
</div>
I never get any error message after clicking the delete link, and no errors show up in my VS Code Terminal, either.
You seem to be missed committing the changes made by the query

Flask retrieve element from for loop

I have a music app project which I am working on that uses Flask and SQLalchemy to display search results when you search for a given song. At the moment, I am trying to create a "song page" if you will that displays details regarding the specific song selected. My flask for loop is as such:
{%for row in result%}
<li class="product-result">
<div class="result">
<h3 style="width: 60%; font-size: 95%;" class="result_title">{{row.title}}</h3>
Similar to {{search}}</h4>
</div>
</li>
{%endfor%}
I want to store the row.title in some sort of session variable that can be passed to the song details page when the result is clicked. I am not sure the proper course of action to do this- hence the question. Any help would be greatly appreciated.
Instead try the following in your html loop:
Detail
and then in your route:
# get a specified post by id
#post_bp.route('/detail/<int:id>/<title>/', methods=['POST', 'GET'])
def detail(id, title):
if request.method =='GET':
# get comments under specific post
comment = db.session.query(Comment).filter(Comment.post_id==id).order_by(Comment.id.desc())
# count comments under specific post
no_of_comments = db.session.query(Comment).filter(Comment.post_id == id).count()
# get post under specific id
post = Post.query.get(id)
user = User.query.get(post.author_id)
return render_template('post.html', post=post, user=user, comments = comment, total_comment = no_of_comments)
N.B: This is my sample. You can convert to your own.

Add Validation Error message to rendered template in Django (via views.py)

I've written a validation that checks to make sure a certain number of images are uploaded before the user can move on to another view. I know that the validation works, because I have successfully redirected them back to the upload page if they don't have enough images and to the preview page if they do.
CODE
#login_required
def preview_website(request, slug):
student = Student.objects.get(slug=slug)
if student.studentimage_set.filter(image_type="portrait").exists() and
student.studentimage_set.filter(image_type="interest1").exists() and
student.studentimage_set.filter(image_type="interest2").exists() and
student.studentimage_set.filter(image_type="interest3").exists():
return render(request, 'students/preview_website.html', {
'student': student,
})
else:
return redirect('upload_student_image', slug=student.slug)
The issue I'm having now is the notification of the error. I would like to let them know that they need to add more images in order to move forward. Is there a way, through views.py, to print a notification at the top of the template? (I know it is more standard to use validation in the forms or models, but this is a special case.)
I've tried the following, but I get an error page with no clean way back to the image upload page:
else:
raise ValidationError("Please make sure you have uploaded 4 images!")
I also tried the following, but I didn't see anything printed on the template page:
else:
msg = "Please make sure you have uploaded 4 images!"
I've already read through: 17105947, but the solution didn't work for me.
Any ideas? I'm pretty new to programming and Django, so I hope this is just a small oversight on my part and a relatively easy fix!
You can add the error message in your template behind an if scope.
In your view:
#login_required
def preview_website(request, slug):
student = Student.objects.get(slug=slug)
if student.studentimage_set.filter(image_type="portrait").exists() and
student.studentimage_set.filter(image_type="interest1").exists() and
student.studentimage_set.filter(image_type="interest2").exists() and
student.studentimage_set.filter(image_type="interest3").exists():
return render(request, 'students/preview_website.html', {
'student': student})
else:
error_message = "Please make sure you have uploaded 4 images!"
return render(request, 'students/preview_website.html', {
'student': student, 'error_message': error_message})
Add this to the top of your template
{% if error_message %}
<p>{{ error_message }}</p>
{% endif %}
You can do it by doing
length(request.FILES)
in view and if length is less than what you want you can return to upload html
return render_to_response('upload.html',{'error':error},context_instance=RequestContext(request))
In html page use following style you can show error
{% if error %}
{{ error }}
{% endif%}

Flask putting form into URL

I've been working on a form that sends data to a scraper and simultaneously generates a URL from form input. The returned templates works flawlessly, but the URL change ends up giving me the entire form in the URL and I can't figure out why.
The URL ends up looking like this:
http://localhost/options/%3Cinput%20id%3D%22symbol%22%20name%3D%22symbol%22%20type%3D%22text%22%20value%3D%22%22%3E
I'd like it to look like this:
http://localhost/options/ABC
Form class:
class OptionsForm(Form):
symbol = StringField('Enter a ticker symbol:', validators=[Required(), Length(min=1, max=5)])
submit = SubmitField('Get Options Quotes')
Views:
# Where the form data ends up
#app.route('/options/<symbol>', methods=['GET', 'POST'])
def options(symbol):
# Created this try/except so I could test functionality - for example, I can do 'localhost/options/ABC' and it works
try:
symbol = request.form['symbol']
except:
pass
return render_template('options.html', symbol=symbol, company_data=OS.pull_data(symbol, name=True))
# Where the form lives
#app.route('/', methods=['GET', 'POST'])
def index():
form = OptionsForm()
print(form.errors)
if form.validate_on_submit():
return redirect(url_for('options', symbol=form.symbol.data))
return render_template('index.html', options_form=form)
Template:
<div id="options_div">
<form method="POST" name="symbol_form" action="{{ url_for('options', symbol=options_form.symbol) }}">
{{ options_form.hidden_tag() }}
{{ options_form.symbol(size=10) }}
{{ options_form.submit(size=10) }}
</form>
Any help would be appreciated.
Try adding enctype='multipart/form-data' to the form tag. It looks like your form is using application/x-www-form-urlencoded, the default.
Edit OK so check this out. When your template is being rendered there is no value in that data attribute (In the url_for call). When not referencing the data attribute (as your original question shows), you're referencing the actual form element (which is why you see all of that html being passed in the url). Here are your options (that I see):
Use some kind of frontend javascript to bind the form's action attribute to the value in the input box. Something like angular would help for this (but is overkill if you don't use any of its other features).
Just have the form POST to /options (no symbol in url). Then, grab the symbol attribute from the form data.

What is the cause of the Bad Request Error when submitting form in Flask application?

After reading many similar sounding problems and the relevant Flask docs, I cannot seem to figure out what is generating the following error upon submitting a form:
400 Bad Request
The browser (or proxy) sent a request that this server could not understand.
While the form always displays properly, the bad request happens when I submit an HTML form that ties to either of these functions:
#app.route('/app/business', methods=['GET', 'POST'])
def apply_business():
if request.method == 'POST':
new_account = Business(name=request.form['name_field'], email=request.form['email_field'], account_type="business",
q1=request.form['q1_field'], q2=request.form['q2_field'], q3=request.form['q3_field'], q4=request.form['q4_field'],
q5=request.form['q5_field'], q6=request.form['q6_field'], q7=request.form['q7_field'],
account_status="pending", time=datetime.datetime.utcnow())
db.session.add(new_account)
db.session.commit()
session['name'] = request.form['name_field']
return redirect(url_for('success'))
return render_template('application.html', accounttype="business")
#app.route('/app/student', methods=['GET', 'POST'])
def apply_student():
if request.method == 'POST':
new_account = Student(name=request.form['name_field'], email=request.form['email_field'], account_type="student",
q1=request.form['q1_field'], q2=request.form['q2_field'], q3=request.form['q3_field'], q4=request.form['q4_field'],
q5=request.form['q5_field'], q6=request.form['q6_field'], q7=request.form['q7_field'], q8=request.form['q8_field'],
q9=request.form['q9_field'], q10=request.form['q10_field'],
account_status="pending", time=datetime.datetime.utcnow())
db.session.add(new_account)
db.session.commit()
session['name'] = request.form['name_field']
return redirect(url_for('success'))
return render_template('application.html', accounttype="student")
The relevant part of HTML is
<html>
<head>
<title>apply</title>
</head>
<body>
{% if accounttype=="business" %}
<form action="{{ url_for('apply_business') }}" method=post class="application_form">
{% elif accounttype=="student" %}
<form action="{{ url_for('apply_student') }}" method=post class="application_form">
{% endif %}
<p>Full Name:</p>
<input name="name_field" placeholder="First and Last">
<p>Email Address:</p>
<input name="email_field" placeholder="your#email.com">
...
The problem for most people was not calling GET or POST, but I am doing just that in both functions, and I double checked to make sure I imported everything necessary, such as from flask import request. I also queried the database and confirmed that the additions from the form weren't added.
In the Flask app, I was requesting form fields that were labeled slightly different in the HTML form. Keeping the names consistent is a must. More can be read at this question Form sending error, Flask
The solution was simple and uncovered in the comments. As addressed in this question, Form sending error, Flask, and pointed out by Sean Vieira,
...the issue is that Flask raises an HTTP error when it fails to find a
key in the args and form dictionaries. What Flask assumes by default
is that if you are asking for a particular key and it's not there then
something got left out of the request and the entire request is
invalid.
In other words, if only one form element that you request in Python cannot be found in HTML, then the POST request is not valid and the error appears, in my case without any irregularities in the traceback. For me, it was a lack of consistency with spelling: in the HTML, I labeled various form inputs
<input name="question1_field" placeholder="question one">
while in Python, when there was a POST called, I grab a nonexistent form with
request.form['question1']
whereas, to be consistent with my HTML form names, it needed to be
request.form['question1_field']

Categories