Initial commit

This commit is contained in:
2024-08-27 20:33:44 +02:00
commit 1f1832267d
14794 changed files with 1599592 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
from wagtail.admin.widgets.auto_height_text import * # NOQA: F403
from wagtail.admin.widgets.boolean_radio_select import * # NOQA: F403
from wagtail.admin.widgets.button import * # NOQA: F403
from wagtail.admin.widgets.chooser import * # NOQA: F403
from wagtail.admin.widgets.datetime import * # NOQA: F403
from wagtail.admin.widgets.filtered_select import * # NOQA: F403
from wagtail.admin.widgets.slug import * # NOQA: F403
from wagtail.admin.widgets.switch import * # NOQA: F403
from wagtail.admin.widgets.tags import * # NOQA: F403

View File

@@ -0,0 +1,20 @@
from django.forms import widgets
class AdminAutoHeightTextInput(widgets.Textarea):
def __init__(self, attrs=None):
# Use more appropriate rows default, given autosize will alter this anyway
default_attrs = {
"rows": 1,
"data-controller": "w-autosize",
}
if attrs:
default_attrs.update(attrs)
# add a w-field__autosize classname
try:
default_attrs["class"] += " w-field__autosize"
except KeyError:
default_attrs["class"] = "w-field__autosize"
super().__init__(default_attrs)

View File

@@ -0,0 +1,40 @@
from django import forms
from django.utils.translation import gettext_lazy as _
class BooleanRadioSelect(forms.RadioSelect):
"""
A radio select widget for boolean fields. Displays as three options; "All", "Yes" and "No".
"""
input_type = "radio"
def __init__(self, attrs=None):
choices = (
("", _("All")),
("true", _("Yes")),
("false", _("No")),
)
super().__init__(attrs, choices)
def format_value(self, value):
try:
return {
True: ["true"],
False: ["false"],
"true": ["true"],
"false": ["false"],
}[value]
except KeyError:
return ""
def value_from_datadict(self, data, files, name):
value = data.get(name)
return {
True: True,
"True": True,
"False": False,
False: False,
"true": True,
"false": False,
}.get(value)

View File

