Cannot add comment to SQL db in python Flask app - python

I am new to programming and have setup a small website with a comments section on pythonanywhere.com, relaying heavily on their tutorial. But when I post a comment in the form, the comment is not added to the database and for some reason the program redirects me to the index page (the intention is to redirect to stay on the same page)
Any suggestions as to what I might be doing wrong would be greatly appreciated!
The pyhthon code:
import random
from flask import Flask, request, session, redirect, url_for, render_template, flash
from flask.ext.sqlalchemy import SQLAlchemy
from werkzeug.routing import RequestRedirect
app = Flask(__name__)
app.config["DEBUG"] = True
SQLALCHEMY_DATABASE_URI = "mysql+mysqlconnector://{username}:{password}#{hostname}/{databasename}".format(
username="username",
password="password",
hostname="hostname",
databasename="majaokholm$majaokholm",
)
app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI
app.config["SQLALCHEMY_POOL_RECYCLE"] = 299
db = SQLAlchemy(app)
class Comment(db.Model):
__tablename__ = "comments"
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(4096))
#app.route("/")
def index():
return render_template("index_page.html")
#app.route('/post', methods=["GET", "POST"])
def post():
if request.method == "GET":
return render_template("post_page.html", comments=Comment.query.all())
comment = Comment(content=request.form["contents"])
db.session.add(comment)
db.session.commit()
return redirect(url_for('post'))
and the form from the HTML template:
<form action="." method="POST">
<textarea class="form-control" name="contents" placeholder="Enter a
comment"></textarea>
<input type="submit" value="Post comment">
</form>
Thanks a lot in advance!

Currently, the action="." in the form actually points to the root of the current directory, which for /post happens to be just / and thus points to the index.
It's always better to use action="{{ url_for('your_target_view') }}" instead.

get rid of action=".", you can use action=""

Related

