We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
harddelete fails with AttributeError: 'NoneType' object has no attribute 'delete'
## Description
The `harddelete` command fails with this backtrace:
```
Aug 5 12:01:39 AM  {"extra": {"landscape_id": "UUID('8483fbd9-307a-4b1f-a6f2-2eafaf6526b9')"}, "event": "Landscape has no default group, but it must have", "timestamp": "2023-08-05T04:01:39.275313Z", "logger": "apps.core.models.landscapes", "level": "error"}
Aug 5 12:01:39 AM  Traceback (most recent call last):
Aug 5 12:01:39 AM    File "/app/terraso_backend/manage.py", line 34, in <module>
Aug 5 12:01:39 AM      main()
Aug 5 12:01:39 AM    File "/app/terraso_backend/manage.py", line 30, in main
Aug 5 12:01:39 AM      execute_from_command_line(sys.argv)
Aug 5 12:01:39 AM    File "/home/terraso/.local/lib/python3.11/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line
Aug 5 12:01:39 AM      utility.execute()
Aug 5 12:01:39 AM    File "/home/terraso/.local/lib/python3.11/site-packages/django/core/management/__init__.py", line 436, in execute
Aug 5 12:01:39 AM      self.fetch_command(subcommand).run_from_argv(self.argv)
Aug 5 12:01:39 AM    File "/home/terraso/.local/lib/python3.11/site-packages/django/core/management/base.py", line 412, in run_from_argv
Aug 5 12:01:39 AM      self.execute(*args, **cmd_options)
Aug 5 12:01:39 AM    File "/home/terraso/.local/lib/python3.11/site-packages/django/core/management/base.py", line 458, in execute
Aug 5 12:01:39 AM      output = self.handle(*args, **options)
Aug 5 12:01:39 AM               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Aug 5 12:01:39 AM    File "/app/terraso_backend/apps/core/management/commands/harddelete.py", line 62, in handle
Aug 5 12:01:39 AM      obj.delete(force_policy=HARD_DELETE)
Aug 5 12:01:39 AM    File "/app/terraso_backend/apps/core/models/landscapes.py", line 129, in delete
Aug 5 12:01:39 AM      default_group.delete()
Aug 5 12:01:39 AM      ^^^^^^^^^^^^^^^^^^^^
Aug 5 12:01:39 AM  AttributeError: 'NoneType' object has no attribute 'delete'
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `terraso_backend/apps/core/models/landscapes.py`
Content:
```
1 # Copyright © 2021-2023 Technology Matters
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License as published
5 # by the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU Affero General Public License for more details.
12 #
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see https://www.gnu.org/licenses/.
15 
16 import structlog
17 from dirtyfields import DirtyFieldsMixin
18 from django.db import models, transaction
19 
20 from apps.core import permission_rules as perm_rules
21 from apps.core.gis.utils import (
22     calculate_geojson_centroid,
23     calculate_geojson_feature_area,
24 )
25 from apps.core.models.taxonomy_terms import TaxonomyTerm
26 
27 from .commons import BaseModel, SlugModel, validate_name
28 from .groups import Group
29 from .users import User
30 
31 logger = structlog.get_logger(__name__)
32 
33 
34 class Landscape(SlugModel, DirtyFieldsMixin):
35     """
36     This model represents a Landscape on Terraso platform.
37 
38     A Landscape is a socio-ecological system that consists of natural
39     and/or human-modified ecosystems. Defined by its stakeholds, a
40     Landscape usually has geographical boundaries. It may correspond to,
41     or be a combination of, natural boundaries, distinct land features,
42     socially defined areas such as indigenous territories, and/or
43     jurisdictional and administrative boundaries. The boundaries of a
44     Landscape can cross several countries.
45     """
46 
47     fields_to_trim = ["name", "description"]
48 
49     name = models.CharField(max_length=128, validators=[validate_name])
50     description = models.TextField(blank=True, default="")
51     website = models.URLField(max_length=500, blank=True, default="")
52     location = models.CharField(max_length=128, blank=True, default="")
53     area_polygon = models.JSONField(blank=True, null=True)
54     email = models.EmailField(blank=True, default="")
55     area_scalar_m2 = models.FloatField(blank=True, null=True)
56 
57     created_by = models.ForeignKey(
58         User,
59         blank=True,
60         null=True,
61         on_delete=models.PROTECT,
62         related_name="created_landscapes",
63     )
64     groups = models.ManyToManyField(Group, through="LandscapeGroup")
65 
66     area_types = models.JSONField(blank=True, null=True)
67     taxonomy_terms = models.ManyToManyField(TaxonomyTerm, blank=True)
68     population = models.IntegerField(blank=True, null=True)
69 
70     PARTNERSHIP_STATUS_NONE = ""
71     PARTNERSHIP_STATUS_NO = "no"
72     PARTNERSHIP_STATUS_IN_PROGRESS = "in-progress"
73     PARTNERSHIP_STATUS_YES = "yes"
74 
75     MEMBERSHIP_TYPES = (
76         (PARTNERSHIP_STATUS_NONE, "None"),
77         (PARTNERSHIP_STATUS_NO, "No"),
78         (PARTNERSHIP_STATUS_IN_PROGRESS, "In Progress"),
79         (PARTNERSHIP_STATUS_YES, "Yes"),
80     )
81     partnership_status = models.CharField(
82         max_length=32, choices=MEMBERSHIP_TYPES, blank=True, default=PARTNERSHIP_STATUS_NONE
83     )
84     profile_image = models.URLField(blank=True, default="")
85     profile_image_description = models.TextField(blank=True, default="")
86     center_coordinates = models.JSONField(blank=True, null=True)
87 
88     field_to_slug = "name"
89 
90     class Meta(SlugModel.Meta):
91         rules_permissions = {
92             "change": perm_rules.allowed_to_change_landscape,
93             "delete": perm_rules.allowed_to_delete_landscape,
94         }
95         _unique_fields = ["name"]
96         abstract = False
97 
98     def save(self, *args, **kwargs):
99         dirty_fields = self.get_dirty_fields()
100         if self.area_polygon and "area_polygon" in dirty_fields:
101             area_scalar_m2 = calculate_geojson_feature_area(self.area_polygon)
102             if area_scalar_m2 is not None:
103                 self.area_scalar_m2 = round(area_scalar_m2, 3)
104             self.center_coordinates = calculate_geojson_centroid(self.area_polygon)
105 
106         with transaction.atomic():
107             creating = not Landscape.objects.filter(pk=self.pk).exists()
108 
109             super().save(*args, **kwargs)
110 
111             if creating and self.created_by:
112                 group = Group(
113                     name="Group {}".format(self.slug),
114                     description="",
115                     created_by=self.created_by,
116                 )
117                 group.save()
118                 landscape_group = LandscapeGroup(
119                     group=group, landscape=self, is_default_landscape_group=True
120                 )
121                 landscape_group.save()
122 
123     def delete(self, *args, **kwargs):
124         default_group = self.get_default_group()
125 
126         with transaction.atomic():
127             ret = super().delete(*args, **kwargs)
128             # default group should be deleted as well
129             default_group.delete()
130 
131         return ret
132 
133     def get_default_group(self):
134         """
135         A default Group in a Landscape is that Group where any
136         individual (associated or not with other Groups) is added when
137         associating directly with a Landscape.
138         """
139         try:
140             # associated_groups is the related_name defined on
141             # LandscapeGroup relationship with Landscape. It returns a
142             # queryset of LandscapeGroup
143             landscape_group = self.associated_groups.get(is_default_landscape_group=True)
144         except LandscapeGroup.DoesNotExist:
145             logger.error(
146                 "Landscape has no default group, but it must have", extra={"landscape_id": self.pk}
147             )
148             return None
149 
150         return landscape_group.group
151 
152     def __str__(self):
153         return self.name
154 
155 
156 class LandscapeDevelopmentStrategy(BaseModel):
157     objectives = models.TextField(blank=True, default="")
158     opportunities = models.TextField(blank=True, default="")
159     problem_situtation = models.TextField(blank=True, default="")
160     intervention_strategy = models.TextField(blank=True, default="")
161     landscape = models.ForeignKey(
162         Landscape, on_delete=models.CASCADE, related_name="associated_development_strategy"
163     )
164 
165 
166 class LandscapeGroup(BaseModel):
167     """
168     This model represents the association between a Landscape and a Group on
169     Terraso platform.
170     """
171 
172     landscape = models.ForeignKey(
173         Landscape, on_delete=models.CASCADE, related_name="associated_groups"
174     )
175     group = models.ForeignKey(Group, on_delete=models.CASCADE, related_name="associated_landscapes")
176 
177     is_default_landscape_group = models.BooleanField(blank=True, default=False)
178     is_partnership = models.BooleanField(blank=True, default=False)
179     partnership_year = models.IntegerField(blank=True, null=True)
180 
181     class Meta:
182         rules_permissions = {
183             "add": perm_rules.allowed_to_add_landscape_group,
184             "delete": perm_rules.allowed_to_delete_landscape_group,
185         }
186         constraints = (
187             models.UniqueConstraint(
188                 fields=("group", "landscape", "is_partnership"),
189                 condition=models.Q(deleted_at__isnull=True),
190                 name="unique_active_landscape_group",
191             ),
192         )
193
```
--- 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/terraso_backend/apps/core/models/landscapes.py b/terraso_backend/apps/core/models/landscapes.py
--- a/terraso_backend/apps/core/models/landscapes.py
+++ b/terraso_backend/apps/core/models/landscapes.py
@@ -126,7 +126,8 @@
         with transaction.atomic():
             ret = super().delete(*args, **kwargs)
             # default group should be deleted as well
-            default_group.delete()
+            if default_group is not None:
+                default_group.delete()
 
         return ret

{"golden_diff": "diff --git a/terraso_backend/apps/core/models/landscapes.py b/terraso_backend/apps/core/models/landscapes.py\n--- a/terraso_backend/apps/core/models/landscapes.py\n+++ b/terraso_backend/apps/core/models/landscapes.py\n@@ -126,7 +126,8 @@\n         with transaction.atomic():\n             ret = super().delete(*args, **kwargs)\n             # default group should be deleted as well\n-            default_group.delete()\n+            if default_group is not None:\n+                default_group.delete()\n \n         return ret\n", "issue": "harddelete fails with AttributeError: 'NoneType' object has no attribute 'delete'\n## Description\r\nThe `harddelete` command fails with this backtrace:\r\n```\r\nAug 5 12:01:39 AM  {\"extra\": {\"landscape_id\": \"UUID('8483fbd9-307a-4b1f-a6f2-2eafaf6526b9')\"}, \"event\": \"Landscape has no default group, but it must have\", \"timestamp\": \"2023-08-05T04:01:39.275313Z\", \"logger\": \"apps.core.models.landscapes\", \"level\": \"error\"}\r\nAug 5 12:01:39 AM  Traceback (most recent call last):\r\nAug 5 12:01:39 AM    File \"/app/terraso_backend/manage.py\", line 34, in <module>\r\nAug 5 12:01:39 AM      main()\r\nAug 5 12:01:39 AM    File \"/app/terraso_backend/manage.py\", line 30, in main\r\nAug 5 12:01:39 AM      execute_from_command_line(sys.argv)\r\nAug 5 12:01:39 AM    File \"/home/terraso/.local/lib/python3.11/site-packages/django/core/management/__init__.py\", line 442, in execute_from_command_line\r\nAug 5 12:01:39 AM      utility.execute()\r\nAug 5 12:01:39 AM    File \"/home/terraso/.local/lib/python3.11/site-packages/django/core/management/__init__.py\", line 436, in execute\r\nAug 5 12:01:39 AM      self.fetch_command(subcommand).run_from_argv(self.argv)\r\nAug 5 12:01:39 AM    File \"/home/terraso/.local/lib/python3.11/site-packages/django/core/management/base.py\", line 412, in run_from_argv\r\nAug 5 12:01:39 AM      self.execute(*args, **cmd_options)\r\nAug 5 12:01:39 AM    File \"/home/terraso/.local/lib/python3.11/site-packages/django/core/management/base.py\", line 458, in execute\r\nAug 5 12:01:39 AM      output = self.handle(*args, **options)\r\nAug 5 12:01:39 AM               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\nAug 5 12:01:39 AM    File \"/app/terraso_backend/apps/core/management/commands/harddelete.py\", line 62, in handle\r\nAug 5 12:01:39 AM      obj.delete(force_policy=HARD_DELETE)\r\nAug 5 12:01:39 AM    File \"/app/terraso_backend/apps/core/models/landscapes.py\", line 129, in delete\r\nAug 5 12:01:39 AM      default_group.delete()\r\nAug 5 12:01:39 AM      ^^^^^^^^^^^^^^^^^^^^\r\nAug 5 12:01:39 AM  AttributeError: 'NoneType' object has no attribute 'delete'\r\n```\n", "before_files": [{"content": "# Copyright \u00a9 2021-2023 Technology Matters\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published\n# by the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program. If not, see https://www.gnu.org/licenses/.\n\nimport structlog\nfrom dirtyfields import DirtyFieldsMixin\nfrom django.db import models, transaction\n\nfrom apps.core import permission_rules as perm_rules\nfrom apps.core.gis.utils import (\n    calculate_geojson_centroid,\n    calculate_geojson_feature_area,\n)\nfrom apps.core.models.taxonomy_terms import TaxonomyTerm\n\nfrom .commons import BaseModel, SlugModel, validate_name\nfrom .groups import Group\nfrom .users import User\n\nlogger = structlog.get_logger(__name__)\n\n\nclass Landscape(SlugModel, DirtyFieldsMixin):\n    \"\"\"\n    This model represents a Landscape on Terraso platform.\n\n    A Landscape is a socio-ecological system that consists of natural\n    and/or human-modified ecosystems. Defined by its stakeholds, a\n    Landscape usually has geographical boundaries. It may correspond to,\n    or be a combination of, natural boundaries, distinct land features,\n    socially defined areas such as indigenous territories, and/or\n    jurisdictional and administrative boundaries. The boundaries of a\n    Landscape can cross several countries.\n    \"\"\"\n\n    fields_to_trim = [\"name\", \"description\"]\n\n    name = models.CharField(max_length=128, validators=[validate_name])\n    description = models.TextField(blank=True, default=\"\")\n    website = models.URLField(max_length=500, blank=True, default=\"\")\n    location = models.CharField(max_length=128, blank=True, default=\"\")\n    area_polygon = models.JSONField(blank=True, null=True)\n    email = models.EmailField(blank=True, default=\"\")\n    area_scalar_m2 = models.FloatField(blank=True, null=True)\n\n    created_by = models.ForeignKey(\n        User,\n        blank=True,\n        null=True,\n        on_delete=models.PROTECT,\n        related_name=\"created_landscapes\",\n    )\n    groups = models.ManyToManyField(Group, through=\"LandscapeGroup\")\n\n    area_types = models.JSONField(blank=True, null=True)\n    taxonomy_terms = models.ManyToManyField(TaxonomyTerm, blank=True)\n    population = models.IntegerField(blank=True, null=True)\n\n    PARTNERSHIP_STATUS_NONE = \"\"\n    PARTNERSHIP_STATUS_NO = \"no\"\n    PARTNERSHIP_STATUS_IN_PROGRESS = \"in-progress\"\n    PARTNERSHIP_STATUS_YES = \"yes\"\n\n    MEMBERSHIP_TYPES = (\n        (PARTNERSHIP_STATUS_NONE, \"None\"),\n        (PARTNERSHIP_STATUS_NO, \"No\"),\n        (PARTNERSHIP_STATUS_IN_PROGRESS, \"In Progress\"),\n        (PARTNERSHIP_STATUS_YES, \"Yes\"),\n    )\n    partnership_status = models.CharField(\n        max_length=32, choices=MEMBERSHIP_TYPES, blank=True, default=PARTNERSHIP_STATUS_NONE\n    )\n    profile_image = models.URLField(blank=True, default=\"\")\n    profile_image_description = models.TextField(blank=True, default=\"\")\n    center_coordinates = models.JSONField(blank=True, null=True)\n\n    field_to_slug = \"name\"\n\n    class Meta(SlugModel.Meta):\n        rules_permissions = {\n            \"change\": perm_rules.allowed_to_change_landscape,\n            \"delete\": perm_rules.allowed_to_delete_landscape,\n        }\n        _unique_fields = [\"name\"]\n        abstract = False\n\n    def save(self, *args, **kwargs):\n        dirty_fields = self.get_dirty_fields()\n        if self.area_polygon and \"area_polygon\" in dirty_fields:\n            area_scalar_m2 = calculate_geojson_feature_area(self.area_polygon)\n            if area_scalar_m2 is not None:\n                self.area_scalar_m2 = round(area_scalar_m2, 3)\n            self.center_coordinates = calculate_geojson_centroid(self.area_polygon)\n\n        with transaction.atomic():\n            creating = not Landscape.objects.filter(pk=self.pk).exists()\n\n            super().save(*args, **kwargs)\n\n            if creating and self.created_by:\n                group = Group(\n                    name=\"Group {}\".format(self.slug),\n                    description=\"\",\n                    created_by=self.created_by,\n                )\n                group.save()\n                landscape_group = LandscapeGroup(\n                    group=group, landscape=self, is_default_landscape_group=True\n                )\n                landscape_group.save()\n\n    def delete(self, *args, **kwargs):\n        default_group = self.get_default_group()\n\n        with transaction.atomic():\n            ret = super().delete(*args, **kwargs)\n            # default group should be deleted as well\n            default_group.delete()\n\n        return ret\n\n    def get_default_group(self):\n        \"\"\"\n        A default Group in a Landscape is that Group where any\n        individual (associated or not with other Groups) is added when\n        associating directly with a Landscape.\n        \"\"\"\n        try:\n            # associated_groups is the related_name defined on\n            # LandscapeGroup relationship with Landscape. It returns a\n            # queryset of LandscapeGroup\n            landscape_group = self.associated_groups.get(is_default_landscape_group=True)\n        except LandscapeGroup.DoesNotExist:\n            logger.error(\n                \"Landscape has no default group, but it must have\", extra={\"landscape_id\": self.pk}\n            )\n            return None\n\n        return landscape_group.group\n\n    def __str__(self):\n        return self.name\n\n\nclass LandscapeDevelopmentStrategy(BaseModel):\n    objectives = models.TextField(blank=True, default=\"\")\n    opportunities = models.TextField(blank=True, default=\"\")\n    problem_situtation = models.TextField(blank=True, default=\"\")\n    intervention_strategy = models.TextField(blank=True, default=\"\")\n    landscape = models.ForeignKey(\n        Landscape, on_delete=models.CASCADE, related_name=\"associated_development_strategy\"\n    )\n\n\nclass LandscapeGroup(BaseModel):\n    \"\"\"\n    This model represents the association between a Landscape and a Group on\n    Terraso platform.\n    \"\"\"\n\n    landscape = models.ForeignKey(\n        Landscape, on_delete=models.CASCADE, related_name=\"associated_groups\"\n    )\n    group = models.ForeignKey(Group, on_delete=models.CASCADE, related_name=\"associated_landscapes\")\n\n    is_default_landscape_group = models.BooleanField(blank=True, default=False)\n    is_partnership = models.BooleanField(blank=True, default=False)\n    partnership_year = models.IntegerField(blank=True, null=True)\n\n    class Meta:\n        rules_permissions = {\n            \"add\": perm_rules.allowed_to_add_landscape_group,\n            \"delete\": perm_rules.allowed_to_delete_landscape_group,\n        }\n        constraints = (\n            models.UniqueConstraint(\n                fields=(\"group\", \"landscape\", \"is_partnership\"),\n                condition=models.Q(deleted_at__isnull=True),\n                name=\"unique_active_landscape_group\",\n            ),\n        )\n", "path": "terraso_backend/apps/core/models/landscapes.py"}], "after_files": [{"content": "# Copyright \u00a9 2021-2023 Technology Matters\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published\n# by the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program. If not, see https://www.gnu.org/licenses/.\n\nimport structlog\nfrom dirtyfields import DirtyFieldsMixin\nfrom django.db import models, transaction\n\nfrom apps.core import permission_rules as perm_rules\nfrom apps.core.gis.utils import (\n    calculate_geojson_centroid,\n    calculate_geojson_feature_area,\n)\nfrom apps.core.models.taxonomy_terms import TaxonomyTerm\n\nfrom .commons import BaseModel, SlugModel, validate_name\nfrom .groups import Group\nfrom .users import User\n\nlogger = structlog.get_logger(__name__)\n\n\nclass Landscape(SlugModel, DirtyFieldsMixin):\n    \"\"\"\n    This model represents a Landscape on Terraso platform.\n\n    A Landscape is a socio-ecological system that consists of natural\n    and/or human-modified ecosystems. Defined by its stakeholds, a\n    Landscape usually has geographical boundaries. It may correspond to,\n    or be a combination of, natural boundaries, distinct land features,\n    socially defined areas such as indigenous territories, and/or\n    jurisdictional and administrative boundaries. The boundaries of a\n    Landscape can cross several countries.\n    \"\"\"\n\n    fields_to_trim = [\"name\", \"description\"]\n\n    name = models.CharField(max_length=128, validators=[validate_name])\n    description = models.TextField(blank=True, default=\"\")\n    website = models.URLField(max_length=500, blank=True, default=\"\")\n    location = models.CharField(max_length=128, blank=True, default=\"\")\n    area_polygon = models.JSONField(blank=True, null=True)\n    email = models.EmailField(blank=True, default=\"\")\n    area_scalar_m2 = models.FloatField(blank=True, null=True)\n\n    created_by = models.ForeignKey(\n        User,\n        blank=True,\n        null=True,\n        on_delete=models.PROTECT,\n        related_name=\"created_landscapes\",\n    )\n    groups = models.ManyToManyField(Group, through=\"LandscapeGroup\")\n\n    area_types = models.JSONField(blank=True, null=True)\n    taxonomy_terms = models.ManyToManyField(TaxonomyTerm, blank=True)\n    population = models.IntegerField(blank=True, null=True)\n\n    PARTNERSHIP_STATUS_NONE = \"\"\n    PARTNERSHIP_STATUS_NO = \"no\"\n    PARTNERSHIP_STATUS_IN_PROGRESS = \"in-progress\"\n    PARTNERSHIP_STATUS_YES = \"yes\"\n\n    MEMBERSHIP_TYPES = (\n        (PARTNERSHIP_STATUS_NONE, \"None\"),\n        (PARTNERSHIP_STATUS_NO, \"No\"),\n        (PARTNERSHIP_STATUS_IN_PROGRESS, \"In Progress\"),\n        (PARTNERSHIP_STATUS_YES, \"Yes\"),\n    )\n    partnership_status = models.CharField(\n        max_length=32, choices=MEMBERSHIP_TYPES, blank=True, default=PARTNERSHIP_STATUS_NONE\n    )\n    profile_image = models.URLField(blank=True, default=\"\")\n    profile_image_description = models.TextField(blank=True, default=\"\")\n    center_coordinates = models.JSONField(blank=True, null=True)\n\n    field_to_slug = \"name\"\n\n    class Meta(SlugModel.Meta):\n        rules_permissions = {\n            \"change\": perm_rules.allowed_to_change_landscape,\n            \"delete\": perm_rules.allowed_to_delete_landscape,\n        }\n        _unique_fields = [\"name\"]\n        abstract = False\n\n    def save(self, *args, **kwargs):\n        dirty_fields = self.get_dirty_fields()\n        if self.area_polygon and \"area_polygon\" in dirty_fields:\n            area_scalar_m2 = calculate_geojson_feature_area(self.area_polygon)\n            if area_scalar_m2 is not None:\n                self.area_scalar_m2 = round(area_scalar_m2, 3)\n            self.center_coordinates = calculate_geojson_centroid(self.area_polygon)\n\n        with transaction.atomic():\n            creating = not Landscape.objects.filter(pk=self.pk).exists()\n\n            super().save(*args, **kwargs)\n\n            if creating and self.created_by:\n                group = Group(\n                    name=\"Group {}\".format(self.slug),\n                    description=\"\",\n                    created_by=self.created_by,\n                )\n                group.save()\n                landscape_group = LandscapeGroup(\n                    group=group, landscape=self, is_default_landscape_group=True\n                )\n                landscape_group.save()\n\n    def delete(self, *args, **kwargs):\n        default_group = self.get_default_group()\n\n        with transaction.atomic():\n            ret = super().delete(*args, **kwargs)\n            # default group should be deleted as well\n            if default_group is not None:\n                default_group.delete()\n\n        return ret\n\n    def get_default_group(self):\n        \"\"\"\n        A default Group in a Landscape is that Group where any\n        individual (associated or not with other Groups) is added when\n        associating directly with a Landscape.\n        \"\"\"\n        try:\n            # associated_groups is the related_name defined on\n            # LandscapeGroup relationship with Landscape. It returns a\n            # queryset of LandscapeGroup\n            landscape_group = self.associated_groups.get(is_default_landscape_group=True)\n        except LandscapeGroup.DoesNotExist:\n            logger.error(\n                \"Landscape has no default group, but it must have\", extra={\"landscape_id\": self.pk}\n            )\n            return None\n\n        return landscape_group.group\n\n    def __str__(self):\n        return self.name\n\n\nclass LandscapeDevelopmentStrategy(BaseModel):\n    objectives = models.TextField(blank=True, default=\"\")\n    opportunities = models.TextField(blank=True, default=\"\")\n    problem_situtation = models.TextField(blank=True, default=\"\")\n    intervention_strategy = models.TextField(blank=True, default=\"\")\n    landscape = models.ForeignKey(\n        Landscape, on_delete=models.CASCADE, related_name=\"associated_development_strategy\"\n    )\n\n\nclass LandscapeGroup(BaseModel):\n    \"\"\"\n    This model represents the association between a Landscape and a Group on\n    Terraso platform.\n    \"\"\"\n\n    landscape = models.ForeignKey(\n        Landscape, on_delete=models.CASCADE, related_name=\"associated_groups\"\n    )\n    group = models.ForeignKey(Group, on_delete=models.CASCADE, related_name=\"associated_landscapes\")\n\n    is_default_landscape_group = models.BooleanField(blank=True, default=False)\n    is_partnership = models.BooleanField(blank=True, default=False)\n    partnership_year = models.IntegerField(blank=True, null=True)\n\n    class Meta:\n        rules_permissions = {\n            \"add\": perm_rules.allowed_to_add_landscape_group,\n            \"delete\": perm_rules.allowed_to_delete_landscape_group,\n        }\n        constraints = (\n            models.UniqueConstraint(\n                fields=(\"group\", \"landscape\", \"is_partnership\"),\n                condition=models.Q(deleted_at__isnull=True),\n                name=\"unique_active_landscape_group\",\n            ),\n        )\n", "path": "terraso_backend/apps/core/models/landscapes.py"}]}