Files
old-saburly-wagtail-web/env/lib/python3.10/site-packages/wagtail/admin/forms/models.py

170 lines
5.3 KiB
Python
Raw Normal View History

2024-08-27 20:33:44 +02:00
import copy
from django import forms
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext as _
from modelcluster.forms import ClusterForm, ClusterFormMetaclass, ClusterFormOptions
from permissionedforms import (
PermissionedForm,
PermissionedFormMetaclass,
PermissionedFormOptionsMixin,
)
from taggit.managers import TaggableManager
from wagtail.admin import widgets
from wagtail.admin.forms.tags import TagField
from wagtail.models import Page
from wagtail.utils.registry import ModelFieldRegistry
# Define a registry of form field properties to override for a given model field
registry = ModelFieldRegistry()
# Aliases to lookups in the overrides registry, for backwards compatibility
FORM_FIELD_OVERRIDES = registry.values_by_class
DIRECT_FORM_FIELD_OVERRIDES = registry.values_by_exact_class
def register_form_field_override(
db_field_class, to=None, override=None, exact_class=False
):
"""
Define parameters for form fields to be used by WagtailAdminModelForm for a given
database field.
"""
if override is None:
raise ImproperlyConfigured(
"register_form_field_override must be passed an 'override' keyword argument"
)
if to and db_field_class != models.ForeignKey:
raise ImproperlyConfigured(
"The 'to' argument on register_form_field_override is only valid for ForeignKey fields"
)
registry.register(db_field_class, to=to, value=override, exact_class=exact_class)
# Define built-in overrides
# Date / time fields
register_form_field_override(
models.DateField, override={"widget": widgets.AdminDateInput}
)
register_form_field_override(
models.TimeField, override={"widget": widgets.AdminTimeInput}
)
register_form_field_override(
models.DateTimeField, override={"widget": widgets.AdminDateTimeInput}
)
# Auto-height text fields (defined as exact_class=True so that it doesn't take effect for RichTextField)
register_form_field_override(
models.TextField,
override={"widget": widgets.AdminAutoHeightTextInput},
exact_class=True,
)
# Page chooser
register_form_field_override(
models.ForeignKey,
to=Page,
override=lambda db_field: {
"widget": widgets.AdminPageChooser(target_models=[db_field.remote_field.model])
},
)
# Tag fields
register_form_field_override(
TaggableManager,
override=(
lambda db_field: {"form_class": TagField, "tag_model": db_field.related_model}
),
)
# Slug fields
register_form_field_override(
models.SlugField,
override={"widget": widgets.SlugInput},
)
# Callback to allow us to override the default form fields provided for each model field.
def formfield_for_dbfield(db_field, **kwargs):
overrides = registry.get(db_field)
if overrides:
kwargs = dict(copy.deepcopy(overrides), **kwargs)
return db_field.formfield(**kwargs)
class WagtailAdminModelFormOptions(PermissionedFormOptionsMixin, ClusterFormOptions):
# Container for the options set in the inner 'class Meta' of a model form, supporting
# extensions for both ClusterForm ('formsets') and PermissionedForm ('field_permissions').
pass
class WagtailAdminModelFormMetaclass(PermissionedFormMetaclass, ClusterFormMetaclass):
options_class = WagtailAdminModelFormOptions
# set extra_form_count to 0, as we're creating extra forms in JS
extra_form_count = 0
@classmethod
def child_form(cls):
return WagtailAdminModelForm
class WagtailAdminModelForm(
PermissionedForm, ClusterForm, metaclass=WagtailAdminModelFormMetaclass
):
def __init__(self, *args, **kwargs):
# keep hold of the `for_user` kwarg as well as passing it on to PermissionedForm
self.for_user = kwargs.get("for_user")
super().__init__(*args, **kwargs)
class Meta:
formfield_callback = formfield_for_dbfield
# Now, any model forms built off WagtailAdminModelForm instead of ModelForm should pick up
# the nice form fields defined in FORM_FIELD_OVERRIDES.
class WagtailAdminDraftStateFormMixin:
@property
def show_schedule_publishing_toggle(self):
return "go_live_at" in self.__class__.base_fields
def clean(self):
super().clean()
# Check scheduled publishing fields
go_live_at = self.cleaned_data.get("go_live_at")
expire_at = self.cleaned_data.get("expire_at")
# Go live must be before expire
if go_live_at and expire_at:
if go_live_at > expire_at:
msg = _("Go live date/time must be before expiry date/time")
self.add_error("go_live_at", forms.ValidationError(msg))
self.add_error("expire_at", forms.ValidationError(msg))
# Expire at must be in the future
if expire_at and expire_at < timezone.now():
self.add_error(
"expire_at",
forms.ValidationError(_("Expiry date/time must be in the future")),
)
# Don't allow an existing first_published_at to be unset by clearing the field
if (
"first_published_at" in self.cleaned_data
and not self.cleaned_data["first_published_at"]
):
del self.cleaned_data["first_published_at"]
return self.cleaned_data