Browse Source

Disable options without translated parent. Add help text. (#7171)

Coen van der Kamp 3 years ago
parent
commit
95693efc10

+ 9 - 0
client/scss/elements/_forms.scss

@@ -44,6 +44,11 @@ label,
         float: none;
     }
 
+    &.disabled {
+        opacity: 0.7;
+        cursor: not-allowed;
+    }
+
     @include media-breakpoint-up(sm) {
         @include column(2);
         padding-top: 1.2em;
@@ -217,6 +222,10 @@ input[type=checkbox]:checked:before {
     color: $color-teal;
 }
 
+input[type=checkbox][disabled]:before {
+    cursor: not-allowed;
+}
+
 
 // Special styles to counteract Firefox's completely unwarranted assumptions about button styles
 input[type=submit],

+ 1 - 1
client/src/components/CommentApp/components/CommentHeader/style.scss

@@ -83,7 +83,7 @@
             > .details-fallback > .details-fallback__summary {  // IE11 uses divs instead with these classes
                 color: #767676;
 
-                 // stylelint-disable-next-line max-nesting-depth
+                // stylelint-disable-next-line max-nesting-depth
                 &:hover {
                     color: $color-grey-25;
                 }

+ 73 - 3
wagtail/contrib/simple_translation/forms.py

@@ -1,9 +1,22 @@
 from django import forms
+from django.urls import reverse
+from django.utils.safestring import mark_safe
 from django.utils.translation import gettext_lazy, ngettext
 
 from wagtail.core.models import Locale, Page
 
 
+class CheckboxSelectMultipleWithDisabledOptions(forms.CheckboxSelectMultiple):
+    option_template_name = "simple_translation/admin/input_option.html"
+    disabled_values = []
+
+    def create_option(self, *args, **kwargs):
+        option = super().create_option(*args, **kwargs)
+        if option["value"] in self.disabled_values:
+            option["attrs"]["disabled"] = True
+        return option
+
+
 class SubmitTranslationForm(forms.Form):
     # Note: We don't actually use select_all in Python, it is just the
     # easiest way to add the widget to the form. It's controlled in JS.
@@ -11,7 +24,7 @@ class SubmitTranslationForm(forms.Form):
     locales = forms.ModelMultipleChoiceField(
         label=gettext_lazy("Locales"),
         queryset=Locale.objects.none(),
-        widget=forms.CheckboxSelectMultiple,
+        widget=CheckboxSelectMultipleWithDisabledOptions,
     )
     include_subtree = forms.BooleanField(
         required=False, help_text=gettext_lazy("All child pages will be created.")
@@ -21,6 +34,7 @@ class SubmitTranslationForm(forms.Form):
         super().__init__(*args, **kwargs)
 
         hide_include_subtree = True
+        self.show_submit = True
 
         if isinstance(instance, Page):
             descendant_count = instance.get_descendants().count()
@@ -36,13 +50,69 @@ class SubmitTranslationForm(forms.Form):
         if hide_include_subtree:
             self.fields["include_subtree"].widget = forms.HiddenInput()
 
-        self.fields["locales"].queryset = Locale.objects.exclude(
+        untranslated_locales = Locale.objects.exclude(
             id__in=instance.get_translations(inclusive=True).values_list(
                 "locale_id", flat=True
             )
         )
+        self.fields["locales"].queryset = untranslated_locales
 
+        # For snippets, hide select all if there is one option.
         # Using len() instead of count() here as we're going to evaluate this queryset
         # anyway and it gets cached so it'll only have one query in the end.
-        if len(self.fields["locales"].queryset) < 2:
+        hide_select_all = len(untranslated_locales) < 2
+
+        if isinstance(instance, Page):
+            parent = instance.get_parent()
+
+            # Find allowed locale options.
+            if parent.is_root():
+                # All locale options are allowed.
+                allowed_locale_ids = Locale.objects.all().values_list("id", flat=True)
+            else:
+                # Only the locale options that have a translated parent are allowed.
+                allowed_locale_ids = (
+                    instance.get_parent()
+                    .get_translations(inclusive=True)
+                    .values_list("locale_id", flat=True)
+                )
+
+            # Get and set the locale options that are disabled.
+            disabled_locales = Locale.objects.exclude(
+                id__in=allowed_locale_ids
+            ).values_list("id", flat=True)
+            self.fields["locales"].widget.disabled_values = disabled_locales
+
+            if disabled_locales:
+                # Display a help text.
+                url = reverse(
+                    "simple_translation:submit_page_translation", args=[parent.id]
+                )
+                help_text = ngettext(
+                    "A locale is disabled because a parent page is not translated.",
+                    "Some locales are disabled because some parent pages are not translated.",
+                    len(disabled_locales),
+                )
+                help_text += "<br>"
+                help_text += '<a href="{}">'.format(url)
+                help_text += ngettext(
+                    "Translate the parent page.",
+                    "Translate the parent pages.",
+                    len(disabled_locales),
+                )
+                help_text += "</a>"
+                self.fields["locales"].help_text = mark_safe(help_text)
+
+            # For pages, if there is one locale or all locales are disabled.
+            hide_select_all = (
+                len(untranslated_locales) == 1
+                or len(untranslated_locales) - len(disabled_locales) == 0
+            )
+
+            # Hide the submit if all untranslated locales are disabled.
+            # This property is used in the template.
+            if len(untranslated_locales) == len(disabled_locales):
+                self.show_submit = False
+
+        if hide_select_all:
             self.fields["select_all"].widget = forms.HiddenInput()

+ 7 - 0
wagtail/contrib/simple_translation/templates/simple_translation/admin/input_option.html

@@ -0,0 +1,7 @@
+{% if widget.wrap_label %}
+    <label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}{% if widget.attrs.disabled %} class="disabled"{% endif %}>
+        {% include "django/forms/widgets/input.html" %} {{ widget.label }}
+    </label>
+{% else %}
+    {% include "django/forms/widgets/input.html" %} {{ widget.label }}
+{% endif %}

+ 8 - 4
wagtail/contrib/simple_translation/templates/simple_translation/admin/submit_translation.html

@@ -21,9 +21,11 @@
                         {% include "wagtailadmin/shared/field_as_li.html" %}
                     {% endfor %}
                 {% endblock %}
-                <li>
-                    <input type="submit" value="{% trans 'Submit' %}" class="button" />
-                </li>
+                {% if form.show_submit %}
+                    <li>
+                        <input type="submit" value="{% trans 'Submit' %}" class="button" />
+                    </li>
+                {% endif %}
             </ul>
         </form>
     </div>
@@ -40,7 +42,9 @@
 
             selectAll.addEventListener('change', function() {
                 for (var i = 0; i < locales.length; i++) {
-                    locales[i].checked = selectAll.checked;
+                    if (locales[i].disabled === false) {
+                        locales[i].checked = selectAll.checked;
+                    }
                 }
             });
         });

+ 54 - 0
wagtail/contrib/simple_translation/tests/test_forms.py

@@ -84,3 +84,57 @@ class TestSubmitPageTranslation(WagtailTestUtils, TestCase):
         form = SubmitTranslationForm(instance=self.en_blog_index)
         # Blog post can be translated to `de` and `fr`.
         self.assertIsInstance(form.fields["select_all"].widget, CheckboxInput)
+
+    def test_locale_disabled(self):
+        form = SubmitTranslationForm(instance=self.en_blog_post)
+        # The parent (blog_index) is translated to English.
+        # German and French are disabled.
+        self.assertEqual(
+            list(form.fields["locales"].widget.disabled_values),
+            [self.de_locale.id, self.fr_locale.id],
+        )
+        label = f"""
+        <label class="disabled">
+            <input type="checkbox" name="None" value="{self.de_locale.id}" disabled>
+            German
+        </label>
+        """
+        self.assertInHTML(label, form.fields["locales"].widget.render(None, None))
+
+    def test_locale_help_text(self):
+        # German and French are disabled.
+        # The help_text is plural
+        form = SubmitTranslationForm(instance=self.en_blog_post)
+        help_text = f"""
+            Some locales are disabled because some parent pages are not translated.
+            <br>
+            <a href="/admin/translation/submit/page/{self.en_blog_index.id}/">
+                Translate the parent pages.
+            </a>
+        """
+        self.assertHTMLEqual(form.fields["locales"].help_text, help_text)
+
+        # Add German translation
+        self.en_blog_index.copy_for_translation(self.de_locale)
+        # French is disabled.
+        # The help_text is singular.
+        form = SubmitTranslationForm(instance=self.en_blog_post)
+        help_text = f"""
+            A locale is disabled because a parent page is not translated.
+            <br>
+            <a href="/admin/translation/submit/page/{self.en_blog_index.id}/">
+                Translate the parent page.
+            </a>
+        """
+        self.assertHTMLEqual(form.fields["locales"].help_text, help_text)
+
+    def test_hide_submit(self):
+        # German and French are disabled.
+        # There are no other pages to be translated.
+        # Submit is hidden.
+        form = SubmitTranslationForm(instance=self.en_blog_post)
+        self.assertFalse(form.show_submit)
+        # A parent is translated
+        self.en_blog_index.copy_for_translation(self.de_locale)
+        form = SubmitTranslationForm(instance=self.en_blog_post)
+        self.assertTrue(form.show_submit)