How to solve 405 Method Not Allowed (flask) [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I'm keep getting the 405 error, I've changed my code following the other related posts but still not working.
routes.py
from flask import render_template, redirect, flash, url_for, abort, request
from flask_login import current_user, login_user, logout_user, login_required
from flask import Flask
import views.model as mod
app = Flask(__name__)
#app.route('/')
def index():
mod.check_database_init()
return render_template("index.html")
#app.route('/login',methods = ['GET','POST'])
def login():
if(request.method == 'POST'):
print("post")
name = request.form['username']
password = request.form['password']
print(name, password)
return redirect(url_for("index"))
else:
return render_template("login.html")
#app.route('/register', methods = ['GET','POST'])
def register():
return render_template("register.html")
login.html
{% include "header.html" %}
<title>Login</title>
<form action="/login" method="POST">
<label>Username</label>
<input type = "text" placeholder = "username" name="username" required>
<label>Password</label>
<input type = "password" placeholder="password" name = "password" required>
<input type="submit">
<p>
Don't have an acoount? Click me to create a new account
</p>
</form>
Back to homepage
I tried this method, https://stackoverflow.com/a/62464826/13239458, it works but I don't know what went wrong in the code above.
server.py
from flask import Flask
from flask_login import LoginManager
import views.routes as rou
import views.database as db
app = Flask(__name__)
app.add_url_rule('/', view_func=rou.index)
app.add_url_rule('/login', view_func=rou.login)
app.add_url_rule('/register', view_func=rou.register)
if __name__ == "__main__":
app.run(host='127.0.0.1', debug = True)
UPDATE
I just change method type into GET (in login.html) and there will be no error and I can see the input from the console, so I think the login method cannot accept the POST method but I have no idea why is that.
Just find out the issue, I created 2 app instances, I move the instantiation into another file then import it, everything works!
You have not included:
if __name__ == '__main__':
app.run(debug=True)
in the end of routes.py

Flask-Login #login-required decorator not redirecting to login page after session has expired

UPDATE
It appears that the Flask redirect (response code 302) below is being passed as the response to the _dash-update-component request:
b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n<title>Redirecting...</title>\n<h1>Redirecting...</h1>\n<p>You should be redirected automatically to target URL: /login. If not click the link.'
This explains the SyntaxError thrown by dash_renderer below, so this led me to add the following in server.py:
#server.after_request
def check_response(response):
redirecting = 'Redirecting...' in response.get_data().decode('utf-8')
dash_response = request.path=='/_dash-update-component'
return make_response('', 204) if redirecting and dash_response else response
Now I can emulate a Dash-like PreventUpdate by returning a "204 No-Content" response to the dash component, but then I am not receiving the additional request for the redirect back to the login page. Commenting out the after_request function and then tracking the requests seen by before_request, it's actually shown that the login() route is invoked and render_template('login.html') is returned, but it's simply not rendered in the browser....
ORIGINAL POST BELOW
I've spent the better part of the last few days attempting to overhaul our login procedures to add some quality of life update and modifications. For the purposes of this question, I'm interested in logging out our users after a certain period of inactivity in the main Dash application.
My approach was to register routes for our Login page, and then point a Flask route for /dashapp to the response returned by app.index() where app points to the Dash application. Once they are logged into the Dash application, I have a before_request decorator that will update the session modified attribute and the session expiration (5 seconds for the purposes of testing). I've also applied the #login_required decorator to this invoked function, so that login_manager.unauthorized_handler is invoked if the user is no longer authenticated when triggering the before_request decorator. I think my logic is sound here, but I am still having issues which I will describe below.
I am able to login my users and redirect them to the main Dash application at /dashapp, and I can use the application without issues. Now when I wait the 5 seconds to allow for the session to expire, clicking on a component in my Dash application that triggers a dash callback produces the following error in the console:
dash_renderer.v1_7_0m1602118443.min.js:20 SyntaxError: Unexpected token < in JSON at position 0
I'm aware that some function is expecting a JSON response, and has apparently received an HTML response instead, but I can't pin down what that is. It's also preventing my redirection back to the login page that I expected to be invoked when the user was no longer authenticated and triggered the before_request decorator.
My code structure is below (not that config.py is simply my SQL connection):
application.py
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
from server import app, server as application, User, login_manager
from flask_login import logout_user, current_user, login_user, login_required
from flask import session, redirect, render_template, url_for, request
from views import main
app.layout = html.Div([
dcc.Location(id='url', refresh=False),
html.Div(id='page-content')
])
#application.route('/login')
def login():
return render_template('login.html')
#application.route('/login', methods=['POST'])
def login_post():
if current_user.is_authenticated:
return redirect('/dashapp')
user = User.query.filter_by(username=request.form['username']).first()
#Check if user exists
if user:
#Check if password is correct
if user.password==request.form['password']:
login_user(user, remember=False)
return redirect('/dashapp')
#login_manager.unauthorized_handler
def unauthorized():
if request.path!='/login':
return redirect('/login')
#application.route('/logout')
#login_required
def logout():
logout_user()
return redirect('/login')
#application.route('/dashapp')
#login_required
def main_page():
return app.index()
#app.callback(
Output('page-content', 'children'),
[Input('url', 'pathname')])
def display_page(pathname):
if current_user.is_authenticated:
content = main.get_layout()
else:
content = dcc.Location(pathname='/login', id='redirect-id')
return content
if __name__ == '__main__':
app.run_server()
views/login.html
<html>
<head>
<title>Flask Intro - login page</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="static/bootstrap.min.css" rel="stylesheet" media="screen">
</head>
<body>
<div class="container">
<h1>Please login</h1>
<br>
<form action="" method="post">
<input type="text" placeholder="Username" name="username" value="{{
request.form.username }}">
<input type="password" placeholder="Password" name="password" value="{{
request.form.password }}">
<input class="btn btn-default" type="submit" value="Login">
</form>
{% if error %}
<p class="error"><strong>Error:</strong> {{ error }}
{% endif %}
</div>
</body>
</html>
server.py
import dash, os, datetime
from flask_login import LoginManager, UserMixin, current_user, login_required
from config import connection_string
import dash_bootstrap_components as dbc
from credentials import db, User as base
from flask import session, g, redirect, url_for, request, flash, render_template
import flask
external_stylesheets = [dbc.themes.BOOTSTRAP]
app_flask = flask.Flask(__name__)
app = dash.Dash(
__name__,
server=app_flask,
external_stylesheets=external_stylesheets,
update_title=None,
url_base_pathname='/'
)
app.title = 'Login Testing Interface'
server = app_flask
app.config.suppress_callback_exceptions = True
server.config.update(
SECRET_KEY=os.urandom(12),
SQLALCHEMY_DATABASE_URI=connection_string,
SQLALCHEMY_TRACK_MODIFICATIONS=False
)
db.init_app(server)
#Setup the LoginManager for the server
login_manager = LoginManager()
login_manager.init_app(server)
login_manager.login_view = 'login'
#Create User class with UserMixin
class User(UserMixin, base):
def get_id(self):
return self.user_id
#Reload the user object
#login_manager.user_loader
def load_user(user_id):
return User.query.get(user_id)
#server.before_request
#login_required
def check_authentication():
session.permanent = True
server.permanent_session_lifetime = datetime.timedelta(seconds=5)
session.modified = True
g.user = current_user
main.py
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from flask_login import current_user
from server import app, server
def get_layout():
return html.Div([
dcc.Location(id='url-main', refresh=False),
dbc.Button('Click me', id='test-click', n_clicks_timestamp=0),
html.Div(id='testing')
])
#app.callback(
Output('testing', 'children'),
[Input('test-click', 'n_clicks_timestamp')])
def update_test_div(clicks):
return f'Last clicked: {clicks}'
credentials.py
from flask_sqlalchemy import SQLAlchemy
from config import engine
db = SQLAlchemy()
db.Model.metadata.reflect(engine)
class User(db.Model):
__table__ = db.Model.metadata.tables['my_sql_table_with_user_details']
Thank you in advance for any guidance here!
I suggest writing your login and login post route as a single fuunction
#app.route('/login', methods=['POST','GET'])
def login():
if current_user.is_authenticated :
return redirect('/')
if request.method == 'POST':
user_name = request.form.get('username')
password_entered =request.form.get('password')
present_user=User.query.filter_by(username=user_name).first()
if present_user.password == password_entered:
login_user(present_user)
next_page= request.args.get('next')
print(next_page)
return redirect(next_page) if next_page else redirect('/')
else:
flash('Incorrect Password',category='danger')
return render_template('user_login.html')
else:
return render_template('user_login.html')
If you redirected from the login_required function to the Login page,
you might notice that the link /url on top says
/login?next=%2FpathofFunction
When we write
next_page= request.args.get('next')
We get the remaining URL after ?next and then redirect the user to where it came from

