I have a base.html that has items, and button for user to click.
<p class="card-text">{{data[0].name}}</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<form action="/update/<%= data[0]._id %>" method="PATCH">
<p style="margin-right:5px;">Available: {{data[0].inventory}}</p>
<button type="button" class="btn btn-sm btn-outline-secondary">Add to cart</button>
</form>
</div>
And I have hello.py python file that runs the server.
from flask import Flask, render_template, redirect, request, url_for
from pymongo import MongoClient
from bson.objectid import ObjectId
app = Flask(__name__)
#app.route('/',methods=['GET'])
def mongoTest():
client = MongoClient('mongodb://localhost:27017/')
db = client.ecommerce
collection = db.items
results = collection.find()
# client.close()
return render_template('base.html', data=results)
#app.route("/update/:id", methods=["PATCH"])
def update_inventory(id):
client = MongoClient('mongodb://localhost:27017/')
db = client.ecommerce
id=request.values.get("_id")
db.items.update_one({ '_id': id}, {'$inc': {'inventory': -1}}, upsert=False)
results2 = db.items.find()
return render_template('base.html', data=results2)
if __name__ == '__main__':
app.run(debug=True)
However, it does not update the inventory values.
How do I solve this problem?
Is the id a string? if so it needs to be transformed into an ObjectId. (in most cases, you let mongo handle ids, so ids are ObjectId, not strings).
from bson.objectid import ObjectId
#app.route("/update/:id", methods=["PATCH"])
def update_inventory(id):
client = MongoClient('mongodb://localhost:27017/')
db = client.ecommerce
id = ObjectId(request.values.get("_id"))
db.items.update_one({ '_id': id}, {'$inc': {'inventory': -1}}, upsert=False)
results2 = db.items.find()
return render_template('base.html', data=results2)
Otherwise, you have to check:
if the id exists, if not return an error?
"items" collection doest exist? (not Item nor item?)
Related
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
Here is my database code in flask and mongodb snadb.py
from flask import Flask, render_template, request, redirect, url_for,request
from bson import ObjectId
from pymongo import MongoClient
import os
app = Flask(__name__)
client = MongoClient("mongodb://127.0.0.1:27017") #host uri
db = client.Student #Select the database
todos = db.Student_list #Select the collection name
def redirect_url():
return url_for('action')
#app.route("/list")
def lists ():
return render_template('snap17.html')
#app.route("/snap17", methods=['GET','POST'])
def action ():
#Adding a Task
name=request.values.get("firstname")
todos.insert({ "name":name})
return redirect("/list")
if __name__ == "__main__":
app.run(debug=True)
`
In your view function for /list. Read your datas from databse and accordingly render it in your template like below:
#app.route("/list")
def lists():
todolist = db.Student_list.find()
return render_template('snap17.html', todolist=todolist)
And In your template snap17.html render it as
<html>
{% for elem in todolist %}
<li> {{elem['name']}}</li>
{% endfor %}
</html>
And to write into your database make a GET request like this in your existing apllication:
http://localhost:5000/snap17?firstname="JaiSambhu"
I am having issues linking my flask program to Postgre SQL
import os
from flask import Flask, session
from flask_session import Session
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from flask import Flask, render_template, request
from models import db
app = Flask(__name__)
POSTGRES = {
'user': 'user',
'pw': 'password',
'db': 'database',
'host': '127.0.0.1',
'port': '5432',
}
# Check for environment variable
if not os.getenv("DATABASE_URL"):
raise RuntimeError("DATABASE_URL is not set")
# Configure session to use filesystem
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
# Set up database
engine = create_engine(os.getenv("DATABASE_URL"))
db = scoped_session(sessionmaker(bind=engine))
#app.route("/")
def index():
return render_template("index.html")
#app.route("/register",methods=['GET','POST'])
def register():
return render_template("register.html")
if request.method=='POST':
name=request.form['username']
password=request.form['password']
connection = mysql.get_db()
cursor = connection.cursor()
query="INSERT INTO 'userdetails'(username,password) VALUES(%s,%s)"
cursor.execute(query,(username,password))
connection.commit()
#app.route("/login")
def login():
return render_template("login.html")
Register.html
<html>
<head>
<title>Project1</title>
</head>
<form method="POST">
<input type="username" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<input type="submit" value="Register">
</form>
</html>
I have already specified the DATABASE_URl when setting up with the credentials given by Heroku. I cannot manage to store the username and password into the database. The register.html page contains a form with username input field and password input field. Help will be appreciated.
P.S I know I left postgre details, they have nothing important. Just help me out.
Thanks
Within the form element you should define the action attribute
e.g. <form method="POST" action="/register">
action attribute specifies where the data gets sent (source MDN Web Docs)
In order to simplify using SQLAlchemy with Flask you can use the Flask-SQLAlchemy extension.
You can link PostgreSQL and Python by using a PostgreSQL adapter. A popular choice is psycopg2.
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=""
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.