【问题标题】:Flask admin overrides password when user model is changed更改用户模型时,Flask 管理员会覆盖密码
【发布时间】:2017-01-04 05:51:21
【问题描述】:

我目前正在研究一个烧瓶项目,并第一次尝试使用烧瓶管理员。到目前为止一切都很好,但有一件事真的让我很困扰: 每当我编辑我的用户模型时,用户密码都会被覆盖。我正在遵循this question 的第二个答案中给出的建议,以防止烧瓶管理员重新散列我的密码。不幸的是,清空的密码字段仍然被写入数据库。

我试图从User-Model 获取当前密码,该密码作为on_model_change 方法的参数提供,但不知何故,密码似乎已经被覆盖(或者它不是实际的数据库我在这里看的模型 - 我在这里有点困惑)。

我的代码如下所示:

用户模型

class User(UserMixin, SurrogatePK, Model):
    """A user of the app."""

    __tablename__ = 'users'
    username = Column(db.String(80), unique=True, nullable=False)
    email = Column(db.String(80), unique=True, nullable=False)
    #: The hashed password
    password = Column(db.String(128), nullable=True)
    created_at = Column(db.DateTime, nullable=False,
                        default=datetime.datetime.utcnow)
    first_name = Column(db.String(30), nullable=True)
    last_name = Column(db.String(30), nullable=True)
    active = Column(db.Boolean(), default=False)
    is_admin = Column(db.Boolean(), default=False)

    def __init__(self, username="", email="", password=None, **kwargs):
        """Create instance."""
        db.Model.__init__(self, username=username, email=email, **kwargs)
        if password:
            self.set_password(password)
        else:
            self.password = None

    def __str__(self):
        """String representation of the user. Shows the users email address."""
        return self.email

    def set_password(self, password):
        """Set password"""
        self.password = bcrypt.generate_password_hash(password)

    def check_password(self, value):
        """Check password."""
        return bcrypt.check_password_hash(self.password, value)

    def get_id(self):
        """Return the email address to satisfy Flask-Login's requirements"""
        return self.id

    @property
    def full_name(self):
        """Full user name."""
        return "{0} {1}".format(self.first_name, self.last_name)

    @property
    def is_active(self):
        """Active or non active user (required by flask-login)"""
        return self.active

    @property
    def is_authenticated(self):
        """Return True if the user is authenticated."""
         if isinstance(self, AnonymousUserMixin):
            return False
        else:
            return True

    @property
    def is_anonymous(self):
        """False, as anonymous users aren't supported."""
        return False

Flask-Admin 用户视图

class UserView(MyModelView):
    """Flask user model view."""
    create_modal = True
    edit_modal = True

    def on_model_change(self, form, User, is_created):
        if form.password.data is not None:
            User.set_password(form.password.data)
        else:
           del form.password

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

非常感谢任何帮助。 提前致谢,

奥尼罗

【问题讨论】:

    标签: python flask flask-admin


    【解决方案1】:

    我也遇到过类似的问题。当密码字段被更改时,我需要生成密码的哈希值。我不想添加用于更改密码的其他表单。在后端,我使用了 MongoDB。我的烧瓶管理员解决方案:

    class User(db.Document, UserMixin):
        ***
        password = db.StringField(verbose_name='Password')
        roles = db.ListField(db.ReferenceField(Role), default=[] 
    
        def save(self) -> None:
            if not self.id:
                self.password = hashlib.md5((self.password + Config.SECURITY_PASSWORD_SALT).encode()).hexdigest()
                return super(User, self).save(self)
            else:
                return super(User, self).update(
                ***
                password = self.password,
                )
    
    class UserModelView(ModelView):   
        def on_model_change(self, form, model, is_created):
            user = User.objects(id=model.id)[0]
            if user.password != form.password.data:
                model.password = hashlib.md5((form.password.data + Config.SECURITY_PASSWORD_SALT).encode()).hexdigest()
    
    admin.add_view(UserModelView(User, 'Users'))
    

    对于 SQL 解决方案,它也是实际的。

    【讨论】:

      【解决方案2】:

      可能更容易覆盖get_edit_form 方法并从编辑表单中完全删除密码字段。

      class UserView(MyModelView):
          def get_edit_form(self):
              form_class = super(UserView, self).get_edit_form()
              del form_class.password
              return form_class
      

      另一种选择是从表单中完全删除模型密码字段,并使用一个虚拟密码字段,然后可以使用该字段填充模型的密码。通过删除真正的密码字段,Flask-Admin 不会踩到我们的密码数据。示例:

      class UserView(MyModelView):
          form_excluded_columns = ('password')
          #  Form will now use all the other fields in the model
      
          #  Add our own password form field - call it password2
          form_extra_fields = {
              'password2': PasswordField('Password')
          }
      
          # set the form fields to use
          form_columns = (
              'username',
              'email',
              'first_name',
              'last_name',
              'password2',
              'created_at',
              'active',
              'is_admin',
          )
      
          def on_model_change(self, form, User, is_created):
              if form.password2.data is not None:
                  User.set_password(form.password2.data)
      

      【讨论】:

      • 感谢您的回复。我也这么想。但随后我需要为同样源自 User-Model 的密码创建另一个视图,flask-admin 以某种方式不希望我这样做。不过,我一定会尽力让你的建议奏效。谢谢。
      • 非常感谢。我不得不做一个微小的改变,但现在一切似乎都很好。我刚刚将form.password2.data != ' ' 添加到on_model_change 内的条件中。这似乎是必要的,因为 password2 字段不会默认为 None,即使它是空的。再次感谢:)