在 WTForms 中将对象传递给自定义验证器

Passing Objects to Custom Validator in WTForms

编辑(试图澄清问题):
我为我的烧瓶应用程序创建的自定义验证器检查重复的用户名和电子邮件不适用于管理员用户修改其他用户帐户。当管理员用户编辑另一个用户的用户名或电子邮件时,验证器会指出它是重复的,因为它正在尝试比较当前(管理员)用户的用户名和电子邮件,而不是正在编辑的用户的用户名和电子邮件。我如何编写一个自定义验证器来使用传递给下面视图函数中的表单的用户对象来比较正在编辑的用户而不是登录用户的用户名和电子邮件?

原文post:
我正在开发一个 Flask 应用程序,该应用程序允许具有管理员权限的用户修改用户帐户信息。目前,我正在使用 Flask-WTF 和 WTForms 在用户个人资料页面上创建一个表单,帐户所有者或具有管理员权限的用户(通过用户模型中的角色分配)可以编辑用户信息。在视图函数中,我将用户信息传递给表单以使用用户信息预填充表单字段。当我为帐户所有者制作个人资料编辑表单时,当提交表单时,我有一个自定义验证器,它将提交的数据与当前登录用户的数据进行比较,以检测是否有任何字段被编辑,然后检查是否有一些这些字段(如用户名、电子邮件等)已经在数据库中以避免重复。对于管理员用户可以编辑其他用户的表单,此验证器一直阻止将新值写入数据库,因为验证器正在将登录的用户信息与数据库进行比较,而不是属于正在编辑的帐户的用户信息。我想知道如何在提交表单时将用于填充表单字段的用户对象传递给表单验证? Flask 应用程序使用蓝图结构。我对此很陌生,所以我希望这个问题是有道理的。这是一些我认为有用的代码。

这里是视图函数:

# Edit account view function for admin users
@users.route('/edit-account/<int:user_id>', methods=['GET', 'POST'])
@login_required
def edit_account(user_id):
    # Get the info for the user being edited
    user = User.query.get_or_404(user_id)
    # Check to be sure user has admin privileges
    if current_user.role != 'admin' and current_user.role != 'agent':
        abort(403)
    form = AccountEditForm()
    if form.validate_on_submit():
        # Update the profile picture, if a file is submitted
        if form.profile_pic.data:
            picture_file = save_picture(form.profile_pic.data)
            user.profile_pic = picture_file
        # Update the database entries for the user
        user.username = form.username.data
        user.first_name = form.first_name.data
        user.last_name = form.last_name.data
        user.email = form.email.data
        user.role = form.role.data
        db.session.commit()
        flash(f'The account for {user.first_name} {user.last_name} has been updated.', 'success')
        return redirect(url_for('users.users_admin'))
    # Pre-populate the form with existing data
    elif request.method == 'GET':
        form.username.data = user.username
        form.first_name.data = user.first_name
        form.last_name.data = user.last_name
        form.email.data = user.email
        form.role.data = user.role
    image_file = url_for('static', filename=f'profile_pics/{user.profile_pic}')
    return render_template('account.html', title='account',
                           image_file=image_file, form=form, user=user)

当管理员用户试图编辑另一个用户的帐户时,此表单 class 对验证器不起作用:

class AccountEditForm(FlaskForm):
    username = StringField('Username',
                           validators=[DataRequired(), Length(min=2, max=20)])
    first_name = StringField('First Name',
                             validators=[DataRequired(), Length(min=2, max=32)])
    last_name = StringField('Last Name',
                            validators=[DataRequired(), Length(min=2, max=32)])
    email = StringField('Email',
                        validators=[DataRequired(), Email()])
    profile_pic = FileField('Update Profile Picture',
                            validators=[FileAllowed(['jpg', 'png'])])
    role = SelectField('Role', validators=[DataRequired()],
                       choices=[('admin', 'Admin'), ('agent', 'Agent'), ('faculty', 'Faculty'),
                                ('staff', 'Staff'), ('student', 'Student')])
    submit = SubmitField('Update')

    # Custom validation to check for duplicate usernames
    def validate_username(self, username):
        # Check to see if the form data is different than the current db entry
        if username.data != username:
            # Query db for existing username matching the one submitted on the form
            user = User.query.filter_by(username=username.data).first()
            if user:
                raise ValidationError('Username is taken, please choose another.')

    # Custom validation to check for duplicate email
    def validate_email(self, email):
        # Check to see if the form data is different than the current db entry
        if email.data != user.email:
            # Query db for existing email matching the one submitted on the form
            user = User.query.filter_by(email=email.data).first()
            if user:
                raise ValidationError('Email is already used, please choose another or login.')

这是当用户从不同的表单编辑他们自己的帐户时工作的自定义验证器 class:

# Custom validation to check for duplicate usernames
def validate_username(self, username):
    # Check to see if the form data is different than the current db entry
    if username.data != current_user.username:
        # Query db for existing username matching the one submitted on the form
        user = User.query.filter_by(username=username.data).first()
        if user:
            raise ValidationError('Username is taken, please choose another.')

# Custom validation to check for duplicate email
def validate_email(self, email):
    # Check to see if the form data is different than the current db entry
    if email.data != current_user.email:
        # Query db for existing email matching the one submitted on the form
        user = User.query.filter_by(email=email.data).first()
        if user:
            raise ValidationError('Email is already used, please choose another or login.')

这里有一个将对象传递给自定义验证器的例子,场景和你的完全一样:

class EditProfileAdminForm(EditProfileForm):
    email = StringField('Email', validators=[DataRequired(), Length(1, 254), Email()])
    role = SelectField('Role', coerce=int)
    active = BooleanField('Active')
    confirmed = BooleanField('Confirmed')
    submit = SubmitField()

    def __init__(self, user, *args, **kwargs):  # accept the object
        super(EditProfileAdminForm, self).__init__(*args, **kwargs)
        self.role.choices = [(role.id, role.name)
                             for role in Role.query.order_by(Role.name).all()]
        self.user = user  # set the object as class attr

    def validate_username(self, field):
        # use self.user.username to get the user's username
        if field.data != self.user.username and User.query.filter_by(username=field.data).first():
            raise ValidationError('The username is already in use.')

    def validate_email(self, field):
        if field.data != self.user.email and User.query.filter_by(email=field.data.lower()).first():
            raise ValidationError('The email is already in use.')


# view function
@admin_bp.route('/profile/<int:user_id>', methods=['GET', 'POST'])
@login_required
@admin_required
def edit_profile_admin(user_id):
    user = User.query.get_or_404(user_id)
    form = EditProfileAdminForm(user=user)  # pass the object
    # ...

示例代码来自 photo-sharing application.