Checking uniqueness contraint during form validati

2019-03-04 19:05发布

I am using Flask and WTforms in App Engine, trying to implement uniqueness contraint on one of the field. The question is big, please be patient and I have been stuck here from many hours, need some help from you people. Started learning App Engine, Flask and WTForms a month ago. Thanks in advance.

Application has model 'Team' as shown below:

class Team(db.Model):
    name = db.StringProperty(required=True)
    -- some other fields here --

Requirement: Name of the team has to be unique.

I have followed the links

Have come up with the following code:

models.py: Created a separate table 'Unique' as given in the link:

class Unique(db.Model):

""" Handles uniqueness constriant on a field """

@classmethod
def unique_check(cls, form_name, field_data):
    def tx(form_name, field_data):
        key_name = "%s%s" % (form_name, field_data)
        uk = Unique.get_by_key_name(key_name)
        app.logger.debug("UK:" + str(uk))
        if uk:
            return False
        uk = Unique(key_name=key_name)
        uk.put()
        return True
    ret_val = db.run_in_transaction(tx, form_name, field_data)
    app.logger.debug("ret_val:" + str(ret_val))
    return ret_val

forms.py: I have overridden the __init__() and validate_on_submit() function in which uniqueness is checked and if it is not unique, error is attached to that field and validation error will be raised in the same way as wtforms's validators.

class TeamForm(wtf.Form):

def __init__(self, *args, **kwargs):
    super(TeamForm, self).__init__(*args, **kwargs)
    if kwargs.get('edit', None):
        self.old_name = self.name.data.lower()

def validate_on_submit(self, edit=False):
    if not super(TeamForm, self).validate_on_submit():
        return False
    if edit:
        if self.old_name and self.old_name != self.name.data.lower():
            Unique.delete_entity(self.__class__.__name__, self.old_name)
            if not Unique.unique_check(self.__class__.__name__, self.name.data.lower()):
                self.name.errors.append("Value '%s' is not unique" % self.name.data)
        return False
    else:
        if not Unique.unique_check(self.__class__.__name__, self.name.data.lower()):
            self.name.errors.append("Value '%s' is not unique" % self.name.data)
            return False

    return True

    **----  Form fields declaration ----**

The above code works when new team is inserted.I mean it checks uniqueness properly. The problem occurs, when user edits the team information. Following two scenarios are problematic:

  • When the user tries to submit the form, application will throw "Not unique" error, it is obvious because "Unique" table has "key_name" for this team.
  • If user changes "team name", application has to delete the previous team name from the "Unique" table and has to check uniqueness for the "changed team name". I am not able to handle these two scenarios.

My edit_team function looks like this:

@app.route('/team/edit/<key>', methods=['GET','POST'])
@login_required
def edit_team(key):

    k = db.Key(key)
    team = db.get(k)
    form = TeamForm(obj = team, edit=True) # to save old name, doesn't work.
    if form.validate_on_submit(edit=True): # edit=True is given only in edit function
        team.name = form.name.data
        -- others fields are updated in the similar way --
        team.put()
        return redirect(url_for('teams_list'))
    return render_template('edit_team.html', form=form)

Problem can be easily solved if I am able to find out 'old name' of the team, so that I can delete it from the "Unique" table. As you can see I am saving old name of the team in TeamForm __init__() function, but __init__() is called during GET(old name is saved) and also in POST(modified name will get saved!!). So, I cannot find out old name at all and it remains in the "Unique" table, nobody can use this "old team name" anymore.

I tried to explain as much as possible, please let me know if you want more info.

1条回答
劳资没心,怎么记你
2楼-- · 2019-03-04 19:53

Edit: didn't answer your question properly the first time.

Separate instances of the Form object will be instantiated for the GET and POST requests, so you can't save the old_name to self.

You'll need to pass the old_name to the browser in the form, and have the browser submit the old_name back in the POST request.

The easyish way to do this is to create a hidden form field that the user doesn't see, but will get submitted by the POST request. I'm not too familiar with WTForms but I assume you can initialize the old_name field value in your the GET request handler.

查看更多
登录 后发表回答