We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Entities of mass deleted terms do not get obsolete
When terms are deleted from the Django Admin, we [obsolete corresponding Entities](https://github.com/mozilla/pontoon/blob/01cddfd0df2f5ddf85d1b5e26a13003f9f320d97/pontoon/terminology/models.py#L239). However, this function doesn't trigger in mass delete actions, which results in the following error when trying to translate deleted Terms:

```
RelatedObjectDoesNotExist: Entity has no term
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pontoon/terminology/models.py`
Content:
```
1 import re
2 
3 from django.db import models
4 
5 from pontoon.base.models import Entity, ProjectLocale, Resource, TranslatedResource
6 
7 
8 def update_terminology_project_stats():
9     resource = Resource.objects.get(project__slug="terminology")
10     project = resource.project
11     total_strings = Entity.objects.filter(resource=resource, obsolete=False).count()
12     resource.total_strings = total_strings
13     resource.save(update_fields=["total_strings"])
14 
15     translated_resources = list(TranslatedResource.objects.filter(resource=resource))
16 
17     for translated_resource in translated_resources:
18         translated_resource.calculate_stats(save=False)
19 
20     TranslatedResource.objects.bulk_update(
21         translated_resources,
22         [
23             "total_strings",
24             "approved_strings",
25             "pretranslated_strings",
26             "strings_with_errors",
27             "strings_with_warnings",
28             "unreviewed_strings",
29         ],
30     )
31 
32     project.aggregate_stats()
33 
34     for locale in project.locales.all():
35         locale.aggregate_stats()
36 
37     for projectlocale in ProjectLocale.objects.filter(project=project):
38         projectlocale.aggregate_stats()
39 
40 
41 class TermQuerySet(models.QuerySet):
42     def for_string(self, string):
43         terms = []
44         available_terms = self.exclude(definition="").exclude(forbidden=True)
45 
46         for term in available_terms:
47             term_text = r"\b" + re.escape(term.text)
48             flags = 0 if term.case_sensitive else re.IGNORECASE
49 
50             if re.search(term_text, string, flags):
51                 terms.append(term)
52 
53         return terms
54 
55 
56 class Term(models.Model):
57     text = models.CharField(max_length=255)
58     entity = models.OneToOneField("base.Entity", models.SET_NULL, null=True, blank=True)
59 
60     class PartOfSpeech(models.TextChoices):
61         ADJECTIVE = "adjective", "Adjective"
62         ADVERB = "adverb", "Adverb"
63         NOUN = "noun", "Noun"
64         VERB = "verb", "Verb"
65 
66     part_of_speech = models.CharField(max_length=50, choices=PartOfSpeech.choices)
67 
68     definition = models.TextField(blank=True)
69     usage = models.TextField(blank=True)
70     notes = models.TextField(blank=True)
71 
72     class Status(models.TextChoices):
73         APPROVED = "approved", "Approved"
74         NEW = "new", "New"
75         OBSOLETE = "obsolete", "Obsolete"
76         REVIEW = "review", "Review"
77 
78     status = models.CharField(
79         max_length=20, choices=Status.choices, null=True, blank=True
80     )
81 
82     case_sensitive = models.BooleanField(default=False)
83     do_not_translate = models.BooleanField(default=False)
84     forbidden = models.BooleanField(default=False)
85 
86     created_at = models.DateTimeField(auto_now_add=True)
87     created_by = models.ForeignKey(
88         "auth.User", models.SET_NULL, related_name="terms", null=True, blank=True
89     )
90 
91     objects = TermQuerySet.as_manager()
92 
93     def translation(self, locale):
94         """
95         Get locale translation of the term.
96         """
97         if self.do_not_translate:
98             return self.text
99         else:
100             try:
101                 return self.translations.get(locale=locale).text
102             except (AttributeError, TermTranslation.DoesNotExist):
103                 return None
104 
105     @property
106     def localizable(self):
107         """
108         Check if the term is localizable.
109         """
110         if self.do_not_translate:
111             return False
112 
113         if self.forbidden:
114             return False
115 
116         if self.definition == "":
117             return False
118 
119         return True
120 
121     def entity_comment(self):
122         """
123         Generate entity comment from the term.
124         """
125         comment = "{}. {}.".format(
126             self.part_of_speech.capitalize(),
127             self.definition.capitalize().rstrip("."),
128         )
129 
130         if self.usage:
131             comment += " E.g. {}.".format(self.usage.capitalize().rstrip("."))
132 
133         return comment
134 
135     def create_entity(self):
136         """
137         An Entity must be created (or deobsoleted) for a Term according to the
138         following rules:
139         - Entity.string contains content of Term.text.
140         - Entity.comment contains joint content of several fields:
141           Term.part_of_speech. Term.definition. E.g.: Term.usage.
142         """
143         resource = Resource.objects.get(project__slug="terminology")
144 
145         entity, created = Entity.objects.get_or_create(
146             string=self.text,
147             comment=self.entity_comment(),
148             resource=resource,
149         )
150 
151         # Using update() to avoid circular Term.save() call
152         Term.objects.filter(pk=self.pk).update(entity_id=entity.id)
153 
154         if not created:
155             entity.obsolete = False
156             entity.save(update_fields=["obsolete"])
157 
158         # Make sure Term entities are ordered alphabetically
159         entities = list(
160             Entity.objects.filter(resource=resource, obsolete=False).order_by("string")
161         )
162         for index, e in enumerate(entities):
163             e.order = index
164         Entity.objects.bulk_update(entities, ["order"])
165 
166     def obsolete_entity(self):
167         entity = self.entity
168 
169         # Ignore if term doesn't have entity assigned
170         if entity is None:
171             return
172 
173         entity.obsolete = True
174         entity.save(update_fields=["obsolete"])
175 
176     def handle_term_update(self):
177         """
178         Before updating an existing Term, update its Entity if neccessary
179         """
180         term = self
181         old_term = Term.objects.get(pk=term.pk)
182 
183         # Ignore changes to non-localizable terms that stay non-localizable
184         if not old_term.localizable and not term.localizable:
185             return
186 
187         # If localizable term becomes non-localizable, obsolete its Entity
188         if old_term.localizable and not term.localizable:
189             old_term.obsolete_entity()
190 
191         # If non-localizable term becomes localizable, create a corresponding Entity
192         elif not old_term.localizable and term.localizable:
193             term.create_entity()
194 
195         # If relevant changes are made to the localizable term that stays localizable
196         else:
197             # If Term.text changes, a new Entity instance gets created and the previous one becomes obsolete.
198             if old_term.text != term.text:
199                 old_term.obsolete_entity()
200                 term.create_entity()
201 
202             # If Term.part_of_speech, Term.definition or Term.usage change, Entity.comment gets updated.
203             elif (
204                 old_term.part_of_speech != term.part_of_speech
205                 or old_term.definition != term.definition
206                 or old_term.usage != term.usage
207             ):
208                 entity = term.entity
209 
210                 # Ignore if term doesn't have entity assigned
211                 if entity is None:
212                     return
213 
214                 entity.comment = term.entity_comment()
215                 entity.save(update_fields=["comment"])
216 
217                 return
218 
219         update_terminology_project_stats()
220 
221     def handle_term_create(self):
222         """
223         After creating a new localizable Term, create its Entity
224         """
225         self.create_entity()
226         update_terminology_project_stats()
227 
228     def save(self, *args, **kwargs):
229         created = self.pk is None
230 
231         if not created:
232             self.handle_term_update()
233 
234         super().save(*args, **kwargs)
235 
236         if created and self.localizable:
237             self.handle_term_create()
238 
239     def delete(self, *args, **kwargs):
240         """
241         Before deleting a Term, obsolete its Entity
242         """
243         self.obsolete_entity()
244         update_terminology_project_stats()
245 
246         super().delete(*args, **kwargs)
247 
248     def __str__(self):
249         return self.text
250 
251 
252 class TermTranslation(models.Model):
253     term = models.ForeignKey(Term, models.CASCADE, related_name="translations")
254     locale = models.ForeignKey("base.Locale", models.CASCADE, related_name="terms")
255 
256     text = models.CharField(max_length=255)
257 
258     def __str__(self):
259         return self.text
260
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pontoon/terminology/models.py b/pontoon/terminology/models.py
--- a/pontoon/terminology/models.py
+++ b/pontoon/terminology/models.py
@@ -52,6 +52,16 @@
 
         return terms
 
+    def delete(self, *args, **kwargs):
+        """
+        Before deleting Terms, obsolete their Entities
+        """
+        for term in self:
+            term.obsolete_entity()
+        update_terminology_project_stats()
+
+        super().delete(*args, **kwargs)
+
 
 class Term(models.Model):
     text = models.CharField(max_length=255)

{"golden_diff": "diff --git a/pontoon/terminology/models.py b/pontoon/terminology/models.py\n--- a/pontoon/terminology/models.py\n+++ b/pontoon/terminology/models.py\n@@ -52,6 +52,16 @@\n \n         return terms\n \n+    def delete(self, *args, **kwargs):\n+        \"\"\"\n+        Before deleting Terms, obsolete their Entities\n+        \"\"\"\n+        for term in self:\n+            term.obsolete_entity()\n+        update_terminology_project_stats()\n+\n+        super().delete(*args, **kwargs)\n+\n \n class Term(models.Model):\n     text = models.CharField(max_length=255)\n", "issue": "Entities of mass deleted terms do not get obsolete\nWhen terms are deleted from the Django Admin, we [obsolete corresponding Entities](https://github.com/mozilla/pontoon/blob/01cddfd0df2f5ddf85d1b5e26a13003f9f320d97/pontoon/terminology/models.py#L239). However, this function doesn't trigger in mass delete actions, which results in the following error when trying to translate deleted Terms:\r\n\r\n```\r\nRelatedObjectDoesNotExist: Entity has no term\r\n```\n", "before_files": [{"content": "import re\n\nfrom django.db import models\n\nfrom pontoon.base.models import Entity, ProjectLocale, Resource, TranslatedResource\n\n\ndef update_terminology_project_stats():\n    resource = Resource.objects.get(project__slug=\"terminology\")\n    project = resource.project\n    total_strings = Entity.objects.filter(resource=resource, obsolete=False).count()\n    resource.total_strings = total_strings\n    resource.save(update_fields=[\"total_strings\"])\n\n    translated_resources = list(TranslatedResource.objects.filter(resource=resource))\n\n    for translated_resource in translated_resources:\n        translated_resource.calculate_stats(save=False)\n\n    TranslatedResource.objects.bulk_update(\n        translated_resources,\n        [\n            \"total_strings\",\n            \"approved_strings\",\n            \"pretranslated_strings\",\n            \"strings_with_errors\",\n            \"strings_with_warnings\",\n            \"unreviewed_strings\",\n        ],\n    )\n\n    project.aggregate_stats()\n\n    for locale in project.locales.all():\n        locale.aggregate_stats()\n\n    for projectlocale in ProjectLocale.objects.filter(project=project):\n        projectlocale.aggregate_stats()\n\n\nclass TermQuerySet(models.QuerySet):\n    def for_string(self, string):\n        terms = []\n        available_terms = self.exclude(definition=\"\").exclude(forbidden=True)\n\n        for term in available_terms:\n            term_text = r\"\\b\" + re.escape(term.text)\n            flags = 0 if term.case_sensitive else re.IGNORECASE\n\n            if re.search(term_text, string, flags):\n                terms.append(term)\n\n        return terms\n\n\nclass Term(models.Model):\n    text = models.CharField(max_length=255)\n    entity = models.OneToOneField(\"base.Entity\", models.SET_NULL, null=True, blank=True)\n\n    class PartOfSpeech(models.TextChoices):\n        ADJECTIVE = \"adjective\", \"Adjective\"\n        ADVERB = \"adverb\", \"Adverb\"\n        NOUN = \"noun\", \"Noun\"\n        VERB = \"verb\", \"Verb\"\n\n    part_of_speech = models.CharField(max_length=50, choices=PartOfSpeech.choices)\n\n    definition = models.TextField(blank=True)\n    usage = models.TextField(blank=True)\n    notes = models.TextField(blank=True)\n\n    class Status(models.TextChoices):\n        APPROVED = \"approved\", \"Approved\"\n        NEW = \"new\", \"New\"\n        OBSOLETE = \"obsolete\", \"Obsolete\"\n        REVIEW = \"review\", \"Review\"\n\n    status = models.CharField(\n        max_length=20, choices=Status.choices, null=True, blank=True\n    )\n\n    case_sensitive = models.BooleanField(default=False)\n    do_not_translate = models.BooleanField(default=False)\n    forbidden = models.BooleanField(default=False)\n\n    created_at = models.DateTimeField(auto_now_add=True)\n    created_by = models.ForeignKey(\n        \"auth.User\", models.SET_NULL, related_name=\"terms\", null=True, blank=True\n    )\n\n    objects = TermQuerySet.as_manager()\n\n    def translation(self, locale):\n        \"\"\"\n        Get locale translation of the term.\n        \"\"\"\n        if self.do_not_translate:\n            return self.text\n        else:\n            try:\n                return self.translations.get(locale=locale).text\n            except (AttributeError, TermTranslation.DoesNotExist):\n                return None\n\n    @property\n    def localizable(self):\n        \"\"\"\n        Check if the term is localizable.\n        \"\"\"\n        if self.do_not_translate:\n            return False\n\n        if self.forbidden:\n            return False\n\n        if self.definition == \"\":\n            return False\n\n        return True\n\n    def entity_comment(self):\n        \"\"\"\n        Generate entity comment from the term.\n        \"\"\"\n        comment = \"{}. {}.\".format(\n            self.part_of_speech.capitalize(),\n            self.definition.capitalize().rstrip(\".\"),\n        )\n\n        if self.usage:\n            comment += \" E.g. {}.\".format(self.usage.capitalize().rstrip(\".\"))\n\n        return comment\n\n    def create_entity(self):\n        \"\"\"\n        An Entity must be created (or deobsoleted) for a Term according to the\n        following rules:\n        - Entity.string contains content of Term.text.\n        - Entity.comment contains joint content of several fields:\n          Term.part_of_speech. Term.definition. E.g.: Term.usage.\n        \"\"\"\n        resource = Resource.objects.get(project__slug=\"terminology\")\n\n        entity, created = Entity.objects.get_or_create(\n            string=self.text,\n            comment=self.entity_comment(),\n            resource=resource,\n        )\n\n        # Using update() to avoid circular Term.save() call\n        Term.objects.filter(pk=self.pk).update(entity_id=entity.id)\n\n        if not created:\n            entity.obsolete = False\n            entity.save(update_fields=[\"obsolete\"])\n\n        # Make sure Term entities are ordered alphabetically\n        entities = list(\n            Entity.objects.filter(resource=resource, obsolete=False).order_by(\"string\")\n        )\n        for index, e in enumerate(entities):\n            e.order = index\n        Entity.objects.bulk_update(entities, [\"order\"])\n\n    def obsolete_entity(self):\n        entity = self.entity\n\n        # Ignore if term doesn't have entity assigned\n        if entity is None:\n            return\n\n        entity.obsolete = True\n        entity.save(update_fields=[\"obsolete\"])\n\n    def handle_term_update(self):\n        \"\"\"\n        Before updating an existing Term, update its Entity if neccessary\n        \"\"\"\n        term = self\n        old_term = Term.objects.get(pk=term.pk)\n\n        # Ignore changes to non-localizable terms that stay non-localizable\n        if not old_term.localizable and not term.localizable:\n            return\n\n        # If localizable term becomes non-localizable, obsolete its Entity\n        if old_term.localizable and not term.localizable:\n            old_term.obsolete_entity()\n\n        # If non-localizable term becomes localizable, create a corresponding Entity\n        elif not old_term.localizable and term.localizable:\n            term.create_entity()\n\n        # If relevant changes are made to the localizable term that stays localizable\n        else:\n            # If Term.text changes, a new Entity instance gets created and the previous one becomes obsolete.\n            if old_term.text != term.text:\n                old_term.obsolete_entity()\n                term.create_entity()\n\n            # If Term.part_of_speech, Term.definition or Term.usage change, Entity.comment gets updated.\n            elif (\n                old_term.part_of_speech != term.part_of_speech\n                or old_term.definition != term.definition\n                or old_term.usage != term.usage\n            ):\n                entity = term.entity\n\n                # Ignore if term doesn't have entity assigned\n                if entity is None:\n                    return\n\n                entity.comment = term.entity_comment()\n                entity.save(update_fields=[\"comment\"])\n\n                return\n\n        update_terminology_project_stats()\n\n    def handle_term_create(self):\n        \"\"\"\n        After creating a new localizable Term, create its Entity\n        \"\"\"\n        self.create_entity()\n        update_terminology_project_stats()\n\n    def save(self, *args, **kwargs):\n        created = self.pk is None\n\n        if not created:\n            self.handle_term_update()\n\n        super().save(*args, **kwargs)\n\n        if created and self.localizable:\n            self.handle_term_create()\n\n    def delete(self, *args, **kwargs):\n        \"\"\"\n        Before deleting a Term, obsolete its Entity\n        \"\"\"\n        self.obsolete_entity()\n        update_terminology_project_stats()\n\n        super().delete(*args, **kwargs)\n\n    def __str__(self):\n        return self.text\n\n\nclass TermTranslation(models.Model):\n    term = models.ForeignKey(Term, models.CASCADE, related_name=\"translations\")\n    locale = models.ForeignKey(\"base.Locale\", models.CASCADE, related_name=\"terms\")\n\n    text = models.CharField(max_length=255)\n\n    def __str__(self):\n        return self.text\n", "path": "pontoon/terminology/models.py"}], "after_files": [{"content": "import re\n\nfrom django.db import models\n\nfrom pontoon.base.models import Entity, ProjectLocale, Resource, TranslatedResource\n\n\ndef update_terminology_project_stats():\n    resource = Resource.objects.get(project__slug=\"terminology\")\n    project = resource.project\n    total_strings = Entity.objects.filter(resource=resource, obsolete=False).count()\n    resource.total_strings = total_strings\n    resource.save(update_fields=[\"total_strings\"])\n\n    translated_resources = list(TranslatedResource.objects.filter(resource=resource))\n\n    for translated_resource in translated_resources:\n        translated_resource.calculate_stats(save=False)\n\n    TranslatedResource.objects.bulk_update(\n        translated_resources,\n        [\n            \"total_strings\",\n            \"approved_strings\",\n            \"pretranslated_strings\",\n            \"strings_with_errors\",\n            \"strings_with_warnings\",\n            \"unreviewed_strings\",\n        ],\n    )\n\n    project.aggregate_stats()\n\n    for locale in project.locales.all():\n        locale.aggregate_stats()\n\n    for projectlocale in ProjectLocale.objects.filter(project=project):\n        projectlocale.aggregate_stats()\n\n\nclass TermQuerySet(models.QuerySet):\n    def for_string(self, string):\n        terms = []\n        available_terms = self.exclude(definition=\"\").exclude(forbidden=True)\n\n        for term in available_terms:\n            term_text = r\"\\b\" + re.escape(term.text)\n            flags = 0 if term.case_sensitive else re.IGNORECASE\n\n            if re.search(term_text, string, flags):\n                terms.append(term)\n\n        return terms\n\n    def delete(self, *args, **kwargs):\n        \"\"\"\n        Before deleting Terms, obsolete their Entities\n        \"\"\"\n        for term in self:\n            term.obsolete_entity()\n        update_terminology_project_stats()\n\n        super().delete(*args, **kwargs)\n\n\nclass Term(models.Model):\n    text = models.CharField(max_length=255)\n    entity = models.OneToOneField(\"base.Entity\", models.SET_NULL, null=True, blank=True)\n\n    class PartOfSpeech(models.TextChoices):\n        ADJECTIVE = \"adjective\", \"Adjective\"\n        ADVERB = \"adverb\", \"Adverb\"\n        NOUN = \"noun\", \"Noun\"\n        VERB = \"verb\", \"Verb\"\n\n    part_of_speech = models.CharField(max_length=50, choices=PartOfSpeech.choices)\n\n    definition = models.TextField(blank=True)\n    usage = models.TextField(blank=True)\n    notes = models.TextField(blank=True)\n\n    class Status(models.TextChoices):\n        APPROVED = \"approved\", \"Approved\"\n        NEW = \"new\", \"New\"\n        OBSOLETE = \"obsolete\", \"Obsolete\"\n        REVIEW = \"review\", \"Review\"\n\n    status = models.CharField(\n        max_length=20, choices=Status.choices, null=True, blank=True\n    )\n\n    case_sensitive = models.BooleanField(default=False)\n    do_not_translate = models.BooleanField(default=False)\n    forbidden = models.BooleanField(default=False)\n\n    created_at = models.DateTimeField(auto_now_add=True)\n    created_by = models.ForeignKey(\n        \"auth.User\", models.SET_NULL, related_name=\"terms\", null=True, blank=True\n    )\n\n    objects = TermQuerySet.as_manager()\n\n    def translation(self, locale):\n        \"\"\"\n        Get locale translation of the term.\n        \"\"\"\n        if self.do_not_translate:\n            return self.text\n        else:\n            try:\n                return self.translations.get(locale=locale).text\n            except (AttributeError, TermTranslation.DoesNotExist):\n                return None\n\n    @property\n    def localizable(self):\n        \"\"\"\n        Check if the term is localizable.\n        \"\"\"\n        if self.do_not_translate:\n            return False\n\n        if self.forbidden:\n            return False\n\n        if self.definition == \"\":\n            return False\n\n        return True\n\n    def entity_comment(self):\n        \"\"\"\n        Generate entity comment from the term.\n        \"\"\"\n        comment = \"{}. {}.\".format(\n            self.part_of_speech.capitalize(),\n            self.definition.capitalize().rstrip(\".\"),\n        )\n\n        if self.usage:\n            comment += \" E.g. {}.\".format(self.usage.capitalize().rstrip(\".\"))\n\n        return comment\n\n    def create_entity(self):\n        \"\"\"\n        An Entity must be created (or deobsoleted) for a Term according to the\n        following rules:\n        - Entity.string contains content of Term.text.\n        - Entity.comment contains joint content of several fields:\n          Term.part_of_speech. Term.definition. E.g.: Term.usage.\n        \"\"\"\n        resource = Resource.objects.get(project__slug=\"terminology\")\n\n        entity, created = Entity.objects.get_or_create(\n            string=self.text,\n            comment=self.entity_comment(),\n            resource=resource,\n        )\n\n        # Using update() to avoid circular Term.save() call\n        Term.objects.filter(pk=self.pk).update(entity_id=entity.id)\n\n        if not created:\n            entity.obsolete = False\n            entity.save(update_fields=[\"obsolete\"])\n\n        # Make sure Term entities are ordered alphabetically\n        entities = list(\n            Entity.objects.filter(resource=resource, obsolete=False).order_by(\"string\")\n        )\n        for index, e in enumerate(entities):\n            e.order = index\n        Entity.objects.bulk_update(entities, [\"order\"])\n\n    def obsolete_entity(self):\n        entity = self.entity\n\n        # Ignore if term doesn't have entity assigned\n        if entity is None:\n            return\n\n        entity.obsolete = True\n        entity.save(update_fields=[\"obsolete\"])\n\n    def handle_term_update(self):\n        \"\"\"\n        Before updating an existing Term, update its Entity if neccessary\n        \"\"\"\n        term = self\n        old_term = Term.objects.get(pk=term.pk)\n\n        # Ignore changes to non-localizable terms that stay non-localizable\n        if not old_term.localizable and not term.localizable:\n            return\n\n        # If localizable term becomes non-localizable, obsolete its Entity\n        if old_term.localizable and not term.localizable:\n            old_term.obsolete_entity()\n\n        # If non-localizable term becomes localizable, create a corresponding Entity\n        elif not old_term.localizable and term.localizable:\n            term.create_entity()\n\n        # If relevant changes are made to the localizable term that stays localizable\n        else:\n            # If Term.text changes, a new Entity instance gets created and the previous one becomes obsolete.\n            if old_term.text != term.text:\n                old_term.obsolete_entity()\n                term.create_entity()\n\n            # If Term.part_of_speech, Term.definition or Term.usage change, Entity.comment gets updated.\n            elif (\n                old_term.part_of_speech != term.part_of_speech\n                or old_term.definition != term.definition\n                or old_term.usage != term.usage\n            ):\n                entity = term.entity\n\n                # Ignore if term doesn't have entity assigned\n                if entity is None:\n                    return\n\n                entity.comment = term.entity_comment()\n                entity.save(update_fields=[\"comment\"])\n\n                return\n\n        update_terminology_project_stats()\n\n    def handle_term_create(self):\n        \"\"\"\n        After creating a new localizable Term, create its Entity\n        \"\"\"\n        self.create_entity()\n        update_terminology_project_stats()\n\n    def save(self, *args, **kwargs):\n        created = self.pk is None\n\n        if not created:\n            self.handle_term_update()\n\n        super().save(*args, **kwargs)\n\n        if created and self.localizable:\n            self.handle_term_create()\n\n    def delete(self, *args, **kwargs):\n        \"\"\"\n        Before deleting a Term, obsolete its Entity\n        \"\"\"\n        self.obsolete_entity()\n        update_terminology_project_stats()\n\n        super().delete(*args, **kwargs)\n\n    def __str__(self):\n        return self.text\n\n\nclass TermTranslation(models.Model):\n    term = models.ForeignKey(Term, models.CASCADE, related_name=\"translations\")\n    locale = models.ForeignKey(\"base.Locale\", models.CASCADE, related_name=\"terms\")\n\n    text = models.CharField(max_length=255)\n\n    def __str__(self):\n        return self.text\n", "path": "pontoon/terminology/models.py"}]}