mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-05-12 01:03:11 +00:00
isort was disabled to avoid CI noise and merge conflicts following the renaming of Wagtail's module paths. Now that the last major merge of 2.0 has (hopefully) taken place, it's time to reinstate it.
295 lines
10 KiB
Python
295 lines
10 KiB
Python
import itertools
|
|
import json
|
|
from functools import total_ordering
|
|
|
|
from django.conf import settings
|
|
from django.forms import widgets
|
|
from django.forms.utils import flatatt
|
|
from django.template.loader import render_to_string
|
|
from django.urls import reverse
|
|
from django.utils.formats import get_format
|
|
from django.utils.functional import cached_property
|
|
from django.utils.html import format_html
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from taggit.forms import TagWidget
|
|
|
|
from wagtail.admin.datetimepicker import to_datetimepicker_format
|
|
from wagtail.core import hooks
|
|
from wagtail.core.models import Page
|
|
from wagtail.utils.widgets import WidgetWithScript
|
|
|
|
DEFAULT_DATE_FORMAT = '%Y-%m-%d'
|
|
DEFAULT_DATETIME_FORMAT = '%Y-%m-%d %H:%M'
|
|
|
|
|
|
class AdminAutoHeightTextInput(WidgetWithScript, widgets.Textarea):
|
|
def __init__(self, attrs=None):
|
|
# Use more appropriate rows default, given autoheight will alter this anyway
|
|
default_attrs = {'rows': '1'}
|
|
if attrs:
|
|
default_attrs.update(attrs)
|
|
|
|
super().__init__(default_attrs)
|
|
|
|
def render_js_init(self, id_, name, value):
|
|
return 'autosize($("#{0}"));'.format(id_)
|
|
|
|
|
|
class AdminDateInput(WidgetWithScript, widgets.DateInput):
|
|
def __init__(self, attrs=None, format=None):
|
|
fmt = format
|
|
if fmt is None:
|
|
fmt = getattr(settings, 'WAGTAIL_DATE_FORMAT', DEFAULT_DATE_FORMAT)
|
|
self.js_format = to_datetimepicker_format(fmt)
|
|
super().__init__(attrs=attrs, format=fmt)
|
|
|
|
def render_js_init(self, id_, name, value):
|
|
config = {
|
|
'dayOfWeekStart': get_format('FIRST_DAY_OF_WEEK'),
|
|
'format': self.js_format,
|
|
}
|
|
return 'initDateChooser({0}, {1});'.format(
|
|
json.dumps(id_),
|
|
json.dumps(config)
|
|
)
|
|
|
|
|
|
class AdminTimeInput(WidgetWithScript, widgets.TimeInput):
|
|
def __init__(self, attrs=None, format='%H:%M'):
|
|
super().__init__(attrs=attrs, format=format)
|
|
|
|
def render_js_init(self, id_, name, value):
|
|
return 'initTimeChooser({0});'.format(json.dumps(id_))
|
|
|
|
|
|
class AdminDateTimeInput(WidgetWithScript, widgets.DateTimeInput):
|
|
def __init__(self, attrs=None, format=None):
|
|
fmt = format
|
|
if fmt is None:
|
|
fmt = getattr(settings, 'WAGTAIL_DATETIME_FORMAT', DEFAULT_DATETIME_FORMAT)
|
|
self.js_format = to_datetimepicker_format(fmt)
|
|
super().__init__(attrs=attrs, format=fmt)
|
|
|
|
def render_js_init(self, id_, name, value):
|
|
config = {
|
|
'dayOfWeekStart': get_format('FIRST_DAY_OF_WEEK'),
|
|
'format': self.js_format,
|
|
}
|
|
return 'initDateTimeChooser({0}, {1});'.format(
|
|
json.dumps(id_),
|
|
json.dumps(config)
|
|
)
|
|
|
|
|
|
class AdminTagWidget(WidgetWithScript, TagWidget):
|
|
def render_js_init(self, id_, name, value):
|
|
return "initTagField({0}, {1}, {2});".format(
|
|
json.dumps(id_),
|
|
json.dumps(reverse('wagtailadmin_tag_autocomplete')),
|
|
'true' if getattr(settings, 'TAG_SPACES_ALLOWED', True) else 'false',
|
|
)
|
|
|
|
|
|
class AdminChooser(WidgetWithScript, widgets.Input):
|
|
input_type = 'hidden'
|
|
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
|
|
|
|
# when looping over form fields, this one should appear in visible_fields, not hidden_fields
|
|
# despite the underlying input being type="hidden"
|
|
is_hidden = False
|
|
|
|
def get_instance(self, model_class, value):
|
|
# helper method for cleanly turning 'value' into an instance object
|
|
if value is None:
|
|
return None
|
|
|
|
try:
|
|
return model_class.objects.get(pk=value)
|
|
except model_class.DoesNotExist:
|
|
return None
|
|
|
|
def get_instance_and_id(self, model_class, value):
|
|
if value is None:
|
|
return (None, None)
|
|
elif isinstance(value, model_class):
|
|
return (value, value.pk)
|
|
else:
|
|
try:
|
|
return (model_class.objects.get(pk=value), value)
|
|
except model_class.DoesNotExist:
|
|
return (None, None)
|
|
|
|
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 __init__(self, **kwargs):
|
|
# allow choose_one_text / choose_another_text to be overridden per-instance
|
|
if 'choose_one_text' in kwargs:
|
|
self.choose_one_text = kwargs.pop('choose_one_text')
|
|
if 'choose_another_text' in kwargs:
|
|
self.choose_another_text = kwargs.pop('choose_another_text')
|
|
if 'clear_choice_text' in kwargs:
|
|
self.clear_choice_text = kwargs.pop('clear_choice_text')
|
|
if 'link_to_chosen_text' in kwargs:
|
|
self.link_to_chosen_text = kwargs.pop('link_to_chosen_text')
|
|
if 'show_edit_link' in kwargs:
|
|
self.show_edit_link = kwargs.pop('show_edit_link')
|
|
super().__init__(**kwargs)
|
|
|
|
|
|
class AdminPageChooser(AdminChooser):
|
|
choose_one_text = _('Choose a page')
|
|
choose_another_text = _('Choose another page')
|
|
link_to_chosen_text = _('Edit this page')
|
|
|
|
def __init__(self, target_models=None, can_choose_root=False, user_perms=None, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
if target_models:
|
|
models = ', '.join([model._meta.verbose_name.title() for model in target_models if model is not Page])
|
|
if models:
|
|
self.choose_one_text += ' (' + models + ')'
|
|
|
|
self.user_perms = user_perms
|
|
self.target_models = list(target_models or [Page])
|
|
self.can_choose_root = can_choose_root
|
|
|
|
def _get_lowest_common_page_class(self):
|
|
"""
|
|
Return a Page class that is an ancestor for all Page classes in
|
|
``target_models``, and is also a concrete Page class itself.
|
|
"""
|
|
if len(self.target_models) == 1:
|
|
# Shortcut for a single page type
|
|
return self.target_models[0]
|
|
else:
|
|
return Page
|
|
|
|
def render_html(self, name, value, attrs):
|
|
model_class = self._get_lowest_common_page_class()
|
|
|
|
instance, value = self.get_instance_and_id(model_class, value)
|
|
|
|
original_field_html = super().render_html(name, value, attrs)
|
|
|
|
return render_to_string("wagtailadmin/widgets/page_chooser.html", {
|
|
'widget': self,
|
|
'original_field_html': original_field_html,
|
|
'attrs': attrs,
|
|
'value': value,
|
|
'page': instance,
|
|
})
|
|
|
|
def render_js_init(self, id_, name, value):
|
|
if isinstance(value, Page):
|
|
page = value
|
|
else:
|
|
# Value is an ID look up object
|
|
model_class = self._get_lowest_common_page_class()
|
|
page = self.get_instance(model_class, value)
|
|
|
|
parent = page.get_parent() if page else None
|
|
|
|
return "createPageChooser({id}, {model_names}, {parent}, {can_choose_root}, {user_perms});".format(
|
|
id=json.dumps(id_),
|
|
model_names=json.dumps([
|
|
'{app}.{model}'.format(
|
|
app=model._meta.app_label,
|
|
model=model._meta.model_name)
|
|
for model in self.target_models
|
|
]),
|
|
parent=json.dumps(parent.id if parent else None),
|
|
can_choose_root=('true' if self.can_choose_root else 'false'),
|
|
user_perms=json.dumps(self.user_perms),
|
|
)
|
|
|
|
|
|
@total_ordering
|
|
class Button:
|
|
show = True
|
|
|
|
def __init__(self, label, url, classes=set(), attrs={}, priority=1000):
|
|
self.label = label
|
|
self.url = url
|
|
self.classes = classes
|
|
self.attrs = attrs.copy()
|
|
self.priority = priority
|
|
|
|
def render(self):
|
|
attrs = {'href': self.url, 'class': ' '.join(sorted(self.classes))}
|
|
attrs.update(self.attrs)
|
|
return format_html('<a{}>{}</a>', flatatt(attrs), self.label)
|
|
|
|
def __str__(self):
|
|
return self.render()
|
|
|
|
def __repr__(self):
|
|
return '<Button: {}>'.format(self.label)
|
|
|
|
def __lt__(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.classes == other.classes and
|
|
self.attrs == other.attrs and
|
|
self.priority == other.priority)
|
|
|
|
|
|
class PageListingButton(Button):
|
|
def __init__(self, label, url, classes=set(), **kwargs):
|
|
classes = {'button', 'button-small', 'button-secondary'} | set(classes)
|
|
super().__init__(label, url, classes=classes, **kwargs)
|
|
|
|
|
|
class BaseDropdownMenuButton(Button):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, url=None, **kwargs)
|
|
|
|
@cached_property
|
|
def dropdown_buttons(self):
|
|
raise NotImplementedError
|
|
|
|
def render(self):
|
|
return render_to_string(self.template_name, {
|
|
'buttons': self.dropdown_buttons,
|
|
'label': self.label,
|
|
'title': self.attrs.get('title'),
|
|
'is_parent': self.is_parent})
|
|
|
|
|
|
class ButtonWithDropdownFromHook(BaseDropdownMenuButton):
|
|
template_name = 'wagtailadmin/pages/listing/_button_with_dropdown.html'
|
|
|
|
def __init__(self, label, hook_name, page, page_perms, is_parent, **kwargs):
|
|
self.hook_name = hook_name
|
|
self.page = page
|
|
self.page_perms = page_perms
|
|
self.is_parent = is_parent
|
|
|
|
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)
|
|
return sorted(itertools.chain.from_iterable(
|
|
hook(self.page, self.page_perms, self.is_parent)
|
|
for hook in button_hooks))
|