2

when attempting to assign a role to a user, what is the trick to make it work with the username instead of the email?

The following works:

user_datastore.add_role_to_user('nunya@beezwax.com', 'site-admin')

this does not work:

user_datastore.add_role_to_user('admin', 'site-admin')

the error it gives is:

AttributeError: 'NoneType' object has no attribute 'roles'

my model looks like so:

roles_users = db.Table('users_to_roles',
    db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
    db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))


class User(db.Model, UserMixin):
  id = db.Column(db.Integer, primary_key=True)
  username = db.Column(db.String(16), unique=True)
  email = db.Column(db.String(256), unique=True)
  password = db.Column(db.String(255))
  active = db.Column(db.Boolean())
  confirmed_at = db.Column(db.DateTime())
  roles = db.relationship('Role', secondary=roles_users,
                        backref=db.backref('users', lazy='dynamic'))
Simply Seth
  • 3,246
  • 17
  • 51
  • 77

3 Answers3

0

If you look at the source code, https://github.com/mattupstate/flask-security/blob/develop/flask_security/datastore.py add_role_to_user calls the method

def add_role_to_user(self, user, role):
        """Adds a role to a user.
        :param user: The user to manipulate
        :param role: The role to add to the user
        """
        user, role = self._prepare_role_modify_args(user, role)
        if role not in user.roles:
            user.roles.append(role)
            self.put(user)
            return True
        return False

which in turn calls

def _prepare_role_modify_args(self, user, role):
        if isinstance(user, string_types):
            user = self.find_user(email=user)
        if isinstance(role, string_types):
            role = self.find_role(role)
        return user, role

This method retrieves the user object using email. If you really need to retrieve by username(and make sure its unique)instead of email, you can fork flask-security and change the line (line 121) from

user = self.find_user(email=user)

to

user = self.find_user(username=user)

then you reinstall flask-security from your repo (uninstall flask-security then install)

pip install git+https://github.com/your_repo/flask-security

I have personally done lots of customizations and works fine. Note that you will then need to use username anytime you need to modify a role

Moses N. Njenga
  • 762
  • 1
  • 9
  • 19
  • No need to modify the source code. Just use my solution below: Get user object, then add role to the user object. As you see in `_prepare_role_modify_args` above, if you pass it a user object, it doesn't need to fetch the user. Apparently, OP couldn't get it to work, so he gave me a -1. – GAEfan Apr 11 '18 at 16:03
0

use 3 tables from flask-security website, the role table isn't included in your example. I am not including 'every nuance' here, just the key parts of the code. I renamed the User table to Users because some databases have naming conflicts. since all you are providing is the query you could obviously query on username or email (I'm using email).

in your ____init____.py (or app.py)

from flask_security import Security, SQLAlchemyUserDatastore


def create_app():
    user_datastore = SQLAlchemyUserDatastore(db, Users, Role)
    security = Security(app, user_datastore,
                        confirm_register_form=ExtendedConfirmRegisterForm)

    return app

models.py

roles_users = db.Table('roles_users',
        db.Column('user_id', db.Integer(), db.ForeignKey('users.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 Users(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(50), unique=True)
    name = db.Column(db.String(30))
    password = db.Column(db.String(255))
    about = db.Column(db.String(255))
    last_login_at = db.Column(db.DateTime())
    current_login_at = db.Column(db.DateTime())
    last_login_ip = db.Column(db.String(100))
    current_login_ip = db.Column(db.String(100))
    login_count = db.Column(db.Integer)
    active = db.Column(db.Boolean())
    confirmed_at = db.Column(db.DateTime())

    roles = db.relationship('Role', secondary=roles_users,
                            backref=db.backref('users', lazy='dynamic'))

views.py

from app import db, create_app

app = create_app()
app.app_context().push()
security = app.extensions.get('security')

in your views.py query the user and the role

u = Users.query.filter_by(email='test@example.com').first()
r = Role.query.filter_by(name='test').first()
security.datastore.add_role_to_user(u, r)
db.session.commit()
Jeff Bluemel
  • 476
  • 9
  • 22
-1

You need to either pass add_role_to_user the email as a string, or the user entity itself. So, you can fetch the user by the username, then send that user entity to add_role_to_user:

user_datastore.add_role_to_user(my_user, 'site-admin')
GAEfan
  • 11,244
  • 2
  • 17
  • 33
  • tried: `auth_user = user_datastore.find_user(username='admin')` then `user_datastore.add_role_to_user(auth_user,'site-admin')` ... didn't work – Simply Seth Apr 10 '18 at 17:36
  • Log `auth_user` to make sure you found an actual user – GAEfan Apr 10 '18 at 18:42
  • Also, you may need to add username as a valid login: `app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ('username','email')` – GAEfan Apr 10 '18 at 19:01