Robert
Robert

Reputation: 281

Wagtail ModelAdmin > How to use custom validation?

I am using wagtail ModelAdmin for some of my non page models and want to add some custom validation.

This is some of the code.

class EditPlanningView(EditView):
    
    def publish_url(self):
        return self.url_helper.get_action_url('publish', self.pk_quoted)

    def unpublish_url(self):
        return self.url_helper.get_action_url('unpublish', self.pk_quoted)

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        if form.is_valid():
            instance = form.save(commit=False)
            if bool(request.POST.get('action-publish')):
                try:
                    instance.publish(commit=True)
                except PublishWithoutMeetingError as e:
                    form.add_error(
                        'planning_meeting',
                        e
                    )
                    return self.form_invalid(form)

When validation fails the invalid form is returned, but the error I added is not bound to the field. In stead a 'general error message' appears at the top.

Can someone help me out?

Cheers,
Robert

Upvotes: 2

Views: 1531

Answers (1)

Ramesh-X
Ramesh-X

Reputation: 5055

I think the error is in the following lines.

                 form.add_error(
                     'planning_meeting',
                      e
                 )

Actually can't say anything without knowing about PublishWithoutMeetingError, the type of e. Better to replace e with a string. And make sure the post method is not throwing any exceptions. Other than that, what you have done is correct. Read the following to also to check if you have missed any point.

Long Answer

There are two ways that you can achieve showing an error messages in forms.

  • Overriding the Form
  • Overriding the EditView

In both of these cases, you are going to use a method called add_error. That method takes 2 argument, field and error. From these two, error is the most important argument. The field simply state the field of the form that this error applies to. This can be None.

The error argument can be multiple types.

  1. The error argument can be an instance of str. Then wagtail will assign the given error to the given field.
  2. The error argument can be an instance of list of str. Then wagtail will assign the given list of errors to the given field.
  3. The error argument can be an instance of dict with str keys and str or list of str values. In this case field should be None. The keys will be used as the fields for the errors given by values.
  4. The error argument can be an instance of ValidationError exception. You can create a ValidationError using a str, list, or dict, which represent the above three cases.

Overriding the Form

In the form clean method need to be overridden in order to find errors.

from wagtail.admin.forms.models import WagtailAdminModelForm


class ExtraForm(WagtailAdminModelForm):
    def clean(self):
        cleaned_data = super().clean()    # Get the already cleaned data. Same as self.cleaned_data in this case. But this way is better.
        title = cleaned_data.get('title')   # Get the cleaned title
        if title is None:    # Title is never None here, but still..
            return cleaned_data
        title = title.strip()   # Do some formatting if needed
        if title.startswith('A'):  # Validation
            self.add_error('title', 'Title cannot start with A')   # Validation error
        else:
            cleaned_data['title'] = title   # Use the formatted title
        return cleaned_data
    

class MyModel(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=500, default='', blank=False)
    # Or any other fields you have
    base_form_class = ExtraForm    # Tell wagtail to use ExtraForm instead of the default one

Overriding the EditView

This way is same as the way that you have mentioned in the question. You need to override post method. You need to check if the form associated with the EditView is valid or invalid and return the appropriate form.

To check validity, is_valid method of the form is used by default. That method will clean the form and check if there are errors added to the form.
If form is valid, you need to return self.valid_form and self.invalid_form otherwise.

Unlike overriding the Form, you can access the request here.

class MyEditView(EditView):
    def post(self, request, *args, **kwargs):
        form = self.get_form()    # Get the form associated with this edit view
        if form.is_valid():    # Check if the form pass the default checks
            my_field = request.POST.get('my_field')   # You can access the request
            title = form.cleaned_data.get('title')    # You can access the form data
            if title != my_field:    # Validation
                form.add_error('title', 'Title must match my_field')   # Validation error
                return self.form_invalid(form)    # Return invalid form if there are validation errors
            return self.form_valid(form)    # Return the valid form if there are no validation errors
        else:
            return self.form_invalid(form)    # Return invalid form if default check failed

class MyModelAdmin(ModelAdmin):
    model = MyModel
    menu_label = 'My Model'
    list_display = ('id', 'title')
    search_fields = (
        'title',
    )
    edit_view_class = MyEditView    # Tell wagtail to use MyEditView instead of the default one.

Upvotes: 2

Related Questions