dadiletta
dadiletta

Reputation: 299

Trouble logging in with Flask-Security: 'User' has no attribute 'get_user'

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

Answers (1)

dadiletta
dadiletta

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

Related Questions