Skip to content

Commit

Permalink
Fix Bug 1578057: update locale's latest_translation for visible proje…
Browse files Browse the repository at this point in the history
…cts only (#1366)

Also:
- reduce DB request count by up to 13
- split test_translation_save_latest_update to individual scenarios
  • Loading branch information
MikkCZ authored and mathjazz committed Sep 4, 2019
1 parent 24ce102 commit dd8c40b
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 99 deletions.
34 changes: 20 additions & 14 deletions pontoon/base/models.py
Expand Up @@ -1446,9 +1446,9 @@ def get_latest_activity(cls, self, extra=None):
or combination of both.
:param self: object to get data for,
instance of Projet or Locale
instance of Project or Locale
:param extra: extra filter to be used,
instance of Projet or Locale
instance of Project or Locale
"""
latest_translation = None

Expand Down Expand Up @@ -1477,9 +1477,9 @@ def get_chart(cls, self, extra=None):
Get chart for project, locale or combination of both.
:param self: object to get data for,
instance of Projet or Locale
instance of Project or Locale
:param extra: extra filter to be used,
instance of Projet or Locale
instance of Project or Locale
"""
chart = None

Expand Down Expand Up @@ -2868,19 +2868,25 @@ def update_latest_translation(self):
resource = self.entity.resource
project = resource.project
locale = self.locale
translatedresource = TranslatedResource.objects.get(resource=resource, locale=locale)

instances = [project, locale, translatedresource]
to_update = [
(TranslatedResource, Q(Q(resource=resource) & Q(locale=locale))),
(ProjectLocale, Q(Q(project=project) & Q(locale=locale))),
(Project, Q(pk=project.pk)),
]

project_locale = utils.get_object_or_none(ProjectLocale, project=project, locale=locale)
if project_locale:
instances.append(project_locale)
if not project.system_project:
to_update.append((Locale, Q(pk=locale.pk)))

for instance in instances:
latest = instance.latest_translation
if latest is None or self.latest_activity['date'] > latest.latest_activity['date']:
instance.latest_translation = self
instance.save(update_fields=['latest_translation'])
for model, query in to_update:
model.objects.filter(
Q(
query & Q(
Q(latest_translation=None) |
Q(latest_translation__date__lt=self.latest_activity['date'])
)
)
).update(latest_translation=self)

def approve(self, user):
"""
Expand Down
6 changes: 4 additions & 2 deletions pontoon/base/tests/models/test_entity.py
Expand Up @@ -72,12 +72,14 @@ def translation_b(translation_a):
This fixture provides a secondary translation
for translation_a's entity.
"""

return TranslationFactory(
translation_b = TranslationFactory(
entity=translation_a.entity,
locale=translation_a.locale,
string="Translation B for entity_a",
)
translation_b.locale.refresh_from_db()
translation_b.entity.resource.project.refresh_from_db()
return translation_b


@pytest.mark.django_db
Expand Down
227 changes: 148 additions & 79 deletions pontoon/base/tests/models/test_translation.py
Expand Up @@ -16,133 +16,214 @@


@pytest.mark.django_db
def test_translation_save_latest_update(locale_a, project_a):
def test_translation_save_latest_update_first_translation(
locale_a, project_a, project_locale_a, resource_a, entity_a
):
"""
When a translation is saved, update the latest_translation
attribute on the related project, locale, translatedresource,
and project_locale objects.
"""
project_locale = ProjectLocaleFactory.create(
project=project_a, locale=locale_a,
)
resource = ResourceFactory.create(
project=project_a,
path="resource.po",
format="po",
)
tr = TranslatedResourceFactory.create(locale=locale_a, resource=resource)
entity = EntityFactory.create(resource=resource, string="Entity X")
tr = TranslatedResourceFactory.create(locale=locale_a, resource=resource_a)

assert locale_a.latest_translation is None
assert project_a.latest_translation is None
assert tr.latest_translation is None
assert project_locale.latest_translation is None
assert project_locale_a.latest_translation is None

translation = TranslationFactory.create(
locale=locale_a,
entity=entity,
entity=entity_a,
date=aware_datetime(1970, 1, 1),
)
locale_a.refresh_from_db()
project_a.refresh_from_db()
tr.refresh_from_db()
project_locale.refresh_from_db()
for i in [locale_a, project_a, project_locale_a, tr]:
i.refresh_from_db()
assert locale_a.latest_translation == translation
assert project_a.latest_translation == translation
assert tr.latest_translation == translation
assert project_locale.latest_translation == translation
assert project_locale_a.latest_translation == translation


@pytest.mark.django_db
def test_translation_save_latest_update_newer_translation(
locale_a, project_a, project_locale_a, resource_a, entity_a
):
"""
When a newer translation is saved, update the latest_translation
attribute on the related project, locale, translatedresource,
and project_locale objects.
"""
tr = TranslatedResourceFactory.create(locale=locale_a, resource=resource_a)

translation = TranslationFactory.create(
locale=locale_a,
entity=entity_a,
date=aware_datetime(1970, 1, 1),
)
for i in [locale_a, project_a, project_locale_a, tr]:
i.refresh_from_db()
assert locale_a.latest_translation == translation
assert project_a.latest_translation == translation
assert tr.latest_translation == translation
assert project_locale_a.latest_translation == translation

# Ensure translation is replaced for newer translations
newer_translation = TranslationFactory.create(
locale=locale_a,
entity=entity,
entity=entity_a,
date=aware_datetime(1970, 2, 1),
)
locale_a.refresh_from_db()
project_a.refresh_from_db()
tr.refresh_from_db()
project_locale.refresh_from_db()
for i in [locale_a, project_a, project_locale_a, tr]:
i.refresh_from_db()
assert locale_a.latest_translation == newer_translation
assert project_a.latest_translation == newer_translation
assert tr.latest_translation == newer_translation
assert project_locale.latest_translation == newer_translation
assert project_locale_a.latest_translation == newer_translation


# Ensure translation isn't replaced for older translations.
@pytest.mark.django_db
def test_translation_save_latest_update_older_translation(
locale_a, project_a, project_locale_a, resource_a, entity_a
):
"""
When an older translation is saved, do not update the latest_translation
attribute on the related project, locale, translatedresource,
and project_locale objects.
"""
tr = TranslatedResourceFactory.create(locale=locale_a, resource=resource_a)

translation = TranslationFactory.create(
locale=locale_a,
entity=entity_a,
date=aware_datetime(1970, 2, 1),
)
for i in [locale_a, project_a, project_locale_a, tr]:
i.refresh_from_db()
assert locale_a.latest_translation == translation
assert project_a.latest_translation == translation
assert tr.latest_translation == translation
assert project_locale_a.latest_translation == translation

# older translation
TranslationFactory.create(
locale=locale_a,
entity=entity,
date=aware_datetime(1970, 1, 5),
entity=entity_a,
date=aware_datetime(1970, 1, 1),
)
locale_a.refresh_from_db()
project_a.refresh_from_db()
tr.refresh_from_db()
project_locale.refresh_from_db()
assert locale_a.latest_translation == newer_translation
assert project_a.latest_translation == newer_translation
assert tr.latest_translation == newer_translation
assert project_locale.latest_translation == newer_translation
for i in [locale_a, project_a, project_locale_a, tr]:
i.refresh_from_db()
assert locale_a.latest_translation == translation
assert project_a.latest_translation == translation
assert tr.latest_translation == translation
assert project_locale_a.latest_translation == translation


# Ensure approved_date is taken into consideration as well.
newer_approved_translation = TranslationFactory.create(
@pytest.mark.django_db
def test_translation_save_latest_update_approved_translation(
locale_a, project_a, project_locale_a, resource_a, entity_a
):
"""
When a translation is approved, update the latest_translation
attribute on the related project, locale, translatedresource,
and project_locale objects if it was approved later than the last
translation was saved before.
"""
tr = TranslatedResourceFactory.create(locale=locale_a, resource=resource_a)

translation = TranslationFactory.create(
locale=locale_a,
entity=entity,
entity=entity_a,
date=aware_datetime(1970, 2, 1),
approved_date=aware_datetime(1970, 2, 1),
)
for i in [locale_a, project_a, project_locale_a, tr]:
i.refresh_from_db()
assert locale_a.latest_translation == translation
assert project_a.latest_translation == translation
assert tr.latest_translation == translation
assert project_locale_a.latest_translation == translation

later_approved_translation = TranslationFactory.create(
locale=locale_a,
entity=entity_a,
date=aware_datetime(1970, 1, 1),
approved_date=aware_datetime(1970, 3, 1),
)
locale_a.refresh_from_db()
project_a.refresh_from_db()
tr.refresh_from_db()
project_locale.refresh_from_db()
assert locale_a.latest_translation == newer_approved_translation
assert project_a.latest_translation == newer_approved_translation
assert tr.latest_translation == newer_approved_translation
assert project_locale.latest_translation == newer_approved_translation
for i in [locale_a, project_a, project_locale_a, tr]:
i.refresh_from_db()
assert locale_a.latest_translation == later_approved_translation
assert project_a.latest_translation == later_approved_translation
assert tr.latest_translation == later_approved_translation
assert project_locale_a.latest_translation == later_approved_translation


@pytest.mark.django_db
def test_translation_save_latest_missing_project_locale(locale_a, project_a):
def test_translation_save_latest_update_for_system_project(locale_a, system_project_a):
"""
If a translation is saved for a locale that isn't active on the
project, do not fail due to a missing ProjectLocale.
When a translation is saved for a system project, update the latest_translation
attribute on the project, translatedresource and project_locale objects,
but not on the locale object.
"""
project_locale = ProjectLocaleFactory.create(
project=system_project_a, locale=locale_a,
)
resource = ResourceFactory.create(
project=project_a,
project=system_project_a,
path="resource.po",
format="po",
)
tr = TranslatedResourceFactory.create(locale=locale_a, resource=resource)
entity = EntityFactory.create(resource=resource, string="Entity X")

assert locale_a.latest_translation is None
assert system_project_a.latest_translation is None
assert tr.latest_translation is None
assert project_locale.latest_translation is None

translation = TranslationFactory.create(
locale=locale_a,
entity=entity,
date=aware_datetime(1970, 1, 1),
)
for i in [locale_a, system_project_a, project_locale, tr]:
i.refresh_from_db()
assert locale_a.latest_translation is None
assert system_project_a.latest_translation == translation
assert tr.latest_translation == translation
assert project_locale.latest_translation == translation


@pytest.mark.django_db
def test_translation_save_latest_missing_project_locale(locale_a, project_a, resource_a, entity_a):
"""
If a translation is saved for a locale that isn't active on the
project, do not fail due to a missing ProjectLocale.
"""
tr = TranslatedResourceFactory.create(locale=locale_a, resource=resource_a)

# This calls .save, this should fail if we're not properly
# handling the missing ProjectLocale.
translation = TranslationFactory.create(
locale=locale_a,
entity=entity,
entity=entity_a,
date=aware_datetime(1970, 1, 1),
)

locale_a.refresh_from_db()
project_a.refresh_from_db()
tr.refresh_from_db()
for i in [locale_a, project_a, tr]:
i.refresh_from_db()
assert locale_a.latest_translation == translation
assert project_a.latest_translation == translation
assert tr.latest_translation == translation


@pytest.mark.django_db
def test_translation_approved_in_tm(locale_a, project_a):
def test_translation_approved_in_tm(locale_a, entity_a):
"""
Every save of approved translation should generate a new
entry in the translation memory.
"""
resource = ResourceFactory.create(
project=project_a,
path="resource.po",
format="po",
)
entity = EntityFactory.create(resource=resource, string="Entity X")
translation = TranslationFactory.create(
locale=locale_a,
entity=entity,
entity=entity_a,
approved=True,
)
assert TranslationMemoryEntry.objects.get(
Expand All @@ -153,19 +234,13 @@ def test_translation_approved_in_tm(locale_a, project_a):


@pytest.mark.django_db
def test_translation_unapproved_not_in_tm(locale_a, project_a):
def test_translation_unapproved_not_in_tm(locale_a, entity_a):
"""
Unapproved translation shouldn't be in the translation memory.
"""
resource = ResourceFactory.create(
project=project_a,
path="resource.po",
format="po",
)
entity = EntityFactory.create(resource=resource, string="Entity X")
translation = TranslationFactory.create(
locale=locale_a,
entity=entity,
entity=entity_a,
)
with pytest.raises(TranslationMemoryEntry.DoesNotExist):
TranslationMemoryEntry.objects.get(
Expand All @@ -176,20 +251,14 @@ def test_translation_unapproved_not_in_tm(locale_a, project_a):


@pytest.mark.django_db
def test_translation_rejected_not_in_tm(locale_a, project_a):
def test_translation_rejected_not_in_tm(locale_a, entity_a):
"""
When translation is deleted, its corresponding TranslationMemoryEntry
needs to be deleted, too.
"""
resource = ResourceFactory.create(
project=project_a,
path="resource.po",
format="po",
)
entity = EntityFactory.create(resource=resource, string="Entity X")
translation = TranslationFactory.create(
locale=locale_a,
entity=entity,
entity=entity_a,
rejected=True,
)
with pytest.raises(TranslationMemoryEntry.DoesNotExist):
Expand Down

0 comments on commit dd8c40b

Please sign in to comment.