@@ -0,0 +1,252 @@
from warnings import warn
from django.forms.utils import flatatt
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.http import urlencode
from wagtail import hooks
from wagtail.admin.ui.components import Component
from wagtail.coreutils import accepts_kwarg
from wagtail.utils.deprecation import RemovedInWagtail70Warning
class Button(Component):
template_name = "wagtailadmin/shared/button.html"
show = True
label = ""
icon_name = None
url = None
attrs = {}
def __init__(
self, label="", url=None, classname="", icon_name=None, attrs={}, priority=1000
):
if label:
self.label = label
if url:
self.url = url
self.classname = classname
if icon_name:
self.icon_name = icon_name
self.attrs = self.attrs.copy()
self.attrs.update(attrs)
# if a 'title' attribute has been passed, correct that to aria-label
# as that's what will be picked up in renderings that don't use button.render
# directly (e.g. _dropdown_items.html)
if "title" in self.attrs and "aria-label" not in self.attrs:
self.attrs["aria-label"] = self.attrs.pop("title")
self.priority = priority
def get_context_data(self, parent_context):
return {"button": self}
@property
def base_attrs_string(self):
# The set of attributes to be included on all renderings of
# the button, as a string. Does not include the href or class
# attributes (since the classnames intended for the button styling
# should not be applied to dropdown items)
return flatatt(self.attrs)
@property
def aria_label(self):
return self.attrs.get("aria-label", "")
def __repr__(self):
return f"<Button: {self.label}>"
def __lt__(self, other):
if not isinstance(other, Button):
return NotImplemented
return (self.priority, self.label) < (other.priority, other.label)
def __le__(self, other):
if not isinstance(other, Button):
return NotImplemented
return (self.priority, self.label) <= (other.priority, other.label)
def __gt__(self, other):
if not isinstance(other, Button):
return NotImplemented
return (self.priority, self.label) > (other.priority, other.label)
def __ge__(self, other):
if not isinstance(other, Button):
return NotImplemented
return (self.priority, self.label) >= (other.priority, other.label)
def __eq__(self, other):
if not isinstance(other, Button):
return NotImplemented
return (
self.label == other.label
and self.url == other.url
and self.classname == other.classname
and self.attrs == other.attrs
and self.priority == other.priority
)
class HeaderButton(Button):
"""An icon-only button to be displayed after the breadcrumbs in the header."""
def __init__(
self,
label="",
url=None,
classname="",
icon_name=None,
attrs={},
icon_only=False,
**kwargs,
):
classname = f"{classname} w-header-button button".strip()
attrs = attrs.copy()
if icon_only:
controller = f"{attrs.get('data-controller', '')} w-tooltip".strip()
attrs["data-controller"] = controller
attrs["data-w-tooltip-content-value"] = label
attrs["aria-label"] = label
label = ""
super().__init__(
label=label,
url=url,
classname=classname,
icon_name=icon_name,
attrs=attrs,
**kwargs,
)
# Base class for all listing buttons
# This is also used by SnippetListingButton defined in wagtail.snippets.widgets
class ListingButton(Button):
def __init__(self, label="", url=None, classname="", **kwargs):
classname = f"{classname} button button-small button-secondary".strip()
super().__init__(label=label, url=url, classname=classname, **kwargs)
class PageListingButton(ListingButton):
aria_label_format = None
url_name = None
def __init__(self, *args, page=None, next_url=None, attrs={}, user=None, **kwargs):
self.page = page
self.user = user
self.next_url = next_url
attrs = attrs.copy()
if (
self.page
and self.aria_label_format is not None
and "aria-label" not in attrs
):
attrs["aria-label"] = self.aria_label_format % {
"title": self.page.get_admin_display_title()
}
super().__init__(*args, attrs=attrs, **kwargs)
@cached_property
def url(self):
if self.page and self.url_name is not None:
url = reverse(self.url_name, args=[self.page.id])
if self.next_url:
url += "?" + urlencode({"next": self.next_url})
return url
@cached_property
def page_perms(self):
if self.page:
return self.page.permissions_for_user(self.user)
class BaseDropdownMenuButton(Button):
template_name = "wagtailadmin/pages/listing/_button_with_dropdown.html"
def __init__(self, *args, **kwargs):
super().__init__(*args, url=None, **kwargs)
@cached_property
def dropdown_buttons(self):
raise NotImplementedError
def get_context_data(self, parent_context):
return {
"buttons": sorted(self.dropdown_buttons),
"label": self.label,
"title": self.aria_label,
"toggle_classname": self.classname,
"icon_name": self.icon_name,
}
class ButtonWithDropdown(BaseDropdownMenuButton):
def __init__(self, *args, **kwargs):
self.dropdown_buttons = kwargs.pop("buttons", [])
super().__init__(*args, **kwargs)
class ButtonWithDropdownFromHook(BaseDropdownMenuButton):
def __init__(
self,
label,
hook_name,
page,
user=None,
page_perms=None,
next_url=None,
**kwargs,
):
self.hook_name = hook_name
self.page = page
if user is None:
if page_perms is not None:
warn(
"ButtonWithDropdownFromHook should be passed a `user` argument instead of `page_perms`",
category=RemovedInWagtail70Warning,
stacklevel=2,
)
self.user = page_perms.user
else:
raise TypeError("ButtonWithDropdownFromHook requires a `user` argument")
else:
self.user = user
self.next_url = next_url
super().__init__(label, **kwargs)
@property
def show(self):
return bool(self.dropdown_buttons)
@cached_property
def dropdown_buttons(self):
button_hooks = hooks.get_hooks(self.hook_name)
buttons = []
for hook in button_hooks:
if accepts_kwarg(hook, "user"):
buttons.extend(
hook(page=self.page, user=self.user, next_url=self.next_url)
)
else:
# old-style hook that accepts page_perms instead of user
warn(
f"`{self.hook_name}` hook functions should accept a `user` argument instead of `page_perms` -"
f" {hook.__module__}.{hook.__name__} needs to be updated",
category=RemovedInWagtail70Warning,
)
page_perms = self.page.permissions_for_user(self.user)
buttons.extend(hook(self.page, page_perms, self.next_url))
buttons = [b for b in buttons if b.show]
return buttons

