cms/forms/fields.py
from django import forms
from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
from django.core.validators import EMPTY_VALUES
from django.forms import ChoiceField
from django.utils.translation import gettext_lazy as _
from cms.forms.utils import get_page_choices, get_site_choices
from cms.forms.validators import validate_url_extra
from cms.forms.widgets import PageSelectWidget, PageSmartLinkWidget
from cms.models.pagemodel import Page
from cms.utils.compat import DJANGO_4_2
class SuperLazyIterator:
def __init__(self, func):
self.func = func
def __iter__(self):
return iter(self.func())
class LazyChoiceField(forms.ChoiceField):
@property
def choices(self):
return super().choices()
@choices.setter
def choices(self, value):
# we overwrite this function so no list(value) or normalize_choices(value) is called
# also, do not call the widget's setter as of Django 5
if DJANGO_4_2:
self._choices = self.widget.choices = value
else:
self._choices = self.widget._choices = value
class PageSelectFormField(forms.MultiValueField):
widget = PageSelectWidget
default_error_messages = {
'invalid_site': _('Select a valid site'),
'invalid_page': _('Select a valid page'),
}
def __init__(self, queryset=None, empty_label="---------", cache_choices=False,
required=True, widget=None, to_field_name=None, limit_choices_to=None,
*args, **kwargs):
errors = self.default_error_messages.copy()
if 'error_messages' in kwargs:
errors.update(kwargs['error_messages'])
self.limit_choices_to = limit_choices_to
kwargs['required'] = required
kwargs.pop('blank', None)
fields = (
ChoiceField(choices=get_site_choices, required=False, error_messages={'invalid': errors['invalid_site']}),
ChoiceField(choices=get_page_choices, required=False, error_messages={'invalid': errors['invalid_page']}),
)
super().__init__(fields, *args, **kwargs)
def compress(self, data_list):
if data_list:
page_id = data_list[1]
if page_id in EMPTY_VALUES:
if not self.required:
return None
raise forms.ValidationError(self.error_messages['invalid_page'])
return Page.objects.get(pk=page_id)
return None
def has_changed(self, initial, data):
is_empty = data and (len(data) >= 2 and data[1] in [None, ''])
if isinstance(self.widget, RelatedFieldWidgetWrapper):
self.widget.decompress = self.widget.widget.decompress
if is_empty and initial is None:
# when empty data will have [u'1', u'', u''] as value
# this will cause django to always return True because of the '1'
# so we simply follow django's default behavior when initial is None and data is "empty"
data = ['' for x in range(0, len(data))]
return super().has_changed(initial, data)
def _has_changed(self, initial, data):
return self.has_changed(initial, data)
class PageSmartLinkField(forms.CharField):
widget = PageSmartLinkWidget
default_validators = [validate_url_extra]
def __init__(self, max_length=None, min_length=None, placeholder_text=None,
ajax_view=None, *args, **kwargs):
self.placeholder_text = placeholder_text
widget = self.widget(ajax_view=ajax_view)
super().__init__(max_length=max_length, min_length=min_length,
widget=widget, *args, **kwargs)
def widget_attrs(self, widget):
attrs = super().widget_attrs(widget)
attrs.update({'placeholder_text': self.placeholder_text})
return attrs
def clean(self, value):
value = self.to_python(value).strip()
return super().clean(value)