I just start learning Flask and i'm trying to use a simple search form on my news headline site but when i submit the search it seems that it's not working, i'm using a simple flask get request
Here is my code:
import feedparser
from flask import Flask
from flask import render_template
from flask import request
app = Flask(__name__)
rss_feed = {'wsj': "https://feeds.a.dj.com/rss/RSSWSJD.xml",
'cnbc': "https://www.cnbc.com/id/100003114/device/rss/rss.html",
'washington_post': "http://feeds.washingtonpost.com/rss/politics?itid=lk_inline_manual_2"}
#app.route("/")
def get_news():
query = request.args.get('publication')
if not query or query.lower not in rss_feed:
publication = 'wsj'
else:
publication = query.lower
feed = feedparser.parse(rss_feed[publication])
return render_template("home.html", articles=feed['entries'])
if __name__ == "__main__":
app.run(port=5000, debug=True)
and my html code:
<html>
<head>
<title>Headlines</title>
</head>
<body>
<h1>Headlines</h1>
<form>
<input type="text" name="publication" placeholder="search">
<input type="submit" value="Submit">
</form>
{% for article in articles %}
<b>{{article.title}}</b><br />
<i>{{ article.published }}</i><br />
<p>{{ article.summary }}</p>
<hr />
{% endfor %}
</body>
</html>
Now when i submit a search for 'cnbc' it still display the result for 'wsj' what wrong with my code?
I add 4 different notes in the code as comment, please have a look in the Glossary.
Here is the solution:
Python file, here is where the error occur, long stoyry short your if statement was always returning True, hence the publication variable was set as 'wsj
import feedparser
from flask import render_template, request, Flask
# Note 1
app = Flask(__name__)
rss_feed = {'wsj': "https://feeds.a.dj.com/rss/RSSWSJD.xml",
'cnbc': "https://www.cnbc.com/id/100003114/device/rss/rss.html",
'washington_post': "http://feeds.washingtonpost.com/rss/politics?itid=lk_inline_manual_2"}
#app.route("/")
def get_news():
if request.args.get('publication'):
query = request.args.get('publication').lower() # Note 2
print(f'Query is : {query}') # Note 4
if not query or query not in rss_feed: # Note 3
print('Publication is wsj') # Note 4
publication = 'wsj'
else:
print('Publication is the query entry') # Note 4
publication = query
feed = feedparser.parse(rss_feed[publication])
return render_template("view.html", articles=feed['entries'])
return render_template("view.html")
if __name__ == "__main__":
app.run(port=5000, debug=True)
HTML File
<html>
<head>
<title>Headlines</title>
</head>
<body>
<h1>Headlines</h1>
<!-- Note 5 -->
<form method="GET">
<input type="text" name="publication" placeholder="search">
<input type="submit" value="Submit">
</form>
{% for article in articles %}
<b>{{article.title}}</b><br />
<i>{{ article.published }}</i><br />
<p>{{ article.summary }}</p>
<hr />
{% endfor %}
</body>
</html>
NOTES GLOSSARY
Note 1: Have the imports from the same package in the same line
Note 2: Rather then check the query if is lower is better to turn it lower already in the variable as I did.
Note 3: Here is where the real issue was. This statement was always True hence setting the 'publication' variable as 'wsj'. This is because of add 'query.lower' not in rss_feed which obsiously it wasn't. And by the way you should have done it 'query.lower()'
Note 4: Just a reminder to How to find and troubleshot your code.The print() function is the first thing to learn in python and is the most usefull tool for this matter. This is how I found the error in less then 5 minutes because I let the code 'Speak' through the terminal.
Note 5: Few suggestion here, first add a method='GET' (not neccessary) just for readability. Then indent better the HTML file as it was all messed around.
Another solution that may be slightly clearer is this:
#app.route("/")
def get_news():
if request.args.get('publication'):
query = request.args.get('publication').lower()
if not query or query not in rss_feed:
publication = 'wsj'
feed = feedparser.parse(rss_feed[publication])
return render_template("view.html", articles=feed['entries'])
publication = query
feed = feedparser.parse(rss_feed[publication])
return render_template("view.html", articles=feed['entries'])
return render_template("view.html")
Related
I have a flask app that I am trying to append a url parameter to after a form submission. For example, I have a date input as a form, and if I selected 2022-02-17 and pressed the button to submit, I would want the url to then append ?date=2022-02-17. My problem is the its always 1 behind. So on first submit, it is only ?date=, and then if I chose another date that isn't 2022-02-17, the url would then update to ?date=2022-02-17. I have a print to make sure the date is being correctly passed into the handler function for that page as well.
Here is a jinja snippet of the form:
<div class="container">
<p>{{ search_date }}</p>
<form action={{ url_for('past_releases', date=search_date) }} method="POST">
<div class="input-group justify-content-center">
<div class="form-outline">
<input class="form-control" type="date" id="searchdate" name="searchdate">
</div>
<button type="submit" class="btn btn-primary">
<i class="fa fa-search"></i>
</button>
</div>
</form>
</div>
and here is the python function for handling that page:
#app.route("/past-releases", methods=["GET", "POST"])
def past_releases():
"""
Search for past releases' info, based on date.
"""
search_date = None
if request.method == "GET" and request.args.get("date"):
search_date = request.args.get("date")
elif request.method == "POST":
search_date = request.form["searchdate"]
# # we use a try here for when someone might click search on the placeholder
# # date of "mm/dd/yyyy"
print(search_date)
if search_date:
try:
date_selcted = dateutil.parser.parse(search_date).strftime("%B %d, %Y")
except ParserError:
return render_template("past_releases.jinja")
pipelines_and_tasks = get_pipeline_runs_by_date(search_date)
return render_template(
"past_releases.jinja",
date_selected=date_selcted,
pipelines_and_tasks=pipelines_and_tasks,
search_date=search_date,
)
return render_template("past_releases.jinja")
I think you should replace the "method" parameter of the form with the GET method. The parameter in url_for is then no longer required. The selected date is automatically added as a URL parameter when the form is submitted.
In the query using request.args, it is also possible to add a standard parameter as the second argument and, specifying the parameter name type, a function to convert the parameter to a date.
The example below illustrates the described possibility.
Flask (app.py)
from datetime import date
from flask import Flask, render_template, request
app = Flask(__name__)
#app.route('/search')
def search():
search_date = request.args.get(
'search-date',
date.today(),
type=date.fromisoformat
)
# Your filter function here.
return render_template('search.html', **locals())
HTML (templates/search.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Search</title>
</head>
<body>
<form method="get">
<input type="date" name="search-date" value="{{search_date.isoformat()}}" />
<input type="submit" />
</form>
</body>
</html>
Problem: There is an arduous update script we run for work. It takes a lot of time to update because of security issues. We want to make sure we have edited the update script correctly/want to avoid all permission issues.
Solution: Using Flask as a front end gui to eliminate the possibility for adding the wrong value. The Flask app will guarantee correct placement of inputs into the update script by auto generating the script, so we can just copy and paste into SSMS.
The current problem I am having is aesthetics. I was able to generate a flask app using user inputs and exports a SQL script. The problem is, the sql looks horrible, here is an example of the output:
Ideally the output would be multi-line, like all current SQL scripts:
UPDATE table_name
SET ordercomplete = 1
WHERE UserName = 'testuser' and OrderID = 12345
Below are my files:
#templates home.html
<!doctype html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SQL Auto Generate</title>
<link rel="stylesheet" href="https://unpkg.com/purecss#0.6.2/build/pure-min.css" integrity="sha384-UQiGfs9ICog+LwheBSRCt1o5cbyKIHbwjWscjemyBMT9YCUMZffs6UqUTd0hObXD" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
</head>
<h1>SQL Auto Generate</h1>
<h2>Add the following inputs, select 'Submit' to generate a script for SQL</h2>
<body>
<div class="main">
<form class="pure-form" method="POST" action="/">
UserName:<br>
<input type="text" name="UserName"><br>
OrderID:<br>
<input type="number" name="OrderID"><br>
<button type="submit" class="pure-button pure-button-primary" value="Submit">Submit</button>
</form>
</div>
<br>
<div class="main">
{% if UserName %}
<p>
{% print('''
UPDATE table_name
SET ordercomplete = 1
WHERE
UserName = {} and OrderID = {}
'''.format(UserName, OrderID)) %}
</p>
{% endif %}
</div>
</body>
# app.py
from flask import Flask, render_template, request
app = Flask(__name__)
#app.route('/', methods=['GET', 'POST'])
def index():
UserName = ''
OrderID = ''
if request.method == 'POST' and 'UserName' in request.form:
UserName = request.form.get('UserName')
OrderID = int(request.form.get('OrderID'))
return render_template("home.html", UserName=UserName, OrderID = OrderID)
if __name__ == '__main__':
app.run(debug=True)
I also uploaded to github: https://github.com/jpf5046/sqlflask
Ask: Is there a better way to create a text output with multi-line via flask?
By default, HTML ignores extra space, such as line endings. If you want to display the query exactly like you generate it, use <pre>:
<pre>
UPDATE table_name
SET ordercomplete = 1
WHERE
UserName = {{UserName}} and OrderID = {{OrderId}}
</pre>
I think you just need to add br tags in your html
{% if UserName %}
<p>UPDATE table_name <br>
SET ordercomplete = 1 <br>
WHERE <br>
UserName = {{ UserName }} and OrderID = {{ OrderID }}
</p>
{% endif %}
I tried this approach to pass the form input variable to function via url_for method. But its not working. can anyone check and let me know whats wrong in this code.
Function:
#app.route('/report_data/<year>/<week>', methods = ['POST'])
def report_data(year,week):
print year
print woy
HTML Code :
<html>
<body>
<form action="{{ url_for('report_data', year=n_year, week=n_woy) }}" method="post">
<h3> Enter the details </h3>
Year :
<input type="text" name="n_year"> <br>
<br>
Week :
<input type="text" name="n_woy"> <br>
<br>
<input type="submit" value="Submit"> <br>
</form>
</body>
</html>
Issue:
Getting "None" for both variable.
Firstly, How do you provide year, week values before submitting them in your HTML code ?
Did you render the HTML template in your function? If not, how does your flask function knows that you were seeking from that specific form?
If this is your Flask app code -
from flask import Flask, render_template, request
app = Flask(__name__)
#app.route('/report_data', methods = ['POST', 'GET'])
def report_data():
if request.method == 'POST':
result = request.form
query_year = result['n_year'].encode('ascii', 'ignore')
query_week = result['n_woy'].encode('ascii', 'ignore')
print(query_year, query_week)
return render_template('so_test.html')
if __name__ == '__main__':
app.run(debug=True)
And opened the URL -
http://127.0.0.1:5000/report_data
And I get this picture.
Once I post the data, I can see this data in my Flask app console.
('2018','08')
In my make_session function, if the passwords from the forms don't match some credentials, then it returns a string which ends up being stored as a cookie named session. In my index file, I have it set so on index.html, if the cookie value is {}, it asks for login information. If I change what is returned from the make_session function by replacing "invalid" with any integer, the code works as expected.
A bad solution to my problem is to add
except:
data = {}
before the return to my get_saved_data function setting data equal to {} but this ends up with the same results as if I had no cookie in my browser at all, but it gets rid of my error which is here: https://gist.github.com/anonymous/e101aa46f154a075b038
I suspect that the get_saved_data function may be a fault.
Map of my directory:
|---- layout.html
|---- index.html
|--- templates -|
Project -|
|--- test.py
test.py:
from flask import Flask, render_template, redirect, url_for, request, make_response
import json
def get_saved_data(key):
try:
data = json.loads(request.cookies.get(key))
except TypeError:
data = {}
return data
def make_session(form_data):
if form_data.get('username') == "username" and form_data.get('password') == "password":
return "12345"
else:
return "invalid"
app = Flask(__name__)
#app.route('/')
def index():
data = get_saved_data("session")
return render_template('index.html', saves=data)
#app.route('/login', methods=['POST'])
def login():
response = make_response(redirect(url_for('index')))
response.set_cookie("session", make_session(dict(request.form.items())))
return response
app.run(debug=True, host='0.0.0.0', port=8000)
index.html:
{% extends "layout.html" %}
{% block content %}
{% if saves == {}: %}
<p>Please log in.</p>
{% else: %}
<p>Your Session value is: {{ saves }}</p>
{% endif %}
{% if saves == {}: %}
<form action="{{ url_for('login') }}" method="POST">
<p>We take your private information very seriously. All data is encrypted not once but twice! in ROT13 to provide the best security.</p><br />
<label for="username">Please enter your username:</label>
<input type="text" name="username" /><br />
<label for="password">Please enter your password:</label>
<input type="text" name="password" /><br />
<button class="btn">Log In</button>
</form>
{% endif %}
{% endblock %}
layout.html:
<!DOCTYPE html>
<html>
<head>
<title>Character Generator</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
Change your get_saved_data to the following will make any difference?
def get_saved_data(key):
data = json.loads(request.cookies.get(key, '{}'))
return data
return '{}' if there is no such key, make sure json won't complain anything.
While I haven't found a way to directly fix the issue, and using lord63.j's answer saved me a few lines of code, a way to indirectly fix the issue is to save the cookie value as a dictionary by modifying the login function to this:
#app.route('/login', methods=['POST'])
def login():
response = make_response(redirect(url_for('index')))
response.set_cookie("data", json.dumps({"session": make_session(dict(request.form.items()))}))
return response
from them on, in order to access the session value I'll need to use
saves.get('session')
as saves is equivalent to
json.loads(request.cookies.get('data', '{}'))
in the index function
#app.route('/')
def index():
data = get_saved_data("data")
return render_template('index.html', saves=data)
I have started just learning Flask and tried to find an answer how to post to SQLite DB from webform. So far haven't managed to get it work and bit lost with this. I manage to print values from DB based on code sample from simplypython but don't know how to add new ones from webform.
I would need to be able to address elements, open connection to the database, Insert values, save and close the connection. As far as I am aware I should add the POST method to the app.py and use request.form statement to pull the elements when submit button is pressed.
then the code should automatically display all the values on index html, which already works. could you please help me with the code I need to add to app.py file to get values added to DB and what to add to form action webform-section on html file?
index.html
<!DOCTYPE html>
<html>
<head>
<title>Flask Intro</title>
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> -->
</head>
<body>
<div class="container">
<h3 potsit </h3>
{% for post in posts %}
Titleotsikko: {{post.title }} <br>
Postotsikko: {{post.description}}
{% endfor %}
</div>
<div>
<form action="/????????????NOT SURE WHERE TO DIRECT" method="post">
<div>
<label for="title">title:</label>
<input type="text" id="title" />
</div>
<div>
<label for="description">description:</label>
<input type="text" id="description" />
</div>
<div class="button">
<button type="submit">Add to db</button>
</div>
</form>
</div>
</body>
</html>
app.py
from flask import Flask, render_template, request, session, g
import sqlite3
app = Flask(__name__)
#app.route('/')
def home():
g.db = sqlite3.connect("sample.db")
cur = g.db.execute('select * from posts')
posts = [dict(title=row[0], description=row[1]) for row in cur.fetchall()]
g.db.close()
return render_template("index.html", posts=posts)
if __name__=='__main__':
app.run(debug=True)
sql.py
import sqlite3
with sqlite3.connect("sample.db") as connection:
c = connection.cursor()
c.execute("DROP TABLE posts")
c.execute("CREATE TABLE posts(title TEXT, description TEXT)")
c.execute('INSERT INTO posts VALUES("Luck", "no luck.")')
EDIT
I made some changes based on the suggestion from Paul Rooney and created file called post.html, moved the form there from index.htmland added another #route on the app.py file. I believe I'm not far off but after trying to figure this out since Tuesday hoping to get it work. Unfortunately now I receive an error 405 'The method is not allowed for the requested URL.' and I'm stuck with this after trying different options.
post.html
<!DOCTYPE html>
<html>
<head>
<title>Flask post</title>
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> -->
</head>
<body
<div>
<form action="/post" method="post">
<div>
<label for="title">title:</label>
<input type="text" id="title" />
</div>
<div>
<label for="description">description:</label>
<input type="text" id="description" />
</div>
<div class="button">
<button type="submit">Add to db</button>
</div>
</form>
</div>
</body>
</html>
(edited) app.py
from flask import Flask, render_template, request, session, g, redirect, url_for
import sqlite3
app = Flask(__name__)
#app.route('/')
def home():
g.db = sqlite3.connect("sample.db")
cur = g.db.execute('select * from posts')
posts = [dict(title=row[0], description=row[1]) for row in cur.fetchall()]
g.db.close()
return render_template("index.html", posts=posts)
#app.route('/post', methods=['POST'])
def post():
title=request.form['title']
description=request.form['description']
print title, description
return redirect(url_for('/'))
if __name__=='__main__':
app.run(debug=True)
To answer your original question you should pick an endpoint to post your data to and use that in both your flask app and the html form action.
I suggested post in comments but it could be anything. I think you've grasped what to do here but for completeness.
The HTML
<form action="/post" method="post">
The Python Code
Add a route to handle the POSTed data.
#app.route('/post', methods=['POST'])
def post():
# Do db stuff then redirect back to index page.
pass
Edited Question
You have an indentation error in your code, which is causing the 405 error. Your post function is inside your home function. Make it more like this
app.py
from flask import Flask, render_template, request, session, g, redirect, url_for
import sqlite3
app = Flask(__name__)
#app.route('/')
def home():
g.db = sqlite3.connect("sample.db")
cur = g.db.execute('select * from posts')
posts = [dict(title=row[0], description=row[1]) for row in cur.fetchall()]
g.db.close()
return render_template("index.html", posts=posts)
# One level of indentation removed from here downwards
#app.route('/post', methods=['POST'])
def post():
title=request.form['title']
description=request.form['description']
return redirect(url_for('home'))
if __name__=='__main__':
app.run(debug=True)
After that I see a 400 Bad Request error, which stems from the fact that you don't have name parameters in your html form.
The attempt to access the title and description values in the form dict in app.py will throw a KeyError exception, as those keys will not be present without the name parameter in the html.
If you add them e.g.
...
<input type="text" id="title" name='title'/>
...
<input type="text" id="description" name='description'/>
...
Then it will run all the way through your function.
The next issue will be
redirect(url_for('/'))
instead use
redirect(url_for('home'))
home being the name of the function called for path '/'.
After that you should be good to go.