View File

@@ -0,0 +1,350 @@
import json
from django import forms
from django.core.exceptions import ImproperlyConfigured
from django.forms import widgets
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from wagtail.admin.admin_url_finder import AdminURLFinder
from wagtail.admin.staticfiles import versioned_static
from wagtail.coreutils import resolve_model_string
from wagtail.models import Page
from wagtail.telepath import register
from wagtail.widget_adapters import WidgetAdapter
class BaseChooser(widgets.Input):
choose_one_text = _("Choose an item")
choose_another_text = _("Choose another item")
clear_choice_text = _("Clear choice")
link_to_chosen_text = _("Edit this item")
show_edit_link = True
show_clear_link = True
template_name = "wagtailadmin/widgets/chooser.html"
display_title_key = (
"title" # key to use for the display title within the value data dict
)
icon = None
classname = None
model = None
js_constructor = "Chooser"
linked_fields = {}
# when looping over form fields, this one should appear in visible_fields, not hidden_fields
# despite the underlying input being type="hidden"
input_type = "hidden"
is_hidden = False
def __init__(self, **kwargs):
# allow attributes to be overridden by kwargs
for var in [
"choose_one_text",
"choose_another_text",
"clear_choice_text",
"link_to_chosen_text",
"show_edit_link",
"show_clear_link",
"icon",
"linked_fields",
]:
if var in kwargs:
setattr(self, var, kwargs.pop(var))
super().__init__(**kwargs)
@cached_property
def model_class(self):
return resolve_model_string(self.model)
def value_from_datadict(self, data, files, name):
# treat the empty string as None
result = super().value_from_datadict(data, files, name)
if result == "":
return None
else:
return result
def get_hidden_input_context(self, name, value, attrs):
"""
Return the context variables required to render the underlying hidden input element
"""
return super().get_context(name, value, attrs)
def render_hidden_input(self, name, value, attrs):
"""Render the HTML for the underlying hidden input element"""
return self._render(
"django/forms/widgets/input.html",
self.get_hidden_input_context(name, value, attrs),
)
def get_chooser_modal_url(self):
return reverse(self.chooser_modal_url_name)
def get_context(self, name, value_data, attrs):
original_field_html = self.render_hidden_input(
name, value_data.get("id"), attrs
)
return {
"widget": self,
"original_field_html": original_field_html,
"attrs": attrs,
"value": bool(
value_data
), # only used by chooser.html to identify blank values
"edit_url": value_data.get("edit_url", ""),
"display_title": value_data.get(self.display_title_key, ""),
"chooser_url": self.get_chooser_modal_url(),
"icon": self.icon,
"classname": self.classname,
}
def render_html(self, name, value_data, attrs):
return render_to_string(
self.template_name,
self.get_context(name, value_data or {}, attrs),
)
def get_instance(self, value):
"""
Given a value passed to this widget for rendering (which may be None, an id, or a model
instance), return a model instance or None
"""
if value is None:
return None
elif isinstance(value, self.model_class):
return value
else: # assume instance ID
try:
return self.model_class.objects.get(pk=value)
except self.model_class.DoesNotExist:
return None
def get_display_title(self, instance):
"""
Return the text to display as the title for this instance
"""
return str(instance)
def get_value_data_from_instance(self, instance):
"""
Given a model instance, return a value that we can pass to both the server-side template
and the client-side rendering code (via telepath) that contains all the information needed
for display. Typically this is a dict of id, title etc; it must be JSON-serialisable.
"""
return {
"id": instance.pk,
"edit_url": AdminURLFinder().get_edit_url(instance),
self.display_title_key: self.get_display_title(instance),
}
def get_value_data(self, value):
"""
Given a value passed to this widget for rendering (which may be None, an id, or a model
instance), return a value that we can pass to both the server-side template and the
client-side rendering code (via telepath) that contains all the information needed
for display. Typically this is a dict of id, title etc; it must be JSON-serialisable.
"""
instance = self.get_instance(value)
if instance:
return self.get_value_data_from_instance(instance)
def render(self, name, value, attrs=None, renderer=None):
# no point trying to come up with sensible semantics for when 'id' is missing from attrs,
# so let's make sure it fails early in the process
try:
id_ = attrs["id"]
except (KeyError, TypeError):
raise TypeError("BaseChooser cannot be rendered without an 'id' attribute")
value_data = self.get_value_data(value)
widget_html = self.render_html(name, value_data, attrs)
js = self.render_js_init(id_, name, value_data)
out = f"{widget_html}<script>{js}</script>"
return mark_safe(out)
@property
def base_js_init_options(self):
"""The set of options to pass to the JS initialiser that are constant every time this widget
instance is rendered (i.e. do not vary based on id / name / value)"""
opts = {
"modalUrl": self.get_chooser_modal_url(),
}
if self.linked_fields:
opts["linkedFields"] = self.linked_fields
return opts
def get_js_init_options(self, id_, name, value_data):
return {**self.base_js_init_options}
def render_js_init(self, id_, name, value_data):
opts = self.get_js_init_options(id_, name, value_data)
return f"new {self.js_constructor}({json.dumps(id_)}, {json.dumps(opts)});"
@cached_property
def media(self):
return forms.Media(
js=[
versioned_static("wagtailadmin/js/chooser-widget.js"),
]
)
class BaseChooserAdapter(WidgetAdapter):
js_constructor = "wagtail.admin.widgets.Chooser"
def js_args(self, widget):
return [
widget.render_html("__NAME__", None, attrs={"id": "__ID__"}),
widget.id_for_label("__ID__"),
widget.base_js_init_options,
]
@cached_property
def media(self):
return forms.Media(
js=[
versioned_static("wagtailadmin/js/chooser-widget-telepath.js"),
]
)
register(BaseChooserAdapter(), BaseChooser)
class AdminPageChooser(BaseChooser):
choose_one_text = _("Choose a page")
choose_another_text = _("Choose another page")
link_to_chosen_text = _("Edit this page")
display_title_key = "display_title"
chooser_modal_url_name = "wagtailadmin_choose_page"
icon = "doc-empty-inverse"
classname = "page-chooser"
js_constructor = "PageChooser"
def __init__(
self, target_models=None, can_choose_root=False, user_perms=None, **kwargs
):
super().__init__(**kwargs)
if target_models:
if not isinstance(target_models, (set, list, tuple)):
# assume we've been passed a single instance; wrap it as a list
target_models = [target_models]
# normalise the list of target models to a list of page classes
cleaned_target_models = []
for model in target_models:
try:
cleaned_target_models.append(resolve_model_string(model))
except (ValueError, LookupError):
raise ImproperlyConfigured(
"Could not resolve %r into a model. "
"Model names should be in the form app_label.model_name"
% (model,)
)
else:
cleaned_target_models = [Page]
if len(cleaned_target_models) == 1 and cleaned_target_models[0] is not Page:
model_name = cleaned_target_models[0]._meta.verbose_name.title()
self.choose_one_text += " (" + model_name + ")"
self.user_perms = user_perms
self.target_models = cleaned_target_models
if len(self.target_models) == 1:
self.model = self.target_models[0]
else:
self.model = Page
self.can_choose_root = bool(can_choose_root)
@property
def model_names(self):
return [
"{app}.{model}".format(
app=model._meta.app_label, model=model._meta.model_name
)
for model in self.target_models
]
@property
def base_js_init_options(self):
# a JSON-serializable representation of the configuration options needed for the
# client-side behaviour of this widget
return {
"modelNames": self.model_names,
"canChooseRoot": self.can_choose_root,
"userPerms": self.user_perms,
**super().base_js_init_options,
}
def get_instance(self, value):
instance = super().get_instance(value)
if instance:
return instance.specific
def get_display_title(self, instance):
return instance.get_admin_display_title()
def get_value_data_from_instance(self, instance):
data = super().get_value_data_from_instance(instance)
parent_page = instance.get_parent()
data["parent_id"] = parent_page.pk if parent_page else None
return data
def get_js_init_options(self, id_, name, value_data):
opts = super().get_js_init_options(id_, name, value_data)
value_data = value_data or {}
parent_id = value_data.get("parent_id")
if parent_id is not None:
opts["parentId"] = parent_id
return opts
@property
def media(self):
return forms.Media(
js=[
versioned_static("wagtailadmin/js/page-chooser-modal.js"),
versioned_static("wagtailadmin/js/page-chooser.js"),
]
)
class PageChooserAdapter(BaseChooserAdapter):
js_constructor = "wagtail.widgets.PageChooser"
@cached_property
def media(self):
return forms.Media(
js=[
versioned_static("wagtailadmin/js/page-chooser-modal.js"),
versioned_static("wagtailadmin/js/page-chooser-telepath.js"),
]
)
class AdminPageMoveChooser(AdminPageChooser):
def __init__(
self, target_models=None, can_choose_root=False, user_perms=None, **kwargs
):
self.pages_to_move = kwargs.pop("pages_to_move", [])
super().__init__(
target_models=target_models,
can_choose_root=can_choose_root,
user_perms=user_perms,
**kwargs,
)
@property
def base_js_init_options(self):
return {
"targetPages": self.pages_to_move,
"matchSubclass": False,
**super().base_js_init_options,
}
register(PageChooserAdapter(), AdminPageChooser)

