Skip to content

Commit

Permalink
fix: create page wizard fails with Asian page titles/unicode slugs (#…
Browse files Browse the repository at this point in the history
…7565)

* Dynamic unihan decoder selection based on page language

* Update test

* fix eslint error

* add eslint exception for new-cap

* Update wizards.py (typo fix)
  • Loading branch information
fsbraun committed Jun 1, 2023
1 parent 8b8b807 commit 0ab640c
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 52 deletions.
54 changes: 47 additions & 7 deletions cms/admin/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.forms.widgets import HiddenInput
from django.template.defaultfilters import slugify
from django.utils.encoding import force_str
from django.utils.translation import gettext
from django.utils.translation import get_language, gettext
from django.utils.translation import gettext_lazy as _

from cms import api
Expand Down Expand Up @@ -44,6 +44,7 @@
get_subordinate_users,
get_user_permission_level,
)
from cms.utils.urlutils import static_with_version
from menus.menu_pool import menu_pool


Expand Down Expand Up @@ -121,14 +122,55 @@ class CopyPermissionForm(forms.Form):
)


class SlugWidget(forms.widgets.TextInput):
"""
Special widget for the slug field that requires Title field to be there.
Adds the js for the slugifying.
"""

def __init__(self, attrs=None):
if attrs is None:
attrs = {}
else:
attrs = attrs.copy()
language = attrs.get("language", get_language())
self.uhd_lang, self.uhd_urls = self.get_unihandecode_settings(language)
attrs["data-decoder"] = self.uhd_lang
super().__init__(attrs)

def get_unihandecode_settings(self, language):
if language[:2] in get_cms_setting('UNIHANDECODE_DECODERS'):
uhd_lang = language[:2]
else:
uhd_lang = get_cms_setting('UNIHANDECODE_DEFAULT_DECODER')
uhd_host = get_cms_setting('UNIHANDECODE_HOST')
uhd_version = get_cms_setting('UNIHANDECODE_VERSION')
if uhd_lang and uhd_host and uhd_version:
uhd_urls = [
f'{uhd_host}unihandecode-{uhd_version}.core.min.js',
f'{uhd_host}unihandecode-{uhd_version}.{uhd_lang}.min.js',
]
else:
uhd_urls = []
return uhd_lang, uhd_urls

@property
def media(self):
js_media = [
'admin/js/urlify.js',
static_with_version('cms/js/dist/bundle.forms.slugwidget.min.js'),
] + self.uhd_urls
return forms.Media(js=js_media)


class BasePageForm(forms.ModelForm):
_user = None
_site = None
_language = None

title = forms.CharField(label=_("Title"), max_length=255, widget=forms.TextInput(),
help_text=_('The default title'))
slug = forms.CharField(label=_("Slug"), max_length=255, widget=forms.TextInput(),
slug = forms.SlugField(label=_("Slug"), max_length=255,
help_text=_('The part of the title that is used in the URL'))
menu_title = forms.CharField(label=_("Menu Title"), widget=forms.TextInput(),
help_text=_('Overwrite what is displayed in the menu'), required=False)
Expand All @@ -144,12 +186,10 @@ class Meta:
model = Page
fields = []

def clean_slug(self):
slug = slugify(self.cleaned_data['slug'])
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['slug'].widget = SlugWidget(attrs={"language": self._language})

if not slug:
raise ValidationError(_("Slug must not be empty."))
return slug


class AddPageForm(BasePageForm):
Expand Down
18 changes: 0 additions & 18 deletions cms/admin/pageadmin.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,22 +354,6 @@ def permissions(self, request, object_id):
raise self._get_404_exception(object_id)
return self.change_view(request, object_id, extra_context={'show_permissions': True, 'title': _("Change Permissions")})

def get_unihandecode_context(self, language):
if language[:2] in get_cms_setting('UNIHANDECODE_DECODERS'):
uhd_lang = language[:2]
else:
uhd_lang = get_cms_setting('UNIHANDECODE_DEFAULT_DECODER')
uhd_host = get_cms_setting('UNIHANDECODE_HOST')
uhd_version = get_cms_setting('UNIHANDECODE_VERSION')
if uhd_lang and uhd_host and uhd_version:
uhd_urls = [
f'{uhd_host}unihandecode-{uhd_version}.core.min.js',
f'{uhd_host}unihandecode-{uhd_version}.{uhd_lang}.min.js',
]
else:
uhd_urls = []
return {'unihandecode_lang': uhd_lang, 'unihandecode_urls': uhd_urls}

def add_view(self, request, form_url='', extra_context=None):
if extra_context is None:
extra_context = {}
Expand All @@ -378,7 +362,6 @@ def add_view(self, request, form_url='', extra_context=None):
extra_context.update({
'language': language,
})
extra_context.update(self.get_unihandecode_context(language))
return super().add_view(request, form_url, extra_context=extra_context)

def change_view(self, request, object_id, form_url='', extra_context=None):
Expand Down Expand Up @@ -411,7 +394,6 @@ def change_view(self, request, object_id, form_url='', extra_context=None):

site = self.get_site(request)
tab_language = get_site_language_from_request(request, site_id=site.pk)
extra_context.update(self.get_unihandecode_context(tab_language))

response = super().change_view(
request, object_id, form_url=form_url, extra_context=extra_context)
Expand Down
36 changes: 17 additions & 19 deletions cms/forms/wizards.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import warnings

from django import forms
from django.core.exceptions import ValidationError
from django.db import transaction
Expand All @@ -6,12 +8,12 @@
from django.utils.translation import gettext_lazy as _

from cms.admin.forms import AddPageForm
from cms.admin.forms import SlugWidget as AdminSlugWidget
from cms.plugin_pool import plugin_pool
from cms.utils import get_current_site, permissions
from cms.utils.conf import get_cms_setting
from cms.utils.page import get_available_slug
from cms.utils.page_permissions import user_can_add_page, user_can_add_subpage
from cms.utils.urlutils import static_with_version

try:
# djangocms_text_ckeditor is not guaranteed to be available
Expand All @@ -21,16 +23,13 @@
text_widget = forms.Textarea


class SlugWidget(forms.widgets.TextInput):
"""
Special widget for the slug field that requires Title field to be there.
Adds the js for the slugifying.
"""
class Media:
js = (
'admin/js/urlify.js',
static_with_version('cms/js/dist/bundle.forms.slugwidget.min.js'),
)
class SlugWidget(AdminSlugWidget):
"""Compatibility shim with deprecation warning:
SlugWidget has moved to cms.admin.forms"""
def __init__(self, *args, **kwargs):
warnings.warn("Import SlugWidget from cms.admin.forms. SlugWidget will be removed from cms.forms.wizards",
DeprecationWarning, stacklevel=2)
super().__init__(*args, **kwargs)


class CreateCMSPageForm(AddPageForm):
Expand Down Expand Up @@ -64,7 +63,6 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].help_text = _("Provide a title for the new page.")
self.fields['slug'].required = False
self.fields['slug'].widget = SlugWidget()
self.fields['slug'].help_text = _("Leave empty for automatic slug, or override as required.")

