Skip to content

Instantly share code, notes, and snippets.

@thibaudcolas
Last active October 30, 2024 13:40
Show Gist options
  • Save thibaudcolas/e1cd014ed0c02b46993b1c4a4e134d9b to your computer and use it in GitHub Desktop.
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.
# 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)
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