Search code examples
pythonflaskmongoengineflask-adminflask-mongoengine

Flask Admin validate unique_with errors


I have a flask monoenginge model with a unique_with field

class RedirectMixin(object):
name = db.StringField(max_length=1000, required=True, help_text="Used internally")
matching_type = db.IntField(
    help_text='`Equals` has higher priority. With concurrent `Start with` rules, longest `Url_from` wins',
    required=True, choices=MATCHING_TYPES)
add_unmatched_ending = db.BooleanField(
    help_text='Example: Starts with /a/ redirect to /b/. This will make /a/c/ redirect to /b/c/',
    default=False)
url_from = db.StringField(max_length=1000, required=True, unique_with='matching_type',
                          help_text='Case insensitive')
url_to = db.StringField(max_length=1000, required=True)

what I wonder is why flask admin doesn't validate the violation of this specification (unique_with i.e) when a form is being filled on the admin side and also how else to go about the validation if flask admin is not built for it. Thanks in advance


Solution

  • It turns this type of validation needs interaction with the db, which flask-admin validator might not be able to provide. I created a custom validator for myself like so:

    class Unique(object):
    def __init__(self, with_=None, message=None):
        self.message = message
        self.with_ = with_
    
    def __call__(self, form, field):
        query, with_msg = {}, ''
        if self.with_:
            query[self.with_] = form[self.with_].data
            with_msg = 'with "%s"' % self.with_
        query[field.name] = field.data
        if form._obj:
            query['id__ne'] = form._obj.id
        matched_entries = form.model_class.objects(**query)
        if matched_entries:
            if self.message is None:
                self.message = field.gettext('Duplicate exists. Value Should be unique ' + with_msg)
            raise ValueError(self.message)
    is_unique = Unique
    

    And then in my model I used it like so:

    from balut.lib.forms.validators import is_unique 
    
    class RedirectMixin(object):
        name = db.StringField(max_length=1000, required=True, help_text="Used internally")
        matching_type = db.IntField(
            help_text='`Equals` has higher priority. With concurrent `Start with` rules, longest `Url_from` wins',
            required=True, choices=MATCHING_TYPES)
        add_unmatched_ending = db.BooleanField(
            help_text='Example: Starts with /a/ redirect to /b/. This will make /a/c/ redirect to /b/c/',
            default=False)
        url_from = db.StringField(max_length=1000, required=True, unique_with='matching_type',
                                  help_text='Case insensitive')
        url_to = db.StringField(max_length=1000, required=True)
        form_args = dict(url_from={'validators': [is_unique(with_='matching_type', message=None)]})