1

Im trying to se flask-admin and flask-security-too

and use the Views from flask-admin to administrate the users and its roles

the problem is that, the view from flask-admin is trying to update or create users with a standar db.commit() when the flask-security-too uses the method create_user() or in security.datastore()

so, i havent find a way to override this little error that avoids me to create or edit the erros

by the moment

i have tried

def form_action(self, form, model, ids):

and gives a error that form, model and ids arent posible to use

then i tried

def update_model(self, form, model, ids):
        try:
            user = security.datastore.find_user(id=ids)
            if user != None:
                username = model.username
                name = model.name
                email = model.email
                password = model.password
                user.username = username
                user.email = email
                user.name = name
                user.password = password
                security.datastore.update_user(user)
                security.datastore.commit()
                flash(f'usuario {user.name} fue editado con exito', 'success')
            else:
                username = model.username
                name = model.name
                email = model.email
                password = model.password
                user.username = username
                user.email = email
                user.name = name
                user.password = password
                security.datastore(user)
                security.datastore.commit()
                flash(f'usuario {user.name} fue editado con exito', 'success')
        except Exception as e:
            print(e)
            flash('ups algo salio mal', 'danger')

and it still tries to update the database in the sqlalchemy ay without using the security methods. so it gives a problem in the fs_uniquifier (1048, "Column 'fs_uniquifier' cannot be null")

so any insigth is well apreciated

models.py

    class RolesUsers(db.Model):
    __tablename__ = 'roles_users'
    __table_args__ = {'extend_existing': True} 
    id = db.Column(db.Integer(), primary_key=True)
    user_id = db.Column('user_id', db.Integer(), db.ForeignKey('user.id'))
    role_id = db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))

class Role(db.Model, RoleMixin):
    __tablename__ = 'role'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True)
    description = db.Column(db.String(255))

    # __str__ is required by Flask-Admin, so we can have human-readable 
    # values for the Role when editing a User.
    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)

class User(db.Model, UserMixin):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True)
    username = db.Column(db.String(255), unique=True, nullable=True)
    name = db.Column(db.String(255), nullable=True)
    password = db.Column(db.String(255), nullable=False)
    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())
    fs_uniquifier = db.Column(db.String(255), unique=True, nullable=False)
    confirmed_at = db.Column(db.DateTime())
    roles = db.relationship('Role', secondary='roles_users',
                         backref=db.backref('users', lazy='dynamic'))
    
    def __repr__(self):
        return '<Usuario {}>'.format(self.username)

the views.py

