Repository: digitalfabrik/integreat-cms

Issue:
Improve Side by Side translation view
* One select / dropdown for the target lanuage, source language is automatically implied due to the language tree
* Target language needs tinymce editor
* Copy content from source to target language button
* Automatically preselect currently selected language as target language
Improve Side by Side translation view
* One select / dropdown for the target lanuage, source language is automatically implied due to the language tree
* Target language needs tinymce editor
* Copy content from source to target language button
* Automatically preselect currently selected language as target language


Discussion:

Resolving patch:
diff --git a/src/cms/views/pages/page_sbs_view.py b/src/cms/views/pages/page_sbs_view.py
--- a/src/cms/views/pages/page_sbs_view.py
+++ b/src/cms/views/pages/page_sbs_view.py
@@ -10,6 +10,7 @@
 from django.utils.translation import ugettext as _
 from django.views.generic import TemplateView
 
+from ...constants import status
 from ...decorators import region_permission_required
 from ...forms.pages import PageTranslationForm
 from ...models import Page, Region, Language
@@ -29,22 +30,45 @@ def get(self, request, *args, **kwargs):
         region = Region.objects.get(slug=kwargs.get("region_slug"))
         page = Page.objects.get(pk=kwargs.get("page_id"))
 
-        source_language_code, target_language_code = kwargs.get("language_code").split(
-            "__"
-        )
-        source_language = Language.objects.get(code=source_language_code)
-        target_language = Language.objects.get(code=target_language_code)
+        target_language = Language.objects.get(code=kwargs.get("language_code"))
+        source_language_node = region.language_tree_nodes.get(
+            language=target_language
+        ).parent
+
+        if source_language_node:
+            source_language = source_language_node.language
+        else:
+            messages.error(
+                request,
+                _(
+                    "You cannot use the side-by-side-view for the region's default language (in this case {default_language})."
+                ).format(default_language=target_language.translated_name),
+            )
+            return redirect(
+                "edit_page",
+                **{
+                    "page_id": page.id,
+                    "region_slug": region.slug,
+                    "language_code": target_language.code,
+                }
+            )
 
         source_page_translation = page.get_translation(source_language.code)
         target_page_translation = page.get_translation(target_language.code)
 
         if not source_page_translation:
+            messages.error(
+                request,
+                _(
+                    "You cannot use the side-by-side-view if the source translation (in this case {source_language}) does not exist."
+                ).format(source_language=source_language.translated_name),
+            )
             return redirect(
                 "edit_page",
                 **{
                     "page_id": page.id,
                     "region_slug": region.slug,
-                    "language_code": source_language.code,
+                    "language_code": target_language.code,
                 }
             )
 
@@ -57,7 +81,6 @@ def get(self, request, *args, **kwargs):
                 **self.base_context,
                 "page_translation_form": page_translation_form,
                 "source_page_translation": source_page_translation,
-                "source_language": source_language,
                 "target_language": target_language,
             },
         )
@@ -71,57 +94,80 @@ def post(self, request, *args, **kwargs):
         region = Region.objects.get(slug=kwargs.get("region_slug"))
         page = Page.objects.get(pk=kwargs.get("page_id"))
 
-        source_language_code, target_language_code = kwargs.get("language_code").split(
-            "__"
-        )
-        source_language = Language.objects.get(code=source_language_code)
-        target_language = Language.objects.get(code=target_language_code)
+        target_language = Language.objects.get(code=kwargs.get("language_code"))
+        source_language_node = region.language_tree_nodes.get(
+            language=target_language
+        ).parent
 
-        source_page_translation = page.get_translation(source_language.code)
-        page_translation_instance = page.get_translation(
-            language_code=target_language_code
-        )
+        if source_language_node:
+            source_page_translation = page.get_translation(
+                source_language_node.language.code
+            )
+        else:
+            messages.error(
+                request,
+                _(
+                    "You cannot use the side-by-side-view for the region's default language (in this case {default_language})."
+                ).format(default_language=target_language.translated_name),
+            )
+            return redirect(
+                "edit_page",
+                **{
+                    "page_id": page.id,
+                    "region_slug": region.slug,
+                    "language_code": target_language.code,
+                }
+            )
+
+        page_translation_instance = page.get_translation(target_language.code)
 
         if not source_page_translation:
+            messages.error(
+                request,
+                _(
+                    "You cannot use the side-by-side-view if the source translation (in this case {source_language}) does not exist."
+                ).format(source_language=source_language_node.language.translated_name),
+            )
             return redirect(
                 "edit_page",
                 **{
                     "page_id": page.id,
                     "region_slug": region.slug,
-                    "language_code": source_language.code,
+                    "language_code": target_language.code,
                 }
             )
 