View File

@@ -0,0 +1,165 @@
import json
from django import forms
from django.conf import settings
from django.forms import widgets
from django.utils.formats import get_format
from wagtail.admin.datetimepicker import to_datetimepicker_format
from wagtail.admin.staticfiles import versioned_static
from wagtail.telepath import register
from wagtail.widget_adapters import WidgetAdapter
DEFAULT_DATE_FORMAT = "%Y-%m-%d"
DEFAULT_DATETIME_FORMAT = "%Y-%m-%d %H:%M"
DEFAULT_TIME_FORMAT = "%H:%M"
class AdminDateInput(widgets.DateInput):
template_name = "wagtailadmin/widgets/date_input.html"
def __init__(self, attrs=None, format=None):
default_attrs = {"autocomplete": "off"}
fmt = format
if attrs:
default_attrs.update(attrs)
if fmt is None:
fmt = getattr(settings, "WAGTAIL_DATE_FORMAT", DEFAULT_DATE_FORMAT)
self.js_format = to_datetimepicker_format(fmt)
super().__init__(attrs=default_attrs, format=fmt)
def get_config(self):
return {
"dayOfWeekStart": get_format("FIRST_DAY_OF_WEEK"),
"format": self.js_format,
}
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
context["widget"]["config_json"] = json.dumps(self.get_config())
return context
@property
def media(self):
return forms.Media(
js=[
versioned_static("wagtailadmin/js/date-time-chooser.js"),
]
)
class AdminDateInputAdapter(WidgetAdapter):
js_constructor = "wagtail.widgets.AdminDateInput"
def js_args(self, widget):
return [
widget.get_config(),
]
register(AdminDateInputAdapter(), AdminDateInput)
class AdminTimeInput(widgets.TimeInput):
template_name = "wagtailadmin/widgets/time_input.html"
def __init__(self, attrs=None, format=None):
default_attrs = {"autocomplete": "off"}
if attrs:
default_attrs.update(attrs)
fmt = format
if fmt is None:
fmt = getattr(settings, "WAGTAIL_TIME_FORMAT", DEFAULT_TIME_FORMAT)
self.js_format = to_datetimepicker_format(fmt)
super().__init__(attrs=default_attrs, format=fmt)
def get_config(self):
return {"format": self.js_format, "formatTime": self.js_format}
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
context["widget"]["config_json"] = json.dumps(self.get_config())
return context
@property
def media(self):
return forms.Media(
js=[
versioned_static("wagtailadmin/js/date-time-chooser.js"),
]
)
class AdminTimeInputAdapter(WidgetAdapter):
js_constructor = "wagtail.widgets.AdminTimeInput"
def js_args(self, widget):
return [
widget.get_config(),
]
register(AdminTimeInputAdapter(), AdminTimeInput)
class AdminDateTimeInput(widgets.DateTimeInput):
template_name = "wagtailadmin/widgets/datetime_input.html"
def __init__(
self,
attrs=None,
format=None,
time_format=None,
js_overlay_parent_selector="body",
):
default_attrs = {"autocomplete": "off"}
fmt = format
if attrs:
default_attrs.update(attrs)
if fmt is None:
fmt = getattr(settings, "WAGTAIL_DATETIME_FORMAT", DEFAULT_DATETIME_FORMAT)
time_fmt = time_format
if time_fmt is None:
time_fmt = getattr(settings, "WAGTAIL_TIME_FORMAT", DEFAULT_TIME_FORMAT)
self.js_format = to_datetimepicker_format(fmt)
self.js_time_format = to_datetimepicker_format(time_fmt)
self.js_overlay_parent_selector = js_overlay_parent_selector
super().__init__(attrs=default_attrs, format=fmt)
def get_config(self):
return {
"dayOfWeekStart": get_format("FIRST_DAY_OF_WEEK"),
"format": self.js_format,
"formatTime": self.js_time_format,
# The parentID option actually takes a CSS selector instead of an ID
"parentID": self.js_overlay_parent_selector,
}
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
context["widget"]["config_json"] = json.dumps(self.get_config())
return context
@property
def media(self):
return forms.Media(
js=[
versioned_static("wagtailadmin/js/date-time-chooser.js"),
]
)
class AdminDateTimeInputAdapter(WidgetAdapter):
js_constructor = "wagtail.widgets.AdminDateTimeInput"
def js_args(self, widget):
return [
widget.get_config(),
]
register(AdminDateTimeInputAdapter(), AdminDateTimeInput)

