Repository: mozilla/pontoon

Issue:
Can't save term translation after updating the term's text in DB
I've changed the term's text to `Review checker`, since that's the correct capitalization in English (changed from the original)
https://pontoon.mozilla.org/a/terminology/term/666/change/

To my surprise, the term showed up as untranslated for Italian. The other problem is that I cannot save the translation: https://pontoon.mozilla.org/it/terminology/all-resources/?status=missing&string=301053

Error in log is 

```
Internal Server Error: /translations/create/
    response = get_response(request)
  File "/app/.heroku/python/lib/python3.9/site-packages/django/core/handlers/base.py", line 181, in _get_response
    return func(request, *args, **kwargs)
  File "/app/pontoon/base/utils.py", line 126, in wrap
    return f(request, *args, **kwargs)
  File "/app/.heroku/python/lib/python3.9/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "/app/.heroku/python/lib/python3.9/contextlib.py", line 79, in inner
  File "/app/pontoon/translations/views.py", line 117, in create_translation
    translation.save(failed_checks=failed_checks)
  File "/app/pontoon/base/models.py", line 3429, in save
    self.entity.reset_term_translation(self.locale)
  File "/app/pontoon/base/models.py", line 2865, in reset_term_translation
    term = self.term
pontoon.base.models.Entity.term.RelatedObjectDoesNotExist: Entity has no term.
```
Can't save term translation after updating the term's text in DB
I've changed the term's text to `Review checker`, since that's the correct capitalization in English (changed from the original)
https://pontoon.mozilla.org/a/terminology/term/666/change/

To my surprise, the term showed up as untranslated for Italian. The other problem is that I cannot save the translation: https://pontoon.mozilla.org/it/terminology/all-resources/?status=missing&string=301053

Error in log is 

```
Internal Server Error: /translations/create/
    response = get_response(request)
  File "/app/.heroku/python/lib/python3.9/site-packages/django/core/handlers/base.py", line 181, in _get_response
    return func(request, *args, **kwargs)
  File "/app/pontoon/base/utils.py", line 126, in wrap
    return f(request, *args, **kwargs)
  File "/app/.heroku/python/lib/python3.9/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "/app/.heroku/python/lib/python3.9/contextlib.py", line 79, in inner
  File "/app/pontoon/translations/views.py", line 117, in create_translation
    translation.save(failed_checks=failed_checks)
  File "/app/pontoon/base/models.py", line 3429, in save
    self.entity.reset_term_translation(self.locale)
  File "/app/pontoon/base/models.py", line 2865, in reset_term_translation
    term = self.term
pontoon.base.models.Entity.term.RelatedObjectDoesNotExist: Entity has no term.
```


Discussion:
> To my surprise, the term showed up as untranslated for Italian. 

That's what the spec says:
https://github.com/mozilla/pontoon/blob/main/specs/0102-terminology-translation.md#synchronization-between-translation-project-and-data-model

See: _When a `Term` instance is updated:_ section.

> The other problem is that I cannot save the translation: https://pontoon.mozilla.org/it/terminology/all-resources/?status=missing&string=301053

The old (now obsolete) entity remains assigned to the `Term` rather than the newly created one. I fixed it manually for this particular case, so it can be translated.
> To my surprise, the term showed up as untranslated for Italian. 

That's what the spec says:
https://github.com/mozilla/pontoon/blob/main/specs/0102-terminology-translation.md#synchronization-between-translation-project-and-data-model

See: _When a `Term` instance is updated:_ section.

> The other problem is that I cannot save the translation: https://pontoon.mozilla.org/it/terminology/all-resources/?status=missing&string=301053

The old (now obsolete) entity remains assigned to the `Term` rather than the newly created one. I fixed it manually for this particular case, so it can be translated.

Resolving patch:
diff --git a/pontoon/terminology/models.py b/pontoon/terminology/models.py
--- a/pontoon/terminology/models.py
+++ b/pontoon/terminology/models.py
@@ -160,10 +160,12 @@ def create_entity(self):
 
         # Using update() to avoid circular Term.save() call
         Term.objects.filter(pk=self.pk).update(entity_id=entity.id)
+        entity.term = self
 
         if not created:
             entity.obsolete = False
-            entity.save(update_fields=["obsolete"])
+
+        entity.save()
 
         # Make sure Term entities are ordered alphabetically
         entities = list(


Test patch:
diff --git a/pontoon/terminology/tests/test_models.py b/pontoon/terminology/tests/test_models.py
--- a/pontoon/terminology/tests/test_models.py
+++ b/pontoon/terminology/tests/test_models.py
@@ -267,3 +267,28 @@ def test_handle_term_update_definition(
     assert create_entity_mock.call_count == 0
     assert obsolete_entity_mock.call_count == 0
     assert update_terminology_project_stats_mock.call_count == 0
+
+
+@pytest.mark.django_db
+def test_create_entity_for_new_term(localizable_term):
+    """
+    Given a new localizable term when create_entity() is called,
+    then a new corresponding entity should be created and related to the term.
+    """
+    localizable_term.create_entity()
+    assert localizable_term.entity is not None
+    assert localizable_term.text in localizable_term.entity.string
+    assert localizable_term.entity.term == localizable_term
+
+
+@pytest.mark.django_db
+def test_create_entity_on_term_text_change(localizable_term):
+    """
+    Given an existing localizable term with an associated entity,
+    when the term's text is changed and create_entity() is called again,
+    then a new entity should be created and associated with the term.
+    """
+    original_entity = localizable_term.entity
+    localizable_term.text = "Changed text"
+    localizable_term.create_entity()
+    assert localizable_term.entity != original_entity