forked from ungleich-public/dynamicweb
249 lines
9.3 KiB
Python
249 lines
9.3 KiB
Python
|
|
from django.contrib.auth import get_permission_codename
|
||
|
|
from django.contrib.sites.models import Site
|
||
|
|
from django.forms.widgets import MultiWidget, Select, TextInput
|
||
|
|
from django.templatetags.static import static
|
||
|
|
from django.urls import NoReverseMatch, reverse_lazy
|
||
|
|
from django.utils.encoding import force_str
|
||
|
|
from django.utils.html import escape, format_html
|
||
|
|
from django.utils.safestring import mark_safe
|
||
|
|
from django.utils.translation import gettext as _
|
||
|
|
|
||
|
|
from cms.forms.utils import get_page_choices, get_site_choices
|
||
|
|
from cms.models import Page, PageUser
|
||
|
|
from cms.utils.urlutils import admin_reverse, static_with_version
|
||
|
|
|
||
|
|
|
||
|
|
class PageSelectWidget(MultiWidget):
|
||
|
|
"""A widget that allows selecting a page by first selecting a site and then
|
||
|
|
a page on that site in a two-step process.
|
||
|
|
"""
|
||
|
|
template_name = 'cms/widgets/pageselectwidget.html'
|
||
|
|
|
||
|
|
class Media:
|
||
|
|
js = (
|
||
|
|
static_with_version('cms/js/dist/bundle.forms.pageselectwidget.min.js'),
|
||
|
|
)
|
||
|
|
|
||
|
|
def __init__(self, site_choices=None, page_choices=None, attrs=None):
|
||
|
|
if attrs is not None:
|
||
|
|
self.attrs = attrs.copy()
|
||
|
|
else:
|
||
|
|
self.attrs = {}
|
||
|
|
self.choices = []
|
||
|
|
super().__init__((Select, Select, Select), attrs)
|
||
|
|
|
||
|
|
def decompress(self, value):
|
||
|
|
"""
|
||
|
|
receives a page_id in value and returns the site_id and page_id
|
||
|
|
of that page or the current site_id and None if no page_id is given.
|
||
|
|
"""
|
||
|
|
if value:
|
||
|
|
page = Page.objects.get(pk=value)
|
||
|
|
return [page.site_id, page.pk, page.pk]
|
||
|
|
site = Site.objects.first()
|
||
|
|
return [site.pk, None, None]
|
||
|
|
|
||
|
|
def _has_changed(self, initial, data):
|
||
|
|
# THIS IS A COPY OF django.forms.widgets.Widget._has_changed()
|
||
|
|
# (except for the first if statement)
|
||
|
|
|
||
|
|
"""
|
||
|
|
Return True if data differs from initial.
|
||
|
|
"""
|
||
|
|
# For purposes of seeing whether something has changed, None is
|
||
|
|
# the same as an empty string, if the data or initial value we get
|
||
|
|
# is None, replace it w/ ''.
|
||
|
|
if data is None or (len(data) >= 2 and data[1] in [None, '']):
|
||
|
|
data_value = ''
|
||
|
|
else:
|
||
|
|
data_value = data
|
||
|
|
if initial is None:
|
||
|
|
initial_value = ''
|
||
|
|
else:
|
||
|
|
initial_value = initial
|
||
|
|
if force_str(initial_value) != force_str(data_value):
|
||
|
|
return True
|
||
|
|
return False
|
||
|
|
|
||
|
|
def _build_widgets(self):
|
||
|
|
site_choices = get_site_choices()
|
||
|
|
page_choices = get_page_choices()
|
||
|
|
self.site_choices = site_choices
|
||
|
|
self.choices = page_choices
|
||
|
|
self.widgets = (
|
||
|
|
Select(choices=site_choices),
|
||
|
|
Select(choices=[('', '----')]),
|
||
|
|
Select(choices=self.choices, attrs={'style': "display:none;"}),
|
||
|
|
)
|
||
|
|
|
||
|
|
def get_context(self, name, value, attrs):
|
||
|
|
self._build_widgets()
|
||
|
|
context = super().get_context(name, value, attrs)
|
||
|
|
context['widget']['script_data'] = {"name": name}
|
||
|
|
return context
|
||
|
|
|
||
|
|
def format_output(self, rendered_widgets):
|
||
|
|
return ' '.join(rendered_widgets)
|
||
|
|
|
||
|
|
|
||
|
|
class PageSmartLinkWidget(TextInput):
|
||
|
|
"""Presents the user with a Select2 widget to select a page and returns the link to this page as a string."""
|
||
|
|
template_name = 'cms/widgets/pagesmartlinkwidget.html'
|
||
|
|
|
||
|
|
class Media:
|
||
|
|
css = {
|
||
|
|
'screen': (
|
||
|
|
'admin/css/vendor/select2/select2.css',
|
||
|
|
'admin/css/autocomplete.css',
|
||
|
|
),
|
||
|
|
}
|
||
|
|
js = (
|
||
|
|
'admin/js/vendor/jquery/jquery.js',
|
||
|
|
'admin/js/vendor/select2/select2.full.js',
|
||
|
|
'admin/js/jquery.init.js',
|
||
|
|
static_with_version('cms/js/dist/bundle.forms.pagesmartlinkwidget.min.js'),
|
||
|
|
)
|
||
|
|
|
||
|
|
def __init__(self, attrs=None, ajax_view=None):
|
||
|
|
super().__init__(attrs)
|
||
|
|
self.ajax_url = self.get_ajax_url(ajax_view=ajax_view)
|
||
|
|
|
||
|
|
def get_ajax_url(self, ajax_view):
|
||
|
|
try:
|
||
|
|
return reverse_lazy(ajax_view)
|
||
|
|
except NoReverseMatch:
|
||
|
|
raise Exception(
|
||
|
|
'You should provide an ajax_view argument that can be reversed to the PageSmartLinkWidget'
|
||
|
|
)
|
||
|
|
|
||
|
|
def _build_script_data(self, name, value, attrs):
|
||
|
|
return {
|
||
|
|
"id": attrs.get('id', ''),
|
||
|
|
"text": str(attrs.get('placeholder_text', '')),
|
||
|
|
"lang": self.language,
|
||
|
|
"url": force_str(self.ajax_url),
|
||
|
|
}
|
||
|
|
|
||
|
|
def get_context(self, name, value, attrs):
|
||
|
|
context = super().get_context(name, value, attrs)
|
||
|
|
context['widget']['script_data'] = self._build_script_data(name, value, context['widget']['attrs'])
|
||
|
|
return context
|
||
|
|
|
||
|
|
|
||
|
|
class UserSelectAdminWidget(Select):
|
||
|
|
"""Special widget used in page permission inlines, because we have to render
|
||
|
|
an add user (plus) icon, but point it somewhere else - to special user creation
|
||
|
|
view, which is accessible only if user haves "add user" permissions.
|
||
|
|
|
||
|
|
Current user should be assigned to widget in form constructor as an user
|
||
|
|
attribute.
|
||
|
|
"""
|
||
|
|
def render(self, name, value, attrs=None, choices=(), renderer=None):
|
||
|
|
output = [super().render(name, value, attrs, renderer=renderer)]
|
||
|
|
if hasattr(self, 'user') and (
|
||
|
|
self.user.is_superuser or self.user.has_perm(
|
||
|
|
PageUser._meta.app_label + '.' + get_permission_codename('add', PageUser._meta))
|
||
|
|
):
|
||
|
|
# append + icon
|
||
|
|
add_url = admin_reverse('cms_pageuser_add')
|
||
|
|
output.append(format_html(
|
||
|
|
'<a href="{}?_popup=1" class="related-widget-wrapper-link add-related"'
|
||
|
|
' id="add_id_{}" data-popup="yes" title="{}">'
|
||
|
|
'<img src="{}" alt="{}" width="20" height="20"></a>',
|
||
|
|
add_url,
|
||
|
|
name,
|
||
|
|
_('Add another'),
|
||
|
|
static('admin/img/icon-addlink.svg'),
|
||
|
|
_('Add'),
|
||
|
|
))
|
||
|
|
return mark_safe(''.join(output))
|
||
|
|
|
||
|
|
|
||
|
|
class AppHookSelect(Select):
|
||
|
|
|
||
|
|
"""Special widget used for the App Hook selector in the Advanced Settings
|
||
|
|
of the Page Admin. It adds support for a data attribute per option and
|
||
|
|
includes supporting JS into the page.
|
||
|
|
"""
|
||
|
|
|
||
|
|
class Media:
|
||
|
|
js = (
|
||
|
|
static_with_version('cms/js/dist/bundle.forms.apphookselect.min.js'),
|
||
|
|
)
|
||
|
|
|
||
|
|
def __init__(self, attrs=None, choices=(), app_namespaces={}):
|
||
|
|
self.app_namespaces = app_namespaces
|
||
|
|
super().__init__(attrs, choices)
|
||
|
|
|
||
|
|
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
|
||
|
|
option = super().create_option(name, value, label, selected, index, subindex, attrs)
|
||
|
|
if value in self.app_namespaces:
|
||
|
|
option['attrs']['data-namespace'] = escape(self.app_namespaces[value])
|
||
|
|
return option
|
||
|
|
|
||
|
|
def _build_option(self, selected_choices, option_value, option_label):
|
||
|
|
if option_value is None:
|
||
|
|
option_value = ''
|
||
|
|
option_value = force_str(option_value)
|
||
|
|
if option_value in selected_choices:
|
||
|
|
selected_html = mark_safe(' selected="selected"')
|
||
|
|
if not self.allow_multiple_selected:
|
||
|
|
# Only allow for a single selection.
|
||
|
|
selected_choices.remove(option_value)
|
||
|
|
else:
|
||
|
|
selected_html = ''
|
||
|
|
|
||
|
|
if option_value in self.app_namespaces:
|
||
|
|
data_html = mark_safe(' data-namespace="%s"' % escape(self.app_namespaces[option_value]))
|
||
|
|
else:
|
||
|
|
data_html = ''
|
||
|
|
return option_value, selected_html, data_html, force_str(option_label)
|
||
|
|
|
||
|
|
def render_option(self, selected_choices, option_value, option_label):
|
||
|
|
option_data = self._build_option(selected_choices, option_value, option_label)
|
||
|
|
return '<option value="%s"%s%s>%s</option>' % option_data
|
||
|
|
|
||
|
|
|
||
|
|
class ApplicationConfigSelect(Select):
|
||
|
|
"""
|
||
|
|
Special widget -populate by javascript- that shows application configurations
|
||
|
|
depending on selected Apphooks.
|
||
|
|
|
||
|
|
Required data are injected in the page as JSON data that forms.apphookselect.js
|
||
|
|
uses to create the appropriate data structure.
|
||
|
|
|
||
|
|
A stub 'addlink' link is created and filled in with the correct URL by the same
|
||
|
|
javascript.
|
||
|
|
"""
|
||
|
|
template_name = 'cms/widgets/applicationconfigselect.html'
|
||
|
|
|
||
|
|
class Media:
|
||
|
|
js = (
|
||
|
|
static_with_version('cms/js/dist/bundle.forms.apphookselect.min.js'),
|
||
|
|
)
|
||
|
|
|
||
|
|
def __init__(self, attrs=None, choices=(), app_configs={}):
|
||
|
|
self.app_configs = app_configs
|
||
|
|
super().__init__(attrs, choices)
|
||
|
|
|
||
|
|
def _build_script_data(self, name, value, attrs):
|
||
|
|
configs = {
|
||
|
|
str(application): [[str(config.pk), str(config)] for config in cms_app.get_configs()]
|
||
|
|
for application, cms_app in self.app_configs.items()
|
||
|
|
}
|
||
|
|
urls = {
|
||
|
|
str(application): cms_app.get_config_add_url()
|
||
|
|
for application, cms_app in self.app_configs.items()
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
"apphooks_configuration": configs,
|
||
|
|
"apphooks_configuration_url": urls,
|
||
|
|
"apphooks_configuration_value": str(value) if value is not None else "",
|
||
|
|
# apphook_configuration_value needs to correspond to correspond to str(config.pk)
|
||
|
|
}
|
||
|
|
|
||
|
|
def get_context(self, name, value, attrs):
|
||
|
|
context = super().get_context(name, value, attrs)
|
||
|
|
context['widget']['script_data'] = self._build_script_data(name, value, context['widget']['attrs'])
|
||
|
|
return context
|