Why does my Flask-SQLAlchemy update query not change my table content

I'm making a flask website, in which I have a SQLite database with a table called mainscreen. On my home screen I have some text which is got from mainscreen - content column. I'm trying to retrieve the data from my textarea in my form which is supposed to update my mainscreen table. Although I'm correctly being redirected to my home.html, I can't see my changes being made, i.e my table is not gettng updated.
MainScreen table structure
|- mainscreen
|- id = integer - primary key
|- content = varchar(1000)
Required Code
flaskapp.py
from flask import Flask, render_template, [...]
from flask_login import [...]
from myproject.__init__ import User, MainScreen
from werkzeug.security import generate_password_hash,check_password_hash
from flask_sqlalchemy import SQLAlchemy
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = [...]
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.abspath(os.path.join(os.path.dirname( __file__ ), 'data.sqlite'))
db = SQLAlchemy(app)
#app.route('/updated', methods=['POST', 'GET'])
def change_home():
if request.method == 'GET':
new_content = request.form.get('home', False)
mainContent = MainScreen.query.get(1)
mainContent.content = new_content
db.session.commit()
return redirect(url_for('home'))
else:
return redirect(url_for('login'))
#app.route('/loggedin', methods=['POST', 'GET'])
def login():
[... ...]
datas = {}
datas['content'] = onlycontent
return render_template('loggedin.html', data=datas)
myproject / __init __.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import [...]
from flask_login import UserMixin
import os
app = Flask(__name__)
db = SQLAlchemy(app)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.abspath(os.path.join(os.path.dirname( __file__ ), os.pardir, 'data.sqlite'))
class MainScreen(db.Model, UserMixin):
__tablename__ = 'mainscreen'
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(1000), unique=False, nullable=False)
def __init__(self, id, content):
self.id = id
self.content = content
def __repr__(self):
return f'<MainScreen {self.id} {self.content}>'
loggedin.html
<form action="{{url_for('change_home')}}" method="get">
<table>
<tr>
<td width="10%">
<h4>Home Page</h4>
</td>
<td class="padding-right:5%">
<textarea name="home" rows="7">{{data['content']}}</textarea>
</td>
<td>
<input type="submit" class="btn btn-primary"></input>
</td>
</tr>
</table>
</form>
home.html
#app.route('/home')
#app.route('/')
def home():
datas = {}
MainContent = MainScreen.query.get(1)
content = MainContent.content
datas['aboutme'] = content
return render_template('home.html', data=datas)
pip freeze
Flask==1.1.2
Flask-Login==0.5.0
Flask-Migrate==2.5.3
Flask-SQLAlchemy==2.4.4
SQLAlchemy==1.3.19
SQLAlchemy-Utils==0.33.2
Update 1
After setting some breakpoints, it appears that my new_content variable is not getting the data. It returns None. Since my content column is set to NOT NULL, that's probably the reason it's not getting updated. I need to figure out why my new_content variable is not retrieving the data
Update 2
It seems my variable mainContent.content is now getting updated - I had to retrieve the data by putting home in double quotes. Now my db.session.commit() doesn't seem to be working. From some online research I found that I might have made some errors in the way I'm initializing flask-sqlalchemy, so I've added some more code related to my initialisation. Thank you, your help is appreciated
It might be because you're sending the data over GET -- try request.args.get("home") instead and see if that works.
You're populating your text area with data['content']:
<textarea name="home" rows="7">{{data['content']}}</textarea>
This should be empty because you don't pass any value in data['content'] to render_template:
#app.route('/home')
#app.route('/')
def home():
datas = {} # empty dict
MainContent = MainScreen.query.get(1)
content = MainContent.content
datas['aboutme'] = content # set datas['aboutme'] but not datas['content']
return render_template('home.html', data=datas) # pass {'aboutme': content} to Jinja
You should either set datas['content'] = content in home() or access data['aboutme'] in loggedin.html.
Possible issue is due to that you are not passing mainContent to store it.
#app.route('/updated', methods=['POST', 'GET'])
def change_home():
if request.method == 'GET':
new_content = request.form.get('home', False)
mainContent = MainScreen.query.get(1)
mainContent.content = new_content
db.session.add(mainContent) # add this line and this should work
db.session.commit()
return redirect(url_for('home'))
else:
return redirect(url_for('login'))
Also your are trying to access form data in the GET method which I think doesn't contain form data that is in the front-end.
if request.method == 'GET':
new_content = request.form.get('home', False)
If you need to set some info, you either need to use POST to send the form data or you need to store in the session['<key>'] your expected value.
I hope this will help on finding the issue.

Bad request error when chosing file on server with form

from flask.ext.wtf import Form
from flask import send_from_directory
from wtforms import StringField, BooleanField
from wtforms import SelectField
import os
from os import listdir
from os.path import isfile, join
crewPath = ("/myproject/app/static/Crews")
filenamesCrew = [f for f in listdir(crewPath) if isfile(join(crewPath,f)) ]
class userInput(Form):
json_fileCrew = SelectField(u"json_fileCrew", choices=[(f, f) for f in filenamesCrew])
def get_data(self):
json = send_from_directory (crewPath, self.json_fileCrew.data)
return json
#app.route('/CastCrew', methods=['GET', 'POST'])
def castCrew():
form = userInput(request.form["crewYear"])
return render_template('CastCrew.html', title = 'Cast Crew View', form = form)
#app.route("/data", methods=['GET', 'POST']) #the javascript will call this
def data():
form = userInput(request.form["crewYear"])
return form.get_data()
<form class = "form" action="/data" method="post" name="crewYear">
{{ form.hidden_tag() }} <!--CSFR config -->
<p>Please choose a year:<br>
{{ form.json_fileCrew }}<br></p>
<p><input type="submit" value="Submit"></p>
</form>
I am getting a "Bad Request" error when I submit the form. How do I fix this?
The layout of project files:
---app
---views.py
---forms.py
---static
---Crews (about 100 .json files in this folder)
---1981.json
---css
---js
---templates
---base.html
---crew.html
I modified code according answer below but I still get a 404 Not Found Error when I click the button.
The immediate problem is that you're passing the value of request.form["crewYear"] to your form, rather than the entire request.form.
There are a lot of minor problems with your code. You don't need to use send_from_directory, since there's a more specific function to send from the static directory. You should populate the select field in the form init, otherwise it will not reflect any files added after the app starts. You should use app.static_folder rather than hard coding the path. You have two routes that do the same thing.
import os
from flask import current_app
from flask_wtf import Form
from wtforms.field import SelectField
class CrewForm(Form):
filename = SelectField()
def __init__(self, *args, **kwargs):
root = os.path.join(current_app.static_folder, 'Crews')
choices = [(f, f) for f in os.listdir(root) if os.path.isfile(os.path.join(root, f))]
self.filename.kwargs['choices'] = choices
super(CrewForm, self).__init__(*args, **kwargs)
#app.route('/crew', methods=['GET', 'POST'])
def crew():
form = CrewForm()
if form.validate_on_submit():
return current_app.send_static_file('Crews/{}'.format(form.filename.data))
return render_template('crew.html', form=form)
<form method="post">
{{ form.hidden_tag() }}
{{ form.filename }}
<input type="submit" value="Get Data"/>
</form>
Consider reading the Flask tutorial and Flask-WTF docs, as they clearly explain how to use forms. Reading PEP 8 would also be beneficial, as your code style is very inconsistent.

Flask 405 Error

I keep getting this error when trying to insert some simple text into a db.
Method Not Allowed
The method is not allowed for the requested URL."
I'm moving from PHP to python so bear with me here.
The code is:
from flask import Flask, request, session, g, redirect, url_for, \
abort, render_template, flash
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:password#localhost/pythontest'
db = SQLAlchemy(app)
app = Flask(__name__)
#app.route('/justadded/')
def justadded():
cur = g.db.execute('select TerminalError, TerminalSolution from Submissions order by id desc')
entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()]
return render_template('view_all.html', entries=entries)
#app.route('/new', methods= "POST")
def newsolution():
if not request.method == 'POST':
abort(401)
g.db.execute('INSERT INTO Submissions (TerminalError, TerminalSolution, VALUES (?, ?)'
[request.form['TerminalError'], request.form['TerminalSolution']])
g.db.commit()
flash('Succesful')
return redirect(url_for('justadded'))
#app.route('/')
def index():
return render_template('index.html')
#app.route('/viewall/')
def viewall():
return render_template('view_all.html')
if __name__ == '__main__':
app.run()
And the html code for the form is:
<form action="/new" method="POST">
<input name="TerminalError" id="searchbar" type="text" placeholder="Paste Terminal error here...">
<input name="TerminalSolution" id="searchbar" type="text" placeholder="Paste Terminal solution here...">
<button type="submit" id="search" class="btn btn-primary">Contribute</button>
</form>
The error has nothing to do with inserting data into the database, it's the methods argument of your /new route.
Instead of this:
#app.route('/new', methods= "POST")
do this:
#app.route('/new', methods= ["POST"])
The list of valid methods needs to be given as an array.

Categories