View File

@@ -0,0 +1,122 @@
from django import forms
from wagtail.admin.staticfiles import versioned_static
class FilteredSelect(forms.Select):
"""
A select box where the options are shown and hidden dynamically in response to another
form field whose HTML `id` is specified in `filter_field`.
The `choices` list accepts entries of the form `(value, label, filter_values)` in addition
to the standard `(value, label)` tuples, where `filter_values` is a list of values;
whenever `filter_field` is set to a non-empty value, only the items with that value in their
`filter_values` list are shown.
filter_field and filter_values are inserted as 'data-' attributes on the rendered HTML, where
they are picked up by the JavaScript behaviour code -
see wagtailadmin/js/filtered-select.js for an example of how these attributes are configured.
"""
def __init__(self, attrs=None, choices=(), filter_field=""):
super().__init__(attrs, choices)
self.filter_field = filter_field
def build_attrs(self, base_attrs, extra_attrs=None):
my_attrs = {
"data-widget": "filtered-select",
"data-filter-field": self.filter_field,
}
if extra_attrs:
my_attrs.update(extra_attrs)
return super().build_attrs(base_attrs, my_attrs)
def optgroups(self, name, value, attrs=None):
# copy of Django's Select.optgroups, modified to accept filter_value as a
# third item in the tuple and expose that as a data-filter-value attribute
# on the final <option>
groups = []
has_selected = False
for index, choice in enumerate(self.choices):
try:
(option_value, option_label, filter_value) = choice
except ValueError:
# *ChoiceField will still output blank options as a 2-tuple,
# so need to handle that too
(option_value, option_label) = choice
filter_value = None
if option_value is None:
option_value = ""
subgroup = []
if isinstance(option_label, (list, tuple)):
# this is an optgroup - we will iterate over the list in the second item of
# the tuple (which has been assigned to option_label)
group_name = option_value
subindex = 0
choices = option_label
else:
# this is a top-level choice; put it in its own group with no name
group_name = None
subindex = None
choices = [(option_value, option_label, filter_value)]
groups.append((group_name, subgroup, index))
for choice in choices:
try:
(subvalue, sublabel, filter_value) = choice
except ValueError:
(subvalue, sublabel) = choice
filter_value = None
selected = str(subvalue) in value and (
not has_selected or self.allow_multiple_selected
)
has_selected |= selected
subgroup.append(
self.create_option(
name,
subvalue,
sublabel,
selected,
index,
subindex=subindex,
filter_value=filter_value,
)
)
if subindex is not None:
subindex += 1
return groups
def create_option(
self,
name,
value,
label,
selected,
index,
subindex=None,
attrs=None,
filter_value=None,
):
option = super().create_option(
name, value, label, selected, index, subindex=subindex, attrs=attrs
)
if filter_value is not None:
option["attrs"]["data-filter-value"] = ",".join(
[str(val) for val in filter_value]
)
return option
@property
def media(self):
return forms.Media(
js=[
versioned_static("wagtailadmin/js/filtered-select.js"),
]
)

