Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link

@realjumy 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

This comment has been minimized.

Copy link

@realjumy 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.