diff --git a/pontoon/administration/forms.py b/pontoon/administration/forms.py index f8440e615f..b5878bcdea 100644 --- a/pontoon/administration/forms.py +++ b/pontoon/administration/forms.py @@ -123,9 +123,9 @@ def __init__(self, *args, **kwargs): # If the project instance is available, filter resources for this project if kwargs.get("instance") and kwargs["instance"].project: project = kwargs["instance"].project - self.fields["resources"].queryset = Resource.objects.filter( - project=project - ).select_related() + self.fields["resources"].queryset = ( + Resource.objects.current().filter(project=project).select_related() + ) TagInlineFormSet = inlineformset_factory(Project, Tag, form=TagInlineForm, extra=1) diff --git a/pontoon/administration/tests/test_views.py b/pontoon/administration/tests/test_views.py index d846be4b24..c8d61c4d28 100644 --- a/pontoon/administration/tests/test_views.py +++ b/pontoon/administration/tests/test_views.py @@ -380,6 +380,32 @@ def test_manage_project_strings_download_csv(client_superuser): assert "Mächt’ge".encode() in response.content +@pytest.mark.django_db +def test_manage_project_translate_link_excludes_obsolete_resources(client_superuser): + """Test that translate_locale is only set when non-obsolete resources exist.""" + locale_kl = LocaleFactory.create(code="tlh", name="Klingon") + project = ProjectFactory.create( + data_source=Project.DataSource.DATABASE, + locales=[locale_kl], + repositories=[], + ) + + # add obsolete resource + ResourceFactory.create(project=project, obsolete=True) + + url = reverse("pontoon.admin.project", args=(project.slug,)) + response = client_superuser.get(url) + assert response.status_code == 200 + assert "translate_locale" not in response.context + + # add non-obsolete resource + ResourceFactory.create(project=project, obsolete=False) + + response = client_superuser.get(url) + assert response.status_code == 200 + assert response.context["translate_locale"] == "tlh" + + @pytest.mark.django_db def test_project_add_locale(client_superuser): locale_kl = LocaleFactory.create(code="kl", name="Klingon") diff --git a/pontoon/administration/views.py b/pontoon/administration/views.py index e321a79436..6bc96d27e8 100644 --- a/pontoon/administration/views.py +++ b/pontoon/administration/views.py @@ -306,7 +306,7 @@ def manage_project(request, slug=None, template="admin_project.html"): } # Set locale in Translate link - if Resource.objects.filter(project=project).exists() and locales_selected: + if Resource.objects.current().filter(project=project).exists() and locales_selected: locale = ( utils.get_project_locale_from_request(request, project.locales) or locales_selected[0].code @@ -373,7 +373,7 @@ def _get_resource_for_database_project(project): """ try: - return Resource.objects.get( + return Resource.objects.current().get( project=project, ) except Resource.DoesNotExist: @@ -492,7 +492,7 @@ def manage_project_strings(request, slug=None): # Get all strings, find the ones that changed, update them in the database. formset = EntityFormSet(request.POST, queryset=entities) if formset.is_valid(): - resource = Resource.objects.filter(project=project).first() + resource = Resource.objects.current().filter(project=project).first() entity_max_order = entities.aggregate(Max("order"))["order__max"] try: # This line can purposefully cause an exception, and that diff --git a/pontoon/base/models/locale.py b/pontoon/base/models/locale.py index d2cd5cc12c..9eff3c806a 100644 --- a/pontoon/base/models/locale.py +++ b/pontoon/base/models/locale.py @@ -58,17 +58,21 @@ def available(self): from pontoon.base.models.translated_resource import TranslatedResource return self.filter( - pk__in=TranslatedResource.objects.values_list("locale", flat=True) + pk__in=TranslatedResource.objects.current().values_list("locale", flat=True) ) def stats_data(self, project=None): if project is not None: - query = self.filter(translatedresources__resource__project=project) + query = self.filter( + translatedresources__resource__project=project, + translatedresources__resource__obsolete=False, + ) else: query = self.filter( translatedresources__resource__project__disabled=False, translatedresources__resource__project__system_project=False, translatedresources__resource__project__visibility="public", + translatedresources__resource__obsolete=False, ) return query.annotate( @@ -115,7 +119,7 @@ class Locale(models.Model, AggregatedStats): def aggregated_stats_query(self): from pontoon.base.models.translated_resource import TranslatedResource - return TranslatedResource.objects.filter( + return TranslatedResource.objects.current().filter( locale=self, resource__project__disabled=False, resource__project__system_project=False, diff --git a/pontoon/base/models/project.py b/pontoon/base/models/project.py index 22b6d99272..1816a848dc 100644 --- a/pontoon/base/models/project.py +++ b/pontoon/base/models/project.py @@ -119,7 +119,7 @@ class Project(models.Model, AggregatedStats): def aggregated_stats_query(self): from pontoon.base.models.translated_resource import TranslatedResource - return TranslatedResource.objects.filter(resource__project=self) + return TranslatedResource.objects.current().filter(resource__project=self) name = models.CharField(max_length=128, unique=True) slug = models.SlugField(unique=True) diff --git a/pontoon/base/models/project_locale.py b/pontoon/base/models/project_locale.py index 586899f5d6..1d06e4d34f 100644 --- a/pontoon/base/models/project_locale.py +++ b/pontoon/base/models/project_locale.py @@ -32,7 +32,8 @@ def visible(self): def stats_data(self, project=None, locale=None): if project: query = self.filter( - locale__translatedresources__resource__project=project + locale__translatedresources__resource__project=project, + locale__translatedresources__resource__obsolete=False, ).prefetch_related("locale") tr = "locale__translatedresources" elif locale: @@ -41,6 +42,7 @@ def stats_data(self, project=None, locale=None): project__disabled=False, project__system_project=False, project__visibility="public", + project__resources__obsolete=False, ).prefetch_related("project") tr = "project__resources__translatedresources" return query.annotate( @@ -74,7 +76,7 @@ class ProjectLocale(models.Model, AggregatedStats): def aggregated_stats_query(self): from pontoon.base.models.translated_resource import TranslatedResource - return TranslatedResource.objects.filter( + return TranslatedResource.objects.current().filter( locale=self.locale, resource__project=self.project ) diff --git a/pontoon/base/models/resource.py b/pontoon/base/models/resource.py index 593c9cc2bc..f9a80fdaec 100644 --- a/pontoon/base/models/resource.py +++ b/pontoon/base/models/resource.py @@ -3,6 +3,24 @@ from django.utils import timezone +class ResourceQuerySet(models.QuerySet): + def mark_as_obsolete(self, now=None): + from pontoon.base.models.entity import Entity + + if now is None: + now = timezone.now() + + self.update(obsolete=True, date_obsoleted=now) + Entity.objects.filter(resource__in=self).update( + obsolete=True, + date_obsoleted=now, + section=None, + ) + + def current(self): + return self.filter(obsolete=False) + + class Resource(models.Model): project = models.ForeignKey("Project", models.CASCADE, related_name="resources") path = models.TextField() # Path to localization file @@ -42,6 +60,8 @@ class Format(models.TextChoices): deadline = models.DateField(blank=True, null=True) + objects = ResourceQuerySet.as_manager() + # Formats that allow empty translations EMPTY_TRANSLATION_FORMATS = { Format.DTD, diff --git a/pontoon/base/models/translated_resource.py b/pontoon/base/models/translated_resource.py index 6da5999280..ad1e8a0683 100644 --- a/pontoon/base/models/translated_resource.py +++ b/pontoon/base/models/translated_resource.py @@ -14,6 +14,9 @@ class TranslatedResourceQuerySet(models.QuerySet): + def current(self): + return self.filter(resource__obsolete=False) + def string_stats( self, user: User | None = None, diff --git a/pontoon/base/models/translation.py b/pontoon/base/models/translation.py index 453ecc4f4c..d42c3b8789 100644 --- a/pontoon/base/models/translation.py +++ b/pontoon/base/models/translation.py @@ -23,9 +23,11 @@ class TranslationQuerySet(models.QuerySet): def translated_resources(self, locale): from pontoon.base.models.translated_resource import TranslatedResource - return TranslatedResource.objects.filter( - resource__entities__translation__in=self, locale=locale - ).distinct() + return ( + TranslatedResource.objects.current() + .filter(resource__entities__translation__in=self, locale=locale) + .distinct() + ) def authors(self): """ diff --git a/pontoon/base/signals.py b/pontoon/base/signals.py index d32b47ac8b..eff2c61ae0 100644 --- a/pontoon/base/signals.py +++ b/pontoon/base/signals.py @@ -28,7 +28,7 @@ def project_locale_removed(sender, **kwargs): project_locale = kwargs.get("instance", None) if project_locale is not None: - TranslatedResource.objects.filter( + TranslatedResource.objects.current().filter( resource__project=project_locale.project, locale=project_locale.locale ).delete() @@ -173,7 +173,7 @@ def add_locale_to_system_projects(sender, instance, created, **kwargs): projects = Project.objects.filter(system_project=True) for project in projects: ProjectLocale.objects.create(project=project, locale=instance) - for resource in project.resources.all(): + for resource in project.resources.current(): translated_resource = TranslatedResource.objects.create( resource=resource, locale=instance, @@ -189,7 +189,7 @@ def add_locale_to_terminology_project(sender, instance, created, **kwargs): if created: project = Project.objects.get(slug="terminology") ProjectLocale.objects.create(project=project, locale=instance) - for resource in project.resources.all(): + for resource in project.resources.current(): translated_resource = TranslatedResource.objects.create( resource=resource, locale=instance, diff --git a/pontoon/base/tests/managers/test_user.py b/pontoon/base/tests/managers/test_user.py index 612ce1bab6..481d27fa2d 100644 --- a/pontoon/base/tests/managers/test_user.py +++ b/pontoon/base/tests/managers/test_user.py @@ -1,6 +1,9 @@ +from unittest.mock import MagicMock + import pytest from django.db.models import Q +from django.utils import timezone from pontoon.base.utils import aware_datetime from pontoon.contributors.utils import users_with_translations_counts @@ -322,3 +325,66 @@ def test_mgr_user_query_args_filtering( assert top_contribs[0].translations_approved_count == 11 assert top_contribs[0].translations_rejected_count == 0 assert top_contribs[0].translations_unapproved_count == 3 + + +@pytest.mark.django_db +def test_mgr_user_translation_counts_after_resource_removed( + resource_a, + locale_a, +): + """ + Tests that contributor translation counts remain unchanged after + a resource is removed via remove_resources. + + Translation counts should include translations from obsolete resources + since they represent the contributor's historical work. + """ + from pontoon.sync.core.entities import remove_resources + + contributor = UserFactory.create() + entities = EntityFactory.create_batch(size=12, resource=resource_a) + + batch_kwargs = ( + [dict(approved=True)] * 7 + + [dict(approved=False, fuzzy=False, rejected=True)] * 3 + + [dict(fuzzy=True)] * 2 + ) + + for i, kwa in enumerate(batch_kwargs): + TranslationFactory.create( + locale=locale_a, + user=contributor, + entity=entities[i], + approved=kwa.get("approved", False), + rejected=kwa.get("rejected", False), + fuzzy=kwa.get("fuzzy", False), + ) + + top_contribs = users_with_translations_counts() + assert len(top_contribs) == 1 + assert top_contribs[0] == contributor + assert top_contribs[0].translations_count == 12 + assert top_contribs[0].translations_approved_count == 7 + assert top_contribs[0].translations_rejected_count == 3 + assert top_contribs[0].translations_unapproved_count == 2 + + # Remove resource using remove_resources (simulates sync removing source file) + checkout = MagicMock() + checkout.path = "/path_1" + checkout.removed = [resource_a.path] + + paths = MagicMock() + paths.ref_root = "/path_1" + + remove_resources(resource_a.project, paths, checkout, timezone.now()) + + resource_a.refresh_from_db() + assert resource_a.obsolete is True + + top_contribs = users_with_translations_counts() + assert len(top_contribs) == 1 + assert top_contribs[0] == contributor + assert top_contribs[0].translations_count == 12 + assert top_contribs[0].translations_approved_count == 7 + assert top_contribs[0].translations_rejected_count == 3 + assert top_contribs[0].translations_unapproved_count == 2 diff --git a/pontoon/base/views.py b/pontoon/base/views.py index 7001e6abb0..f5efac0444 100755 --- a/pontoon/base/views.py +++ b/pontoon/base/views.py @@ -104,7 +104,11 @@ def locale_projects(request, locale): def locale_stats(request, locale): """Get locale stats used in All Resources part.""" locale = get_object_or_404(Locale, code=locale) - stats = TranslatedResource.objects.filter(locale=locale).string_stats(request.user) + stats = ( + TranslatedResource.objects.current() + .filter(locale=locale) + .string_stats(request.user) + ) stats["title"] = "all-resources" return JsonResponse([stats], safe=False) @@ -116,9 +120,11 @@ def locale_project_parts(request, locale, slug): try: locale = Locale.objects.get(code=locale) project = Project.objects.visible_for(request.user).get(slug=slug) - tr = TranslatedResource.objects.filter( - locale=locale, resource__project=project - ).distinct() + tr = ( + TranslatedResource.objects.current() + .filter(locale=locale, resource__project=project) + .distinct() + ) details = list( tr.annotate( title=F("resource__path"), @@ -192,7 +198,7 @@ def _get_entities_list(locale, preferred_source_locale, project, form): return JsonResponse( { "entities": Entity.map_entities(locale, preferred_source_locale, entities), - "stats": TranslatedResource.objects.query_stats( + "stats": TranslatedResource.objects.current().query_stats( project, form.cleaned_data["paths"], locale ), }, @@ -227,7 +233,7 @@ def _get_paginated_entities(locale, preferred_source_locale, project, form, enti requested_entity=requested_entity, ), "has_next": entities_page.has_next(), - "stats": TranslatedResource.objects.query_stats( + "stats": TranslatedResource.objects.current().query_stats( project, form.cleaned_data["paths"], locale ), }, diff --git a/pontoon/batch/views.py b/pontoon/batch/views.py index 04bbd697eb..0b0c3903a4 100644 --- a/pontoon/batch/views.py +++ b/pontoon/batch/views.py @@ -121,7 +121,7 @@ def batch_edit_translations(request): ) tr_pks = [tr.pk for tr in action_status["translated_resources"]] - TranslatedResource.objects.filter(pk__in=tr_pks).calculate_stats() + TranslatedResource.objects.current().filter(pk__in=tr_pks).calculate_stats() # Mark translations as changed active_translations.bulk_mark_changed() diff --git a/pontoon/insights/tasks.py b/pontoon/insights/tasks.py index fe493d048e..2cf02d7628 100644 --- a/pontoon/insights/tasks.py +++ b/pontoon/insights/tasks.py @@ -272,7 +272,8 @@ def count_projectlocale_stats() -> Iterable[dict[str, int]]: which may need a local copy if the behaviour here is modified. """ return ( - TranslatedResource.objects.filter( + TranslatedResource.objects.current() + .filter( resource__project__disabled=False, resource__project__system_project=False, resource__project__visibility="public", diff --git a/pontoon/localizations/views.py b/pontoon/localizations/views.py index 00b47130ed..0b76f70bcd 100644 --- a/pontoon/localizations/views.py +++ b/pontoon/localizations/views.py @@ -38,7 +38,7 @@ def localization(request, code, slug): get_object_or_404(ProjectLocale, locale=locale, project=project) - trans_res = TranslatedResource.objects.filter( + trans_res = TranslatedResource.objects.current().filter( locale=locale, resource__project=project ) @@ -75,7 +75,8 @@ def ajax_resources(request, code, slug): # Prefetch data needed for the latest activity column translatedresources = ( - TranslatedResource.objects.filter( + TranslatedResource.objects.current() + .filter( resource__project=project, locale=locale, resource__entities__obsolete=False, diff --git a/pontoon/messaging/management/commands/send_deadline_notifications.py b/pontoon/messaging/management/commands/send_deadline_notifications.py index c6c85b9356..a60d58757f 100644 --- a/pontoon/messaging/management/commands/send_deadline_notifications.py +++ b/pontoon/messaging/management/commands/send_deadline_notifications.py @@ -37,10 +37,14 @@ def handle(self, *args, **options): locales = [] for project_locale in project.project_locale.all(): - pl_stats = TranslatedResource.objects.filter( - locale=project_locale.locale, - resource__project=project_locale.project, - ).string_stats() + pl_stats = ( + TranslatedResource.objects.current() + .filter( + locale=project_locale.locale, + resource__project=project_locale.project, + ) + .string_stats() + ) if pl_stats["approved"] < pl_stats["total"]: locales.append(project_locale.locale) diff --git a/pontoon/pretranslation/tasks.py b/pontoon/pretranslation/tasks.py index 118a2af203..838b396247 100644 --- a/pontoon/pretranslation/tasks.py +++ b/pontoon/pretranslation/tasks.py @@ -65,7 +65,8 @@ def pretranslate(project: Project, paths: set[str] | None): # Fetch all available locale-resource pairs (TranslatedResource objects) tr_pairs = ( - TranslatedResource.objects.filter( + TranslatedResource.objects.current() + .filter( resource__project=project, locale__in=locales, ) @@ -196,9 +197,13 @@ def pretranslate(project: Project, paths: set[str] | None): # `operator.ior` is the '|' Python operator, which turns into a logical OR # when used between django ORM query objects. tr_query = reduce(operator.ior, tr_filter) - translatedresources = TranslatedResource.objects.filter(tr_query).annotate( - locale_resource=Concat( - "locale_id", V("-"), "resource_id", output_field=CharField() + translatedresources = ( + TranslatedResource.objects.current() + .filter(tr_query) + .annotate( + locale_resource=Concat( + "locale_id", V("-"), "resource_id", output_field=CharField() + ) ) ) translatedresources.calculate_stats() diff --git a/pontoon/projects/views.py b/pontoon/projects/views.py index c9ec64d885..987accf990 100644 --- a/pontoon/projects/views.py +++ b/pontoon/projects/views.py @@ -39,7 +39,7 @@ def projects(request): "projects/projects.html", { "projects": projects, - "all_projects_stats": TranslatedResource.objects.all().string_stats( + "all_projects_stats": TranslatedResource.objects.current().string_stats( request.user ), "project_stats": project_stats, @@ -57,7 +57,7 @@ def project(request, slug): return project project_locales = project.project_locale - project_tr = TranslatedResource.objects.filter(resource__project=project) + project_tr = TranslatedResource.objects.current().filter(resource__project=project) # Only include filtered teams if provided teams = request.GET.get("teams", "").split(",") diff --git a/pontoon/sync/core/entities.py b/pontoon/sync/core/entities.py index 01ba6b1b06..e70da4de53 100644 --- a/pontoon/sync/core/entities.py +++ b/pontoon/sync/core/entities.py @@ -70,7 +70,7 @@ def sync_resources_from_repo( with transaction.atomic(): renamed_paths = rename_resources(project, paths, checkout) - removed_paths = remove_resources(project, paths, checkout) + removed_paths = remove_resources(project, paths, checkout, now) old_res_added_ent_count, changed_paths = update_resources(project, updates, now) new_res_added_ent_count, _ = add_resources(project, updates, changed_paths, now) update_translated_resources(project, locale_map, paths) @@ -103,7 +103,10 @@ def rename_resources( def remove_resources( - project: Project, paths: L10nConfigPaths | L10nDiscoverPaths, checkout: Checkout + project: Project, + paths: L10nConfigPaths | L10nDiscoverPaths, + checkout: Checkout, + now: datetime, ) -> set[str]: if not checkout.removed: return set() @@ -115,8 +118,7 @@ def remove_resources( ) removed_db_paths = {res.path for res in removed_resources} if removed_db_paths: - # FIXME: https://github.com/mozilla/pontoon/issues/2133 - removed_resources.delete() + removed_resources.mark_as_obsolete(now) rm_count = len(removed_db_paths) str_source_files = "source file" if rm_count == 1 else "source files" log.info( @@ -340,12 +342,13 @@ def update_translated_resources( ) -> None: prev_tr_keys: set[tuple[int, int]] = set( (tr["resource_id"], tr["locale_id"]) - for tr in TranslatedResource.objects.filter(resource__project=project) + for tr in TranslatedResource.objects.current() + .filter(resource__project=project) .values("resource_id", "locale_id") .iterator() ) add_tr: list[TranslatedResource] = [] - for resource in Resource.objects.filter(project=project).iterator(): + for resource in Resource.objects.current().filter(project=project).iterator(): _, locales = paths.target(resource.path) for lc in locales: locale = locale_map.get(lc, None) @@ -370,7 +373,7 @@ def update_translated_resources( del_tr_q = Q() for resource_id, locale_id in prev_tr_keys: del_tr_q |= Q(resource_id=resource_id, locale_id=locale_id) - _, del_dict = TranslatedResource.objects.filter(del_tr_q).delete() + _, del_dict = TranslatedResource.objects.current().filter(del_tr_q).delete() del_count = del_dict.get("base.translatedresource", 0) str_tr = "translated resource" if del_count == 1 else "translated resources" log.info(f"[{project.slug}] Removed {del_count} {str_tr}") diff --git a/pontoon/sync/core/translations_from_repo.py b/pontoon/sync/core/translations_from_repo.py index 2abd2b5c46..a633837df4 100644 --- a/pontoon/sync/core/translations_from_repo.py +++ b/pontoon/sync/core/translations_from_repo.py @@ -115,9 +115,9 @@ def delete_removed_gettext_resources( Translation.objects.filter(entity__resource__project=project).filter( rm_t ).delete() - TranslatedResource.objects.filter(resource__project=project).filter( - rm_tr - ).delete() + TranslatedResource.objects.current().filter( + resource__project=project + ).filter(rm_tr).delete() return count @@ -178,9 +178,9 @@ def find_db_updates( resources: dict[str, Resource] = { res.path: res - for res in Resource.objects.filter( - project=project, path__in=resource_paths - ).iterator() + for res in Resource.objects.current() + .filter(project=project, path__in=resource_paths) + .iterator() } # Exclude translations for which DB & repo already match diff --git a/pontoon/sync/tests/test_entities.py b/pontoon/sync/tests/test_entities.py index 08d01075c0..67b7caa47b 100644 --- a/pontoon/sync/tests/test_entities.py +++ b/pontoon/sync/tests/test_entities.py @@ -12,6 +12,7 @@ from django.utils import timezone from pontoon.base.models import Entity, Project, TranslatedResource +from pontoon.base.models.translation import Translation from pontoon.base.tests import ( EntityFactory, LocaleFactory, @@ -41,7 +42,7 @@ def test_no_changes(): @pytest.mark.django_db -def test_remove_resource(): +def test_resource_obsoletion(): with TemporaryDirectory() as root: # Database setup settings.MEDIA_ROOT = root @@ -51,9 +52,21 @@ def test_remove_resource(): project = ProjectFactory.create( name="test-rm", locales=[locale], repositories=[repo] ) - ResourceFactory.create(project=project, path="a.ftl", format="fluent") - ResourceFactory.create(project=project, path="b.po", format="gettext") + res_a = ResourceFactory.create(project=project, path="a.ftl", format="fluent") + res_b = ResourceFactory.create(project=project, path="b.po", format="gettext") res_c = ResourceFactory.create(project=project, path="c.ftl", format="fluent") + entity_a = EntityFactory.create(resource=res_a, string="Window") + entity_b = EntityFactory.create(resource=res_b, string="Close") + entity_c = EntityFactory.create(resource=res_c, string="Hello") + translation_a = TranslationFactory.create( + entity=entity_a, locale=locale, string="Fenetre" + ) + translation_b = TranslationFactory.create( + entity=entity_b, locale=locale, string="Ferme" + ) + translation_c = TranslationFactory.create( + entity=entity_c, locale=locale, string="Bonjour" + ) # Filesystem setup makedirs(repo.checkout_path) @@ -65,6 +78,12 @@ def test_remove_resource(): }, ) + # check TranslatedResource objects before resource obsoletion + assert { + translated.resource.path + for translated in TranslatedResource.objects.current() + } == {"a.ftl", "b.po", "c.ftl", "common", "playground"} + # Paths setup mock_checkout = Mock( Checkout, @@ -75,13 +94,32 @@ def test_remove_resource(): ) paths = find_paths(project, Checkouts(mock_checkout, mock_checkout)) - # Test + # Test sync_resources_from_repo assert sync_resources_from_repo( project, locale_map, mock_checkout, paths, now ) == (0, set(), {"c.ftl"}) - assert {res.path for res in project.resources.all()} == {"a.ftl", "b.po"} - with pytest.raises(TranslatedResource.DoesNotExist): - TranslatedResource.objects.get(resource=res_c) + assert {res.path: res.obsolete for res in project.resources.all()} == { + "a.ftl": False, + "b.po": False, + "c.ftl": True, + } + assert {res.path for res in project.resources.current()} == { + "a.ftl", + "b.po", + } + assert TranslatedResource.objects.filter(resource=res_c).exists() + assert { + translated.resource.path + for translated in TranslatedResource.objects.current() + } == {"a.ftl", "b.po", "common", "playground"} + assert Entity.objects.filter(pk=entity_c.pk).exists() + + assert ( + Translation.objects.filter( + pk__in=[translation_a.pk, translation_b.pk, translation_c.pk] + ).count() + == 3 + ) @pytest.mark.django_db diff --git a/pontoon/teams/views.py b/pontoon/teams/views.py index dc55355e57..a0db5271e7 100644 --- a/pontoon/teams/views.py +++ b/pontoon/teams/views.py @@ -74,7 +74,7 @@ def teams(request): "teams/teams.html", { "locales": locales, - "all_locales_stats": TranslatedResource.objects.all().string_stats(), + "all_locales_stats": TranslatedResource.objects.current().string_stats(), "locale_stats": locale_stats, "form": form, "top_instances": get_top_instances(locales, locale_stats), @@ -92,8 +92,10 @@ def team(request, locale): if not visible_count: raise Http404 - locale_stats = TranslatedResource.objects.filter(locale=locale).string_stats( - request.user + locale_stats = ( + TranslatedResource.objects.current() + .filter(locale=locale) + .string_stats(request.user) ) return render( diff --git a/pontoon/terminology/models.py b/pontoon/terminology/models.py index 80c1371084..26c6cf5815 100644 --- a/pontoon/terminology/models.py +++ b/pontoon/terminology/models.py @@ -6,12 +6,12 @@ def update_terminology_project_stats(): - resource = Resource.objects.get(project__slug="terminology") + resource = Resource.objects.current().get(project__slug="terminology") resource.total_strings = Entity.objects.filter( resource=resource, obsolete=False ).count() resource.save(update_fields=["total_strings"]) - TranslatedResource.objects.filter(resource=resource).calculate_stats() + TranslatedResource.objects.current().filter(resource=resource).calculate_stats() class TermQuerySet(models.QuerySet): diff --git a/pontoon/translations/views.py b/pontoon/translations/views.py index 8a0c5fecd3..aa3a282ddf 100644 --- a/pontoon/translations/views.py +++ b/pontoon/translations/views.py @@ -26,7 +26,7 @@ def _add_stats(response_data, resource, locale, stats): if stats: paths = [resource.path] if stats == "resource" else [] - response_data["stats"] = TranslatedResource.objects.query_stats( + response_data["stats"] = TranslatedResource.objects.current().query_stats( resource.project, paths, locale )