From b8b76b301c9bd67c6a3bb9f783228ca063866d7f Mon Sep 17 00:00:00 2001 From: alexkiro <1538458+alexkiro@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:25:25 +0200 Subject: [PATCH] Add support for filtering the translations report by status (#830) --- .../tests/test_translations_report.py | 59 ++++++++++++++++++- wagtail_localize/views/report.py | 36 ++++++++++- 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/wagtail_localize/tests/test_translations_report.py b/wagtail_localize/tests/test_translations_report.py index d423fa016..dbd046843 100644 --- a/wagtail_localize/tests/test_translations_report.py +++ b/wagtail_localize/tests/test_translations_report.py @@ -4,7 +4,13 @@ from wagtail.models import Locale, Page from wagtail.test.utils import WagtailTestUtils -from wagtail_localize.models import Translation, TranslationSource +from wagtail_localize.models import ( + String, + StringTranslation, + Translation, + TranslationContext, + TranslationSource, +) from wagtail_localize.test.models import TestSnippet from .utils import make_test_page @@ -40,7 +46,10 @@ def setUp(self): self.en_blog_index = make_test_page(self.en_homepage, title="Blog", slug="blog") self.en_blog_post = make_test_page( - self.en_blog_index, title="Blog post", slug="blog-post" + self.en_blog_index, + title="Blog post", + slug="blog-post", + test_charfield="Test content", ) self.snippet_translation = Translation.objects.create( @@ -67,6 +76,10 @@ def setUp(self): source=TranslationSource.get_or_create_from_instance(self.en_blog_post)[0], target_locale=self.fr_locale, ) + self.test_charfield_context = TranslationContext.objects.get( + path="test_charfield" + ) + self.test_content_string = String.objects.get(data="Test content") def test_get_empty_report(self): Translation.objects.all().delete() @@ -192,3 +205,45 @@ def test_locale_filters_get_proper_choices(self): ("es", "Spanish"), ], ) + + def test_filter_by_waiting_for_translation_true(self): + StringTranslation.objects.create( + translation_of=self.test_content_string, + context=self.test_charfield_context, + locale=self.fr_locale, + data="Contenu de test", + ) + response = self.client.get( + reverse("wagtail_localize:translations_report") + + "?waiting_for_translation=true" + ) + + # These pages don't have any translations, so they should be listed + self.assertIn(self.snippet_translation, response.context["object_list"]) + self.assertIn(self.homepage_translation, response.context["object_list"]) + self.assertIn(self.de_homepage_translation, response.context["object_list"]) + # Blog index page has no translatable strings, so should not be included + self.assertNotIn(self.blog_index_translation, response.context["object_list"]) + # Translation is complete for this page, so should not be included + self.assertNotIn(self.blog_post_translation, response.context["object_list"]) + + def test_filter_by_waiting_for_translation_false(self): + StringTranslation.objects.create( + translation_of=self.test_content_string, + context=self.test_charfield_context, + locale=self.fr_locale, + data="Contenu de test", + ) + response = self.client.get( + reverse("wagtail_localize:translations_report") + + "?waiting_for_translation=false" + ) + + # These pages don't have any translations, so they should not be listed + self.assertNotIn(self.snippet_translation, response.context["object_list"]) + self.assertNotIn(self.homepage_translation, response.context["object_list"]) + self.assertNotIn(self.de_homepage_translation, response.context["object_list"]) + # Blog index page has no translatable strings, so should be included + self.assertIn(self.blog_index_translation, response.context["object_list"]) + # Translation is complete for this page, so should be included + self.assertIn(self.blog_post_translation, response.context["object_list"]) diff --git a/wagtail_localize/views/report.py b/wagtail_localize/views/report.py index da5762215..be916182a 100644 --- a/wagtail_localize/views/report.py +++ b/wagtail_localize/views/report.py @@ -1,6 +1,7 @@ import django_filters from django.contrib.contenttypes.models import ContentType +from django.db.models import Exists, OuterRef from django.utils.text import capfirst from django.utils.translation import gettext_lazy from django_filters.constants import EMPTY_VALUES @@ -12,7 +13,7 @@ from wagtail.coreutils import get_content_languages from wagtail.models import get_translatable_models -from wagtail_localize.models import Translation +from wagtail_localize.models import StringSegment, StringTranslation, Translation class SourceTitleFilter(django_filters.CharFilter): @@ -130,10 +131,19 @@ class TranslationsReportFilterSet(WagtailFilterSet): null_label=gettext_lazy("All"), null_value="all", ) + waiting_for_translation = django_filters.BooleanFilter( + label=gettext_lazy("Waiting for translations"), + ) class Meta: model = Translation - fields = ["content_type", "source_title", "source_locale", "target_locale"] + fields = [ + "content_type", + "source_title", + "source_locale", + "target_locale", + "waiting_for_translation", + ] def _adapt_wagtail_report_attributes(cls): @@ -175,4 +185,24 @@ class TranslationsReportView(ReportView): filterset_class = TranslationsReportFilterSet def get_queryset(self): - return Translation.objects.all() + return Translation.objects.annotate( + # Check to see if there is at least one string segment that is not + # translated. + waiting_for_translation=Exists( + StringSegment.objects.filter(source_id=OuterRef("source_id")) + .annotate( + # Annotate here just to filter in the next subquery, as Django + # doesn't have support for nested OuterRefs. + _target_locale_id=OuterRef("target_locale_id"), + is_translated=Exists( + StringTranslation.objects.filter( + translation_of_id=OuterRef("string_id"), + context_id=OuterRef("context_id"), + locale_id=OuterRef("_target_locale_id"), + has_error=False, + ) + ), + ) + .filter(is_translated=False) + ) + )