class UserModelView(sqla.ModelView):
    def is_accessible(self):
            return (current_user.is_active and
                    current_user.is_authenticated and
                    current_user.has_role('superuser')
            )
    column_exclude_list = ('password', 'fs_uniquifier', 'email')
    column_default_sort = 'name'
    can_delete = False

    #
    # exclude fileds that im not interested in render
    #
    form_excluded_columns = ('roles', 'last_login_at', 'current_login_at', 'last_login_ip', 'current_login_ip', 'login_count', 'fs_uniquifier', 'confirmed_at', 'password')

    #override the basic form to show fields that i want

    def scaffold_form(self):

        # Start with the standard form as provided by Flask-Admin. We've already told Flask-Admin to exclude the
        # password field from this form.
        form_class = super(UserModelView, self).scaffold_form()

        # Add a password field, naming it "password2" and labeling it "New Password".
        form_class.password2 = PasswordField('Nuevo Password',  [validators.InputRequired(), validators.EqualTo('password3', message='los passwords tienen que coincidir'), validators.Length(min=8, message='el password tiene que tener 8 digitos')])
        form_class.password3 = PasswordField('confirmar password')
        return form_class

    def on_model_change(self, form, model, is_created):

        # If the password field isn't blank...
        if model.password3 == model.password2:
            # ... then encrypt the new password prior to storing it in the database. If the password field is blank,
            # the existing password in the database will be retained.
            model.password = hash_password(model.password2)
        else:
            flash('ups, hay algo mal en el password')

    # add methods to use with flask security too and use the UserMixin of security
    #
    def update_model(self, form, model, ids):
        try:
            user = security.datastore.find_user(id=ids)
            if user != None:
                username = model.username
                name = model.name
                email = model.email
                password = model.password
                user.username = username
                user.email = email
                user.name = name
                user.password = password
                security.datastore.update_user(user)
                security.datastore.commit()
                flash(f'usuario {user.name} fue creado con exito', 'success')
            else:
                username = model.username
                name = model.name
                email = model.email
                password = model.password
                user.username = username
                user.email = email
                user.name = name
                user.password = password
                security.datastore(user)
                security.datastore.commit()
                flash(f'usuario {user.name} fue editado con exito', 'success')
        except Exception as e:
            print(e)
            flash('ups algo salio mal', 'danger')

    @action('activar', 'activar usuarios', 'Seguro que quiere activar este usuario?')
    def activar(self, ids):
        try:
            user = security.datastore.find_user(id=ids)
            security.datastore.activate_user(user)
            security.datastore.commit()
            flash(f'usuario {user.name} fue activado', 'success')
        except Exception as e:
            print(e)
            flash('ups algo salio mal', 'danger')

    @action('desactivar', 'desactivar usuarios', 'Seguro que quiere desactivar este usuario?')
    def desactivar(self, ids):
        try:
            user = security.datastore.find_user(id=ids)
            security.datastore.deactivate_user(user)
            security.datastore.commit()
            flash(f'usuario {user.name} fue desactivado', 'success')
        except Exception as e:
            print(e)
            flash('ups algo salio mal', 'danger')
    @action('cerrar sesion', 'cerrar sesiones', 'Seguro que quieres cerrar todas sus sesiones?')
    def cerrar_sesion(self, ids):
        try:
            user = security.datastore.find_user(id=ids)
            security.datastore.set_uniquifier(user)
            security.datastore.commit()
            flash(f'usuario {user.name} fueron cerradas todas sus sesiones', 'success')
        except Exception as e:
            print(e)
            flash('ups algo salio mal', 'danger')    

    def _handle_view(self, name, **kwargs):
        # Override builtin _handle_view in order to redirect users when a view is not accessible.
        if not self.is_accessible():
            if current_user.is_authenticated:
            # permission denied
                abort(403)
            else:
                # login
                return redirect(url_for('security.login', next=request.url))
FalkZerd
  • 56
  • 6

1 Answers1

1

Your problem is that there is no fs_uniquifier field in your form, and thus it is not set in your user model. It is however required in your SQL table.

There are two different methods you can hook into to add the fs_uniquifier to your user model automatically.

Method 1: When data is passed from the form to the model

This is the method used in flask-react-spa, slightly adjusted.

In views.py:

import uuid

# Import the Baseform from flask_admin...
from flask_admin.form import BaseForm

# extend it ...
class BaseUserForm(BaseForm):

    def populate_obj(self, user):
        super().populate_obj(user)
        if user.fs_uniquifier is None:
            user.fs_uniquifier = uuid.uuid4().hex

# and use the extended BaseForm for your view
class UserModelView(sqla.ModelView):
    form_base_class = BaseUserForm
    ...

This will then add a fs_uniquifier to your model if it is missing when you are populating your user object with data from a form.

Method 2: Via the on_model_change event

The second method hooks into the on_model_change event of your view, like you already did for the passwords.

In views.py:

import uuid

class UserModelView(sqla.ModelView):

    def on_model_change(self, form, model, is_created):
        if model.fs_uniquifier is None:
            model.fs_uniquifier = uuid.uuid4().hex
        ...

This will then add a fs_uniquifier to your model if it is missing when you are changing or creating a new user via the view.

ofurkusi
  • 133
  • 1
  • 6