Do Nguyen Ha
Do Nguyen Ha

Reputation: 101

how to use flask-admin for editing modelview

How to set password_hash using generate_password_hash from the edit page of flask-admin

  1. i create a username and password in python shell. the password is hashing
  2. admin.add_view(MyModelView(User, db.session) - let me edit the User class Models
  3. when i edit the password and submit but the password is saved in plain text.

How to edit password from flask-admin, the password should be save in hashing type

My code is:

from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     email = db.Column(db.String(120))
     password_hash = db.Column(db.String(64))
     username = db.Column(db.String(64), unique=True, index=True)

     @password.setter
     def password(self, password):
          self.password_hash = generate_password_hash(password)

     def __repr__(self):
          return '<User %r>' % self.username

#Create custom models view
class MyModelView(sqla.ModelView):
    @admin.expose('/login/')
    def index(self):
        return self.render('login.html')

# Create custom admin view
class MyAdminView(admin.BaseView):
    @admin.expose('/')
    def index(self):
        return self.render('myadmin.html')

admin = admin.Admin(name="Simple Views")
admin.add_view(MyAdminView(name='hello'))
admin.add_view(MyModelView(User, db.session))
admin.init_app(app)
app.run()

Upvotes: 4

Views: 5139

Answers (5)

alvinMemphis
alvinMemphis

Reputation: 352

my solution was to simply add the column formatters field to the UserView model which returns a dict of all the form fields .then chose to format the password being saved with a bcrypt hash.

class UserView(ModelView):
        column_formatters =dict(password=lambda v,c,m,password: bcrypt.generate_password_hash(m.password, config.get('BCRYPT_LOG_ROUNDS')) \
                .decode('utf-8'))

for more details on the arguments the lambdafunction takes ,the official flaskAdmin docs .column_formatters

Upvotes: 0

MarSoft
MarSoft

Reputation: 3913

Alternative solution is to subclass a TextField adding custom processing logic:

class MyPassField(TextField):
    def process_data(self, value):
        self.data = ''  # even if password is already set, don't show hash here
        # or else it will be double-hashed on save
        self.orig_hash = value

    def process_formdata(self, valuelist):
        value = ''
        if valuelist:
            value = valuelist[0]
        if value:
            self.data = generate_password_hash(value)
        else:
            self.data = self.orig_hash

class UserView(ModelView):
    form_overrides = dict(
        passhash=MyPassField,
    )
    form_widget_args = dict(
        passhash=dict(
            placeholder='Enter new password here to change password',
        ),
    )

Upvotes: 8

Nick Woodhams
Nick Woodhams

Reputation: 12717

I tried the solutions outlined in some of the other answers and I was only partially successful. There were issues with being able to edit the user later, and the password rehashing, or disappearing altogether.

One discovery I made was that on_model_change actually gets called AFTER the model is populated from the form. There is no way to access the old values of the model without querying the database or monkey patching update_model.

I have come up with a simpler version (I believe) that works in all scenarios.

Here is the whole view:

class UserView(AdminModel):
    can_create = True
    column_list = ('name', 'email',)
    column_searchable_list = ('name', 'email',)
    form_excluded_columns = ('password',)
    form_extra_fields = {
        'set_password': PasswordField('Set New Password')
    }

    def on_model_change(self, form, model, is_created):
        if is_created:
            model.active = True
            model.pending = False
        if form.email.data:
            # Strip spaces from the email
            form.email = form.email.data.strip()
        if form.set_password.data:
            model.password = bcrypt.generate_password_hash(form.set_password.data.strip())

    def __init__(self, session, **kwargs):
        # You can pass name and other parameters if you want to
        super(UserView, self).__init__(User, session, **kwargs)

What I did was add a form field set_password which when populated creates a password hash and updates password on the model.

One and done!

Upvotes: 2

Kyc Kyc
Kyc Kyc

Reputation: 33

Simpler solution, you don't need to subclass TextField

Just add on_form_prefill :

def on_model_change(self, form, User, is_created):
    if form.password_hash.data:
        User.set_password(form.password_hash.data)
    else:
        del form.password_hash

def on_form_prefill(self, form, id):
    form.password_hash.data = ''

This will prevent double-hashing.

Upvotes: 3

Do Nguyen Ha
Do Nguyen Ha

Reputation: 101

i solved my problem by using on_model_change function in flask-admin

#Create custom models view
class MyModelView(sqla.ModelView):
    @admin.expose('/login/')
    def index(self):
        return self.render('login.html')
    def on_model_change(self, form, User, is_created=False):
        User.password = form.password_hash.data

Upvotes: 6

Related Questions