Last active
October 30, 2024 13:40
-
-
Save thibaudcolas/e1cd014ed0c02b46993b1c4a4e134d9b to your computer and use it in GitHub Desktop.
Wagtail snippet to have fields that are only required as part of publishing, rather than when saving drafts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Example code to demonstrate how this works in practice | |
@publish_requires("date_published", block_schedule=True) | |
class BlogPage(Page): | |
# […] | |
date_published = models.DateField("Date article published", blank=True, null=True) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.core.exceptions import ValidationError | |
from django.http import QueryDict | |
class PublishRequiresMixin: | |
_required_for_publishing = [] | |
_block_schedule = False | |
def __init__(self, *args, **kwargs): | |
if len(args) > 0 and isinstance(args[0], QueryDict): | |
data = args[0] | |
self._is_publishing = data.get("action-publish", "no") == "action-publish" | |
else: | |
self._is_publishing = False | |
super().__init__(*args, **kwargs) | |
def clean(self): | |
cleaned_data = super().clean() | |
if not self._is_publishing and not self._block_schedule: | |
return cleaned_data | |
elif not self._is_publishing and cleaned_data.get("go_live_at", None) in (None, ""): | |
# publication has not been scheduled | |
return cleaned_data | |
errors = {} | |
for field in self._required_for_publishing: | |
if cleaned_data.get(field, None) in (None, ""): | |
errors[field] = "This field is required to publish page" | |
if len(errors) > 0: | |
raise ValidationError(errors) | |
return cleaned_data | |
def publish_requires(*args, block_schedule=False): | |
def _wrapper(page_class): | |
old_clean = page_class.clean | |
# Create a new base_form_class from the original class and a mixin | |
page_class.base_form_class = type( | |
f"{page_class.base_form_class.__name__}PublishingRequires", # new class name | |
(PublishRequiresMixin, page_class.base_form_class), # parent classes | |
{"_required_for_publishing": args, "_block_schedule": block_schedule}, # attributes | |
) | |
# For some reason, creating a new Model class based on a Mixin | |
# like we did for the form class above doesn't work. | |
# Therefore, we are monkey patching the clean method on the original page class | |
def clean(self): | |
old_clean(self) | |
if (self.id is None or self.status_string != "live") and ( | |
self.go_live_at in (None, "") or not block_schedule | |
): | |
return | |
for field in args: | |
if getattr(self, field) in (None, ""): | |
raise ValidationError(f"{self._meta.get_field(field).verbose_name} is required to publish page") | |
page_class.clean = clean | |
return page_class | |
if len(args) > 0 and not isinstance(args[0], str): | |
return _wrapper(args[0]) | |
return _wrapper |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment