Issue
I've been loosely following the tutorial @ https://www.digitalocean.com/community/tutorials/how-to-add-authentication-to-your-app-with-flask-login
But I am using MongoDB using pymongo and have had to modify my code along the way. I've been good up until Step 10.
The part I am unable to get functioning is where the tutorial (Step 10) is able to access the name, ID, email, etc from the user when logged in. I have tried several methods of trying to access the information from the mongoDB but fall flat any time.
What I would like to do is be able to access, when a user is authenticated, any of the records I so choose.
I am able to get /profile to render - which I suspect means my authentication is working as intended.
from flask import Flask, render_template, redirect, url_for, Blueprint, request, flash
from flask_login import LoginManager
import os
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, EmailField
from wtforms.validators import DataRequired, Email
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin, LoginManager, login_user, login_required, current_user
import pymongo
myclient = pymongo.MongoClient("properstuff")
mydb = myclient["database"]
mycol = mydb["databasedb"]
app = Flask(__name__)
app.config['SECRET_KEY'] = '12345'
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id):
user_json = mycol.find_one({'_id': user_id})
return User(user_json)
class LoginForm(FlaskForm):
username = StringField('Username',validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Login')
class SignUp(FlaskForm):
username = StringField('Username',validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
email = EmailField('Email', validators=[DataRequired(), Email()])
submit = SubmitField('Register')
class User(UserMixin):
def __init__(self, user_json):
self.user_json = user_json
# Overriding get_id is required if you don't have the id property
# Check the source code for UserMixin for details
def get_id(self):
object_id = self.user_json.get('_id')
return str(object_id)
@app.route("/")
def homepage():
form = LoginForm()
return render_template('index.html', form=form)
@app.route("/login", methods=['POST'])
def login_post():
username = request.form.get('username')
password = request.form.get('password')
user = mycol.find_one({"username": username})
if not user or not check_password_hash(user['password'], password):
flash('Please check your login details and try again.')
return redirect(url_for('homepage'))
loginuser = User(user)
login_user(loginuser, remember=True)
return redirect(url_for('profile'))
@app.route("/register")
def register():
form = SignUp()
return render_template('register.html', form=form)
@app.route("/register", methods=["POST"])
def register_post():
email = request.form.get('email')
username = request.form.get('username')
password = request.form.get('password')
user = mycol.find_one({"email": email})
if user:
flash('Email address already exists. Please login below.')
return redirect(url_for('homepage'))
mycol.insert_one({"username": username, "email": email, "password": generate_password_hash(password, method='sha256')})
return redirect(url_for('homepage'))
@app.route("/profile")
@login_required
def profile():
return "Logged in successfully"
I tried adding name = user_json['username'] to the init function in the class user and calling it by current_user.name however, it gave me the following error:
Traceback (most recent call last):
File "C:\Users\David PC\AppData\Roaming\Python\Python37\site-packages\flask\app.py", line 2077, in wsgi_app
response = self.full_dispatch_request()
File "C:\Users\David PC\AppData\Roaming\Python\Python37\site-packages\flask\app.py", line 1525, in full_dispatch_request
rv = self.handle_user_exception(e)
File "C:\Users\David PC\AppData\Roaming\Python\Python37\site-packages\flask\app.py", line 1523, in full_dispatch_request
rv = self.dispatch_request()
File "C:\Users\David PC\AppData\Roaming\Python\Python37\site-packages\flask\app.py", line 1509, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
File "C:\Users\David PC\Desktop\VS Code Python\Flask Site\site.py", line 57, in homepage
return render_template('index.html', form=form)
File "C:\Users\David PC\AppData\Roaming\Python\Python37\site-packages\flask\templating.py", line 147, in render_template
ctx.app.update_template_context(context)
File "C:\Users\David PC\AppData\Roaming\Python\Python37\site-packages\flask\app.py", line 756, in update_template_context
context.update(func())
File "C:\Users\David PC\AppData\Roaming\Python\Python37\site-packages\flask_login\utils.py", line 417, in _user_context_processor
return dict(current_user=_get_user())
File "C:\Users\David PC\AppData\Roaming\Python\Python37\site-packages\flask_login\utils.py", line 384, in _get_user
current_app.login_manager._load_user()
File "C:\Users\David PC\AppData\Roaming\Python\Python37\site-packages\flask_login\login_manager.py", line 367, in _load_user
user = self._load_user_from_remember_cookie(cookie)
File "C:\Users\David PC\AppData\Roaming\Python\Python37\site-packages\flask_login\login_manager.py", line 411, in _load_user_from_remember_cookie
user = self._user_callback(user_id)
File "C:\Users\David PC\Desktop\VS Code Python\Flask Site\site.py", line 41, in load_user
return User(user_json)
File "C:\Users\David PC\Desktop\VS Code Python\Flask Site\site.py", line 30, in __init__
name = user_json['username']
TypeError: 'NoneType' object is not subscriptable
Solution
So it looks like my issue was related to my user_loader
.
My database query was always returning None
as no record was found. This is because MongoDB defines the "_id"
as an objectId
, and not a str
, I assume.
I imported ObjectId from bson.objectid
and modified my user_loader
to:
@login_manager.user_loader
def load_user(user_id):
u = mycol.find_one({"_id": ObjectId(user_id)})
if not u:
return None
return User(u)
I was then able to call individual records using current_user.user_json['key']
Answered By - David
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.