-        if not page_translation_instance:
-            # copy object instance because required status field is missing otherwise
-            page_translation_instance = source_page_translation
-            page_translation_instance.id = None
-            page_translation_instance.language = target_language
-            page_translation_instance.creator = request.user
-            page_translation_instance.public = False
-            page_translation_instance.save()
-            created = True
-        else:
-            created = False
-
         page_translation_form = PageTranslationForm(
-            request.POST, instance=page_translation_instance,
+            request.POST,
+            instance=page_translation_instance,
+            region=region,
+            language=target_language,
         )
-        page_translation_form.fields["status"].required = False
 
-        if page_translation_form.is_valid():
-            page_translation_form.save()
-            if page_translation_form.has_changed():
-                if created:
+        if not page_translation_form.is_valid():
+            messages.error(request, _("Errors have occurred."))
+        elif not page_translation_form.has_changed():
+            messages.info(request, _("No changes detected."))
+        else:
+            page_translation = page_translation_form.save(page=page, user=request.user)
+            published = page_translation.status == status.PUBLIC
+            if not page_translation_instance:
+                if published:
+                    messages.success(
+                        request,
+                        _("Translation was successfully created and published."),
+                    )
+                else:
                     messages.success(
                         request, _("Translation was successfully created.")
                     )
+            else:
+                if published:
+                    messages.success(
+                        request, _("Translation was successfully published.")
+                    )
                 else:
                     messages.success(request, _("Translation was successfully saved."))
-            else:
-                messages.info(request, _("No changes detected."))
-        else:
-            messages.error(request, _("Errors have occurred."))
 
         return render(
             request,
@@ -130,7 +176,6 @@ def post(self, request, *args, **kwargs):
                 **self.base_context,
                 "page_translation_form": page_translation_form,
                 "source_page_translation": source_page_translation,
-                "source_language": source_language,
                 "target_language": target_language,
             },
         )
diff --git a/src/cms/views/pages/page_view.py b/src/cms/views/pages/page_view.py
--- a/src/cms/views/pages/page_view.py
+++ b/src/cms/views/pages/page_view.py
@@ -70,6 +70,10 @@ def get(self, request, *args, **kwargs):
             instance=page_translation, disabled=disabled
         )
 
+        side_by_side_language_options = self.get_side_by_side_language_options(
+            region, language, page
+        )
+
         return render(
             request,
             self.template_name,
@@ -81,6 +85,7 @@ def get(self, request, *args, **kwargs):
                 "language": language,
                 # Languages for tab view
                 "languages": region.languages if page else [language],
+                "side_by_side_language_options": side_by_side_language_options,
             },
         )
 
@@ -115,6 +120,10 @@ def post(self, request, *args, **kwargs):
             if not request.user.has_perm("cms.publish_page", page_instance):
                 raise PermissionDenied
 
+        side_by_side_language_options = self.get_side_by_side_language_options(
+            region, language, page_instance
+        )
+
         # TODO: error handling
         if not page_form.is_valid() or not page_translation_form.is_valid():
             messages.error(request, _("Errors have occurred."))
@@ -129,6 +138,7 @@ def post(self, request, *args, **kwargs):
                     "language": language,
                     # Languages for tab view
                     "languages": region.languages if page_instance else [language],
+                    "side_by_side_language_options": side_by_side_language_options,
                 },
             )
 
@@ -145,6 +155,7 @@ def post(self, request, *args, **kwargs):
                     "language": language,
                     # Languages for tab view
                     "languages": region.languages if page_instance else [language],
+                    "side_by_side_language_options": side_by_side_language_options,
                 },
             )
 
@@ -178,5 +189,41 @@ def post(self, request, *args, **kwargs):
                 "page_id": page.id,
                 "region_slug": region.slug,
                 "language_code": language.code,
-            }
+            },
         )
+
+    @staticmethod
+    def get_side_by_side_language_options(region, language, page):
+        """
+        This is a helper function to generate the side-by-side langauge options for both the get and post requests.
+
+        :param region: The current region
+        :type region: ~cms.models.regions.region.Region
+
+        :param language: The current language
+        :type language: ~cms.models.languages.language.Language
+
+        :param page: The current page
+        :type page: ~cms.models.pages.page.Page
+
+        :return: The list of language options, each represented by a dict
+        :rtype: list
+        """
+        side_by_side_language_options = []
+        for language_node in region.language_tree_nodes.all():
+            if language_node.parent:
+                source_translation = PageTranslation.objects.filter(
+                    page=page, language=language_node.parent.language,
+                )
+                side_by_side_language_options.append(
+                    {
+                        "value": language_node.language.code,
+                        "label": _("{source_language} to {target_language}").format(
+                            source_language=language_node.parent.language.translated_name,
+                            target_language=language_node.language.translated_name,
+                        ),
+                        "selected": language_node.language == language,
+                        "disabled": not source_translation.exists(),
+                    }
+                )
+        return side_by_side_language_options


Test patch: