Skip to content

Commit

Permalink
feat: Delete project data on a preset change (#1081)
Browse files Browse the repository at this point in the history
* fix: Create project soil settings on project creation

Ran into a bug on the frontend when I tried creating a new
project. This commit makes sure that the project has a soil project
settings associated it. It also allows this to be disabled if we're
creating a project from somewhere where the project soil settings is
not necessary.

* feat: Apply changes on project preset change

* test: Add test to make sure depth dependent data preserved

* test: Add test to make sure depth dependent data deleted

* test: Test updating project preset to custom

* fix: Restrict project depth intervals on preset

* feat: Don't create project intervals on project soil creation

For projects that don't have the custom preset, the goal is to manage
these on the frontend.

* fix: Apply PR suggestions
  • Loading branch information
David Code Howard authored Jan 10, 2024
1 parent 097cbca commit 20ee421
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 64 deletions.
3 changes: 0 additions & 3 deletions terraso_backend/apps/graphql/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1086,9 +1086,6 @@ enum SoilIdSoilDataDepthIntervalPresetChoices {
"""Nrcs"""
NRCS

"""None"""
NONE

"""Custom"""
CUSTOM
}
Expand Down
12 changes: 7 additions & 5 deletions terraso_backend/apps/soil_id/graphql/soil_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
from apps.project_management.models.sites import Site
from apps.soil_id.models.depth_dependent_soil_data import DepthDependentSoilData
from apps.soil_id.models.project_soil_settings import (
LandPKSPresets,
NRCSPresets,
LandPKSIntervalDefaults,
NRCSIntervalDefaults,
ProjectDepthInterval,
ProjectSoilSettings,
)
Expand Down Expand Up @@ -452,7 +452,9 @@ def mutate_and_get_payload(cls, root, info, project_id, depth_interval, **kwargs
project = cls.get_or_throw(Project, "id", project_id)

user = info.context.user
if not user.has_perm(Project.get_perm("change"), project):
if not user.has_perm(Project.get_perm("change"), project) or not user.has_perm(
ProjectSoilSettings.get_perm("change_project_depth_interval"), project.soil_settings
):
raise cls.not_allowed(MutationTypes.UPDATE)

with transaction.atomic():
Expand Down Expand Up @@ -531,9 +533,9 @@ def mutate_and_get_payload(cls, root, info, site_id, preset, **kwargs):
# insert new intervals
match preset.value:
case "LANDPKS":
preset_values = LandPKSPresets
preset_values = LandPKSIntervalDefaults
case "NRCS":
preset_values = NRCSPresets
preset_values = NRCSIntervalDefaults
case "CUSTOM" | "NONE":
preset_values = []

Expand Down
11 changes: 10 additions & 1 deletion terraso_backend/apps/soil_id/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@


from .depth_dependent_soil_data import DepthDependentSoilData
from .project_soil_settings import ProjectDepthInterval, ProjectSoilSettings
from .project_soil_settings import (
DepthIntervalPreset,
LandPKSIntervalDefaults,
NRCSIntervalDefaults,
ProjectDepthInterval,
ProjectSoilSettings,
)
from .soil_data import SoilData, SoilDataDepthInterval

__all__ = [
Expand All @@ -24,4 +30,7 @@
"ProjectSoilSettings",
"ProjectDepthInterval",
"SoilDataDepthInterval",
"LandPKSIntervalDefaults",
"NRCSIntervalDefaults",
"DepthIntervalPreset",
]
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,7 @@ class CarbonateResponse(models.TextChoices):
)

carbonates = models.CharField(blank=True, null=True, choices=CarbonateResponse.choices)

@classmethod
def delete_in_project(cls, project_id):
return cls.objects.filter(soil_data__site__project__id=project_id).delete()
59 changes: 43 additions & 16 deletions terraso_backend/apps/soil_id/models/project_soil_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see https://www.gnu.org/licenses/.

from django.db import models
from dirtyfields import DirtyFieldsMixin
from django.db import models, transaction

from apps.core.models.commons import BaseModel
from apps.project_management.models.projects import Project
from apps.soil_id import permission_rules
from apps.soil_id.models.depth_dependent_soil_data import DepthDependentSoilData
from apps.soil_id.models.depth_interval import BaseDepthInterval


Expand All @@ -27,26 +30,32 @@ class DepthIntervalPreset(models.TextChoices):
CUSTOM = "CUSTOM"


LandPKSPresets = [
dict(depth_interval_start=0, depth_interval_end=10, label="0-10 cm"),
dict(depth_interval_start=10, depth_interval_end=20, label="10-20 cm"),
dict(depth_interval_start=20, depth_interval_end=50, label="20-50 cm"),
dict(depth_interval_start=50, depth_interval_end=70, label="50-70 cm"),
dict(depth_interval_start=70, depth_interval_end=100, label="70-100 cm"),
dict(depth_interval_start=100, depth_interval_end=200, label="100-200 cm"),
LandPKSIntervalDefaults = [
dict(depth_interval_start=0, depth_interval_end=10),
dict(depth_interval_start=10, depth_interval_end=20),
dict(depth_interval_start=20, depth_interval_end=50),
dict(depth_interval_start=50, depth_interval_end=70),
dict(depth_interval_start=70, depth_interval_end=100),
dict(depth_interval_start=100, depth_interval_end=200),
]

NRCSPresets = [
dict(depth_interval_start=0, depth_interval_end=5, label="0-5 cm"),
dict(depth_interval_start=5, depth_interval_end=15, label="5-15 cm"),
dict(depth_interval_start=15, depth_interval_end=30, label="15-30 cm"),
dict(depth_interval_start=30, depth_interval_end=60, label="30-60 cm"),
dict(depth_interval_start=60, depth_interval_end=100, label="60-100 cm"),
dict(depth_interval_start=100, depth_interval_end=200, label="100-200 cm"),
NRCSIntervalDefaults = [
dict(depth_interval_start=0, depth_interval_end=5),
dict(depth_interval_start=5, depth_interval_end=15),
dict(depth_interval_start=15, depth_interval_end=30),
dict(depth_interval_start=30, depth_interval_end=60),
dict(depth_interval_start=60, depth_interval_end=100),
dict(depth_interval_start=100, depth_interval_end=200),
]


class ProjectSoilSettings(BaseModel):
class ProjectSoilSettings(BaseModel, DirtyFieldsMixin):
class Meta(BaseModel.Meta):
abstract = False
rules_permissions = {
"change_project_depth_interval": permission_rules.allowed_to_change_depth_interval
}

project = models.OneToOneField(Project, on_delete=models.CASCADE, related_name="soil_settings")

class MeasurementUnit(models.TextChoices):
Expand Down Expand Up @@ -77,6 +86,24 @@ class MeasurementUnit(models.TextChoices):
photos_required = models.BooleanField(blank=True, default=False)
notes_required = models.BooleanField(blank=True, default=False)

@property
def is_custom_preset(self):
return self.depth_interval_preset == DepthIntervalPreset.CUSTOM

def save(self, *args, **kwargs):
dirty_fields = self.get_dirty_fields()
with transaction.atomic():
result = super().save(*args, **kwargs)
if (
"depth_interval_preset" in dirty_fields
and dirty_fields.get("depth_interval_preset") != self.depth_interval_preset
):
# delete project intervals
ProjectDepthInterval.objects.filter(project=self).delete()
# delete related soil data
DepthDependentSoilData.delete_in_project(self.project.id)
return result


class ProjectDepthInterval(BaseModel, BaseDepthInterval):
project = models.ForeignKey(
Expand Down
9 changes: 7 additions & 2 deletions terraso_backend/apps/soil_id/models/soil_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from apps.core.models.commons import BaseModel
from apps.project_management.models.sites import Site
from apps.soil_id.models.depth_interval import BaseDepthInterval
from apps.soil_id.models.project_soil_settings import DepthIntervalPreset


def default_depth_intervals():
Expand Down Expand Up @@ -205,8 +204,14 @@ class Grazing(models.TextChoices):
WILDLIFE_GRASSLANDS = "WILDLIFE_GRASSLANDS", "Wildlife (grasslands, giraffes, ibex)"

grazing_select = models.CharField(blank=True, null=True, choices=Grazing.choices)

class SoilDataDepthIntervalPreset(models.TextChoices):
LANDPKS = "LANDPKS"
NRCS = "NRCS"
CUSTOM = "CUSTOM"

depth_interval_preset = models.CharField(
choices=DepthIntervalPreset.choices, blank=True, null=True
choices=SoilDataDepthIntervalPreset.choices, blank=True, null=True
)


Expand Down
23 changes: 23 additions & 0 deletions terraso_backend/apps/soil_id/permission_rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright © 2024 Technology Matters
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see https://www.gnu.org/licenses/.

import rules


@rules.predicate
def allowed_to_change_depth_interval(user, soil_settings):
if not soil_settings:
return False
return soil_settings.is_custom_preset
19 changes: 19 additions & 0 deletions terraso_backend/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
from apps.core.gis.utils import DEFAULT_CRS
from apps.core.models import User
from apps.project_management.models import Project, Site
from apps.soil_id.models import (
DepthDependentSoilData,
LandPKSIntervalDefaults,
ProjectSoilSettings,
SoilData,
)

pytestmark = pytest.mark.django_db

Expand Down Expand Up @@ -131,3 +137,16 @@ def project_user_w_role(request, project: Project):
user = mixer.blend(User)
project.add_user_with_role(user, request.param)
return user


@pytest.fixture
def site_with_soil_data(request, project_manager: User, project: Project, project_site: Site):
ProjectSoilSettings.objects.create(project=project)
SoilData.objects.create(site=project_site)
for interval in LandPKSIntervalDefaults:
DepthDependentSoilData.objects.create(
soil_data=project_site.soil_data,
depth_interval_start=interval["depth_interval_start"],
depth_interval_end=interval["depth_interval_end"],
)
return project_site
Loading

0 comments on commit 20ee421

Please sign in to comment.