@staticmethod
Expand Down Expand Up @@ -95,7 +93,12 @@ def clean(self):
if self._errors:
return data

slug = data.get('slug') or slugify(data['title'])
slug = slugify(data.get('slug')) or slugify(data['title'])
if not slug:
data["slug"] = ""
forms.ValidationError({
"slug": [_("Cannot automatically create slug. Please provide one manually.")],
})

parent_node = data.get('parent_node')

Expand All @@ -110,7 +113,7 @@ def clean(self):
data['path'] = '%s/%s' % (base, data['slug']) if base else data['slug']

if not data['slug']:
raise forms.ValidationError("Please provide a valid slug.")
raise forms.ValidationError(_("Please provide a valid slug."))
return data

def clean_parent_node(self):
Expand Down Expand Up @@ -138,11 +141,6 @@ def clean_parent_node(self):
raise ValidationError(message)
return parent_page.node if parent_page else None

def clean_slug(self):
# Don't let the PageAddForm validate this
# on the wizard it is not a required field
return self.cleaned_data['slug']

def get_template(self):
return get_cms_setting('PAGE_WIZARD_DEFAULT_TEMPLATE')

Expand Down
4 changes: 4 additions & 0 deletions cms/static/cms/js/modules/slug.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ module.exports = function addSlugHandlers(title, slug) {
if (slug.val().trim() === '') {
prefill = true;
}
if (window.unihandecode) {
// eslint-disable-next-line new-cap
window.UNIHANDECODER = window.unihandecode.Unihan(slug.data('decoder'));
}

// always bind the title > slug generation and do the validation inside for better ux
title.on('keyup keypress', function() {
Expand Down
7 changes: 0 additions & 7 deletions cms/templates/admin/cms/page/change_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,6 @@ <h2>{% trans 'All permissions' %}</h2>
{{ block.super }}
{% endblock %}

{% for url in unihandecode_urls %}<script src="{{ url }}" type="text/javascript"></script>{% endfor %}
{% if unihandecode_urls %}
<script>
var UNIHANDECODER = unihandecode.Unihan('{{ unihandecode_lang }}');
</script>
{% endif %}

{# JavaScript for prepopulated fields #}
{% prepopulated_fields_js %}

Expand Down
3 changes: 2 additions & 1 deletion cms/tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -885,7 +885,8 @@ def test_form_errors(self):
with self.login_user_context(superuser):
# Invalid slug
response = self.client.post(self.get_admin_url(Page, 'add'), new_page_data)
expected_error = '<ul class="errorlist"><li>Slug must not be empty.</li></ul>'
expected_error = '<ul class="errorlist"><li>Enter a valid “slug” consisting of letters, numbers, ' \
'underscores or hyphens.</li></ul>'
self.assertEqual(response.status_code, 200)
self.assertContains(response, expected_error, html=True)

Expand Down

0 comments on commit 0ab640c

Please sign in to comment.