Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: project change intervals on preset change #1063

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
9 changes: 8 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,12 @@


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

__all__ = [
Expand All @@ -24,4 +29,6 @@
"ProjectSoilSettings",
"ProjectDepthInterval",
"SoilDataDepthInterval",
"LandPKSIntervalDefaults",
"NRCSIntervalDefaults",
]
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()
69 changes: 53 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,34 @@ 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 or not self.id:
# delete project intervals...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# delete project intervals...
# delete project intervals

ProjectDepthInterval.objects.filter(project=self).delete()
# delete related soil data
DepthDependentSoilData.delete_in_project(self.project.id)
# create new intervals
self.apply_preset()
return result

def apply_preset(self):
match self.depth_interval_preset:
case DepthIntervalPreset.LANDPKS.value:
self.make_intervals(LandPKSIntervalDefaults)
case DepthIntervalPreset.NRCS.value:
self.make_intervals(NRCSIntervalDefaults)

def make_intervals(self, presets):
intervals = [ProjectDepthInterval(project=self, **kwargs) for kwargs in presets]
return ProjectDepthInterval.objects.bulk_create(intervals)


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 © 2021-2023 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
14 changes: 14 additions & 0 deletions terraso_backend/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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, ProjectSoilSettings, SoilData

pytestmark = pytest.mark.django_db

Expand Down Expand Up @@ -131,3 +132,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 project.soil_settings.depth_intervals.all():
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