View File

@@ -0,0 +1,24 @@
from django.conf import settings
from django.forms import widgets
class SlugInput(widgets.TextInput):
"""
Associates the input field with the Stimulus w-slug (SlugController).
Slugifies content based on `WAGTAIL_ALLOW_UNICODE_SLUGS` and supports
fields syncing their value to this field (see `TitleFieldPanel`) if
also used.
"""
def __init__(self, attrs=None):
default_attrs = {
"data-controller": "w-slug",
"data-action": "blur->w-slug#slugify w-sync:check->w-slug#compare w-sync:apply->w-slug#urlify:prevent",
"data-w-slug-allow-unicode-value": getattr(
settings, "WAGTAIL_ALLOW_UNICODE_SLUGS", True
),
"data-w-slug-compare-as-param": "urlify",
}
if attrs:
default_attrs.update(attrs)
super().__init__(default_attrs)

View File

@@ -0,0 +1,5 @@
from django.forms import widgets
class SwitchInput(widgets.CheckboxInput):
template_name = "wagtailadmin/widgets/switch.html"

View File

@@ -0,0 +1,60 @@
import json
from django.conf import settings
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from taggit.forms import TagWidget
from taggit.models import Tag
class AdminTagWidget(TagWidget):
template_name = "wagtailadmin/widgets/tag_widget.html"
def __init__(self, *args, **kwargs):
self.tag_model = kwargs.pop("tag_model", Tag)
# free_tagging = None means defer to the tag model's setting
self.free_tagging = kwargs.pop("free_tagging", None)
default_attrs = {"data-controller": "w-tag"}
attrs = kwargs.get("attrs")
if attrs:
default_attrs.update(attrs)
kwargs["attrs"] = default_attrs
super().__init__(*args, **kwargs)
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
if self.tag_model == Tag:
autocomplete_url = reverse("wagtailadmin_tag_autocomplete")
else:
autocomplete_url = reverse(
"wagtailadmin_tag_model_autocomplete",
args=(self.tag_model._meta.app_label, self.tag_model._meta.model_name),
)
if self.free_tagging is None:
free_tagging = getattr(self.tag_model, "free_tagging", True)
else:
free_tagging = self.free_tagging
tag_spaces_allowed = getattr(settings, "TAG_SPACES_ALLOWED", True)
if tag_spaces_allowed:
help_text = _(
'Multi-word tags with spaces will automatically be enclosed in double quotes (").'
)
else:
help_text = _("Tags can only consist of a single word, no spaces allowed.")
context["widget"]["help_text"] = help_text
context["widget"]["attrs"]["data-w-tag-url-value"] = autocomplete_url
context["widget"]["attrs"]["data-w-tag-options-value"] = json.dumps(
{
"allowSpaces": getattr(settings, "TAG_SPACES_ALLOWED", True),
"tagLimit": getattr(settings, "TAG_LIMIT", None),
"autocompleteOnly": not free_tagging,
}
)
return context

View File

@@ -0,0 +1,30 @@
import json
from django import forms
from django.utils.translation import gettext_lazy as _
from wagtail.admin.staticfiles import versioned_static
from wagtail.admin.widgets import BaseChooser
from wagtail.models import Task
class AdminTaskChooser(BaseChooser):
choose_one_text = _("Choose a task")
choose_another_text = _("Choose another task")
link_to_chosen_text = _("Edit this task")
model = Task
icon = "thumbtack"
chooser_modal_url_name = "wagtailadmin_workflows:task_chooser"
classname = "task-chooser"
def render_js_init(self, id_, name, value_data):
return f"createTaskChooser({json.dumps(id_)});"
@property
def media(self):
return forms.Media(
js=[
versioned_static("wagtailadmin/js/task-chooser-modal.js"),
versioned_static("wagtailadmin/js/task-chooser.js"),
]
)