Skip to content

Instantly share code, notes, and snippets.

@KalobTaulien
Created March 24, 2019 23:16
Show Gist options
  • Save KalobTaulien/c1a6f2c9b356cadbd100d60d83d82bcb to your computer and use it in GitHub Desktop.
Save KalobTaulien/c1a6f2c9b356cadbd100d60d83d82bcb to your computer and use it in GitHub Desktop.
StructBlock StreamField Validation
from django.core.exceptions import ValidationError
from django.forms.utils import ErrorList
from wagtail.core import blocks
class CTABlock(blocks.StructBlock):
"""A simple call to action section."""
button_url = blocks.URLBlock(required=False)
button_page = blocks.PageChooserBlock(required=False)
class Meta: # noqa
template = "streams/cta_block.html"
icon = "placeholder"
label = "Call to Action"
def clean(self, value):
# Error container
errors = {}
# Check if both fields are empty
if not value.get('button_url') and value.get('button_page') is None:
errors['button_url'] = ErrorList(['You must select a page, or enter a URL. Please fill just one of these.'])
errors['button_page'] = ErrorList(['You must select a page, or enter a URL. Please fill just one of these.'])
# Check if both fields are filled
elif value.get('button_url') and value.get('button_page'):
errors['button_url'] = ErrorList(['Please select a page OR enter a URL (choose one)'])
errors['button_page'] = ErrorList(['Please select a page OR enter a URL (choose one)'])
# If there are errors, raise a validation with the errors dict from line 20.
if errors:
raise ValidationError('Validation error in StructBlock', params=errors)
# If no ValidationError was raised, perform a regular clean on this StreaemField.
return super().clean(value)
@realjumy
Copy link

realjumy commented Sep 22, 2020

As usual, a brilliant and clear tutorial. Thanks so much for it!

Also, it would be great if you can explain how to do something similar but with a StructBlock inside a ListBlock. It's something I have spent a lot of time and I still can't quite find how to do it. Something like

document_item = ListBlock(StructBlock([
        ('doc_name', CharBlock(label="Document name", required=False)),
        ('doc_file', DocumentChooserBlock(label="File to upload", required=False)),
        ('doc_alternative_url', URLBlock(label="Alternative link to the document", required=False)),
    ]))

I tried with something like this:

def clean(self, value):
        
        # This is for forcing the user to choose either an internal link or an external one,
        # but not both.

        # Error container 
        errors = {}

        added_content = value.get('document_item')

        for content_item in added_content:

            # Check if both fields are empty
            if not content_item.get('doc_file') and content_item.get('doc_alternative_url') is "":
                errors['doc_file'] = ErrorList(['You must select a page, or enter a URL. Please fill just one of these.'])
                errors['doc_alternative_url'] = ErrorList(['You must select a page, or enter a URL. Please fill just one of these.'])
            # Check if both fields are filled
            elif content_item.get('doc_file') and content_item.get('doc_alternative_url'):
                errors['doc_file'] = ErrorList(['Please select a page OR enter a URL (choose one)'])
                errors['doc_alternative_url'] = ErrorList(['Please select a page OR enter a URL (choose one)'])
        
        # If there are errors, raise a validation with the errors dict.
        if any (errors):
            raise ValidationError('Validation error in StructBlock', params=errors)
        # If no ValidationError was raised, perform a regular clean on this StreamField.
        return super().clean(value)

But it's clearly not the way to do it... I can prevent the page from being saved, but I can't display the reason or indicate which fields are not correct...

@realjumy
Copy link

realjumy commented Sep 24, 2020

OK, I sorted it. In case you're curious about my approach, I simply took the StructBlock out of the ListBlock:

class DocumentItemBlock(StructBlock):
    doc_name= CharBlock(label="Document name", required=False)
    doc_file= DocumentChooserBlock(label="File to upload", required=False)
    doc_alternative_url= URLBlock(label="Alternative link to the document", required=False)

    def clean(self, value):
        
        # This is for forcing the user to choose either an internal link or an external one,
        # but not both.

        # Error container 
        errors = {}
        
        # Check if both fields are empty
        if not value.get('doc_file') and value.get('doc_alternative_url') is "":
            errors['doc_file'] = ErrorList(['You must select a page, or enter a URL. Please fill just one of these.'])
            errors['doc_alternative_url'] = ErrorList(['You must select a page, or enter a URL. Please fill just one of these.'])
        # Check if both fields are filled
        elif value.get('doc_file') and value.get('doc_alternative_url'):
            errors['doc_file'] = ErrorList(['Please select a page OR enter a URL (choose one)'])
            errors['doc_alternative_url'] = ErrorList(['Please select a page OR enter a URL (choose one)'])
        
        # If there are errors, raise a validation with the errors dict.
        if any (errors):
            raise ValidationError('Validation error in StructBlock', params=errors)
        # If no ValidationError was raised, perform a regular clean on this StreamField.
        return super().clean(value)

And then is just a matter of calling it from the other class:

class DocumentsBlock(StructBlock):
    doc_category = CharBlock(label="Documents group")
    document_item = ListBlock(
        DocumentItemBlock(
            label="Document description", 
            required=False)
    )

Once again, thank you so much for your blog :D

@stevedya
Copy link

stevedya commented Feb 7, 2022

For anyone who's errors are not showing at the field level, try changing your exception on line 33 to:
raise StructBlockValidationError(errors)

This is imported from:
from wagtail.core.blocks.struct_block import StructBlockValidationError

This comment outlines the reason for this. 🙂

@arijit-s04
Copy link

Extending stevedya's answer, if someone is not able to import StructBlockValidationError,
try importing from this
from wagtail.blocks.struct_block import StructBlockValidationError

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment