Search code examples
pythondjangovalidationwagtail

Is there a way to perform validation on related Orderable in InlinePanel in WagtailCMS?


I am using Wagtail CMS and I need some validation for my Orderable model. Like, ensuring that at most one of the fields is filled.

Normally, I would override the clean(self) method for the Django model, but calling super().clean() inside that method returns None. I am still able to access the fields with self.field_name and raising ValidationError still prevents the model from creation, but it doesn't show which fields caused the error for the model in the admin interface.

I have tried overriding the clean method, that stops the model from being committed but doesn't show errors on the interface

I have tried following this part of the guide, but the clean method there isn't even called for the Orderable.

This is the example of my clean method

def clean(self):
    super().clean()
    has_image =  self.image is not None
    has_video = self.video_url is not None

    if has_image == has_video:
        raise ValidationError('Either a video or an image must be set')

I expect validation errors to show up in the admin interface.


Solution

  • I've dug into the wagtail source code and I think I found the way of how to access the form controls for the orderable.

    Let's say you have a model for a page,

    class TestPage(Page):
    
        testPageTitle = RichTextField(blank=True, max_length=250)
    
        content_panels = Page.content_panels + [
        FieldPanel('testPageTitle'),
        InlinePanel('test_page_field')
        ]
    
        base_form_class = TestPageForm
    

    with some orderable model, that is linked to the page through a related name 'test_page_field',

    class TestPageField(Orderable):
        page = ParentalKey(TestPage, on_delete=models.CASCADE, related_name='test_page_field')
    
        testPageFieldTitle = models.CharField(blank=True, max_length=250)
    
        panels = [
        FieldPanel('testPageFieldFieldTitle')
        ]
    

    then you can access that within the clean method of the page, via self.formsets['test_page_field'].forms, which is a list of Django form objects, where the regular checks can be done and .add_error() method can be used. The relevant base_form_class would look as follows:

    class TestPageForm(WagtailAdminPageForm):
    
        def clean(self):
        cleaned_data = super().clean()
    
        #loop over linked orderables
        for form in self.formsets['test_page_field'].forms:
    
            #check first if form is valid, otherwise cleaned_data will not be accesible/set
            if form.is_valid():
                cleaned_form_data = form.clean()
                testPageFieldFieldTitle = cleaned_form_data.get('testPageFieldFieldTitle')
    
                #execute some validation condition, and raise the error if it fails
                if testPageFieldFieldTitle is None:
                    form.add_error('testPageFieldFieldTitle', 'please dont leave me empty')
    
        return cleaned_data
    

    I hope this helps.