Reputation: 299
When I use Flask-Security's login form, I get the following error. It's telling me that I don't have a get_user
method, (which is a part of my user_datastore
).
Traceback (most recent call last):
File "/home/ubuntu/workspace/my_app/venv/lib/python3.6/site-packages/flask/app.py", line 1994, in __call__
return self.wsgi_app(environ, start_response)
File "/home/ubuntu/workspace/my_app/venv/lib/python3.6/site-packages/flask/app.py", line 1985, in wsgi_app
response = self.handle_exception(e)
File "/home/ubuntu/workspace/my_app/venv/lib/python3.6/site-packages/flask/app.py", line 1540, in handle_exception
reraise(exc_type, exc_value, tb)
File "/home/ubuntu/workspace/my_app/venv/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
raise value
File "/home/ubuntu/workspace/my_app/venv/lib/python3.6/site-packages/flask/app.py", line 1982, in wsgi_app
response = self.full_dispatch_request()
File "/home/ubuntu/workspace/my_app/venv/lib/python3.6/site-packages/flask/app.py", line 1614, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/home/ubuntu/workspace/my_app/venv/lib/python3.6/site-packages/flask/app.py", line 1517, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/home/ubuntu/workspace/my_app/venv/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
raise value
File "/home/ubuntu/workspace/my_app/venv/lib/python3.6/site-packages/flask/app.py", line 1612, in full_dispatch_request
rv = self.dispatch_request()
File "/home/ubuntu/workspace/my_app/venv/lib/python3.6/site-packages/flask/app.py", line 1598, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/home/ubuntu/workspace/my_app/venv/lib/python3.6/site-packages/flask_security/decorators.py", line 225, in wrapper
return f(*args, **kwargs)
File "/home/ubuntu/workspace/my_app/venv/lib/python3.6/site-packages/flask_security/views.py", line 75, in login
if form.validate_on_submit():
File "/home/ubuntu/workspace/my_app/venv/lib/python3.6/site-packages/flask_wtf/form.py", line 101, in validate_on_submit
return self.is_submitted() and self.validate()
File "/home/ubuntu/workspace/my_app/venv/lib/python3.6/site-packages/flask_security/forms.py", line 230, in validate
self.user = _datastore.get_user(self.email.data)
File "/home/ubuntu/workspace/my_app/venv/lib/python3.6/site-packages/werkzeug/local.py", line 343, in __getattr__
return getattr(self._get_current_object(), name)
AttributeError: type object 'User' has no attribute 'get_user'
When I dug into Flask-Security's datasource.py
, I see the following method:
def get_user(self, id_or_email):
"""Returns a user matching the specified ID or email address."""
raise NotImplementedError
This seems like an abstract method and must be implemented within my User
model. If that's true, why do none of the Flask-Security examples implement this method? If I try to add the method to my User
model, things don't get much better. Here's my model with the experimental overridden method:
from extensions import db
from blog.models import Post
from sentence.models import Sentence
from roster.models import Roster
from datetime import datetime
import datetime
from flask_security import UserMixin, RoleMixin
# Helper table for a many-to-many relationship
roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
class User(db.Model, UserMixin):
# general variables
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(155))
last_name = db.Column(db.String(155))
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean(), default=True)
confirmed_at = db.Column(db.DateTime())
# app-specific variables
paid_until = db.Column(db.DateTime, default=datetime.datetime.utcnow)
lifetime = db.Column(db.Boolean, default=False)
# relations
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
posts = db.relationship('Post', backref='user', lazy='dynamic')
sentences = db.relationship('Sentence', backref='user', lazy='dynamic')
def __init__(self, **kwargs):
self.password = kwargs['password']
self.email = kwargs['email']
if kwargs.get('first_name', False):
self.first_name = kwargs['first_name']
self.last_name = kwargs['last_name']
# create a roster
roster = Roster("default", self.email)
db.session.add(roster)
db.session.commit()
def __repr__(self):
return '<User %r>' % self.username
# __str__ is required by Flask-Admin (not using?), so we can have human-readable values for the Role when editing a User.
# If we were using Python 2.7, this would be __unicode__ instead.
def __str__(self):
return self.name
# __hash__ is required to avoid the exception TypeError: unhashable type: 'Role' when saving a User
def __hash__(self):
return hash(self.name)
def get_user(self, *args):
# I had to use *args or I'd always get missing 1 required positional argument
for arg in args:
user = User.query.filter_by(email=arg).first()
if user:
return user
'''
def find_user(self, *args, **kwargs):
"""Returns a user matching the provided parameters."""
raise NotImplementedError
def find_role(self, *args, **kwargs):
"""Returns a role matching the provided name."""
raise NotImplementedError
'''
The above approach results either in missing 1 required positional argument
if I don't use *args
. As the model is written above, the app doesn't crash but says "Specified user does not exist", which is not the case as I can see the user in the database CLI. So, do I need to implement the get_user
method?
Upvotes: 0
Views: 1770
Reputation: 299
The problem was in Flask app's __init__
. As suspected, Flask-Security's attempt to call get_user
using the User model indicated the wrong object was passed when calling security.init_app(app, user_datastore)
. The same parameters from the instantiation of the user_datastore
were being reused for security.init_app()
which is why the User model was being mistakenly accessed.
Upvotes: 1