Search code examples
pythonflaskflask-wtformswtforms

WTForms skip validation if field data is unchanged


I have a Flask web app which manage all the kiosks' information. I created a WTForms object for the kiosk's information (forms.py):

class KioskForm(FlaskForm):
    kiosk_name = StringField('Kiosk Name', validators=[DataRequired()])
    kiosk_site = SelectField('Kiosk\'s Site', validators=[DataRequired()])
    kiosk_location = StringField(
        'Kiosk\'s Location', validators=[DataRequired()])
    kiosk_ip = StringField('IP Address', validators=[
        IPAddress(), DataRequired()])
    kiosk_mac = StringField('MAC Address', validators=[DataRequired()])
    submit = SubmitField('Submit')

    def validate_kiosk_name(self, kiosk_name):
        kiosk = db.session.query(KioskConf).filter_by(
            equipment_name=self.kiosk_name.data).first()

        if kiosk is not None:
            raise ValidationError('Kiosk Name existed.')

Suppose I want to edit an existing kiosk's information (routes.py):

@app.route('/edit_kiosk/<kiosk_name>', methods=['GET', 'POST'])
def edit(kiosk_name):
    data = db.session.query(KioskConf).filter_by(
        equipment_name=kiosk_name).first()

    form = KioskForm(kiosk_name=data.equipment_name,
                     kiosk_location=data.equipment_location,
                     kiosk_ip=data.equipment_ip,
                     kiosk_mac=data.equipment_mac)
    ... ...

What I wanted to do is: if someone changed a kiosk's name to another existing ones, it'll complaint the name is already existed. Which the validation is in the forms.py.

But, one problem is that if I edited an existing kiosk, perhaps changed the ip address, but name remains the same, upon submit it still complaint the name is already existed. Something must be wrong about the name validation here.

How to make WTForms skip the name validation if the name is unchanged?


Solution

  • So I came up with a solution that behaves exactly what I wanted, although ended up with a slightly more clunky codebase.

    In forms.py, add an initialiser to store the original field data, and used to compare the new data:

    class KioskForm(FlaskForm):
        kiosk_name = StringField('Kiosk Name', validators=[DataRequired()])
        kiosk_site = SelectField('Kiosk\'s Site', validators=[DataRequired()])
        kiosk_location = StringField(
            'Kiosk\'s Location', validators=[DataRequired()])
        kiosk_ip = StringField('IP Address', validators=[
            IPAddress(), DataRequired()])
        kiosk_mac = StringField('MAC Address', validators=[DataRequired()])
        submit = SubmitField('Submit')
    
        # Initialiser
        def __init__(self, original_name, *args, **kwargs):
            super(KioskForm, self).__init__(*args, **kwargs)
            self.original_name = original_name
    
        # Changed to compare the old & new data
        def validate_kiosk_name(self, kiosk_name):
            if kiosk_name.data != self.original_name:
                kiosk = db.session.query(KioskConf).filter_by(
                    equipment_name=self.kiosk_name.data).first()
                if kiosk is not None:
                    raise ValidationError('Kiosk ID existed.')
    

    And in the routes.py, feed in the original name into original_name:

    @app.route('/edit_kiosk/<kiosk_name>', methods=['GET', 'POST'])
    def edit(kiosk_name):
        data = db.session.query(KioskConf).filter_by(
            equipment_name=kiosk_name).first()
    
        form = KioskForm(kiosk_name=data.equipment_name,
                         kiosk_location=data.equipment_location,
                         kiosk_ip=data.equipment_ip,
                         kiosk_mac=data.equipment_mac,
                         original_name=data.equipment_name
    )
        ... ...