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: soilDataPush mutation #1472

Merged
merged 9 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
40 changes: 25 additions & 15 deletions terraso_backend/apps/graphql/schema/commons.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@

import enum
import json
from typing import Optional
from typing import Any, Optional

import structlog
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
from django.db import IntegrityError
from django.db import IntegrityError, models
from graphene import Connection, Int, relay
from graphene.types.generic import GenericScalar
from graphql import get_nullable_type
Expand Down Expand Up @@ -178,7 +178,7 @@ def not_found(cls, model=None, field=None, msg=None):


class BaseWriteMutation(BaseAuthenticatedMutation):
skip_field_validation: Optional[str] = None
skip_field_validation: Optional[list[str]] = None

@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
Expand All @@ -202,16 +202,12 @@ def mutate_and_get_payload(cls, root, info, **kwargs):
result_class = cls.result_class or cls.model_class
result_instance = kwargs.pop("result_instance", model_instance)

for attr, value in kwargs.items():
if isinstance(value, enum.Enum):
value = value.value
setattr(model_instance, attr, value)

try:
kwargs = {}
if cls.skip_field_validation is not None:
kwargs["exclude"] = cls.skip_field_validation
model_instance.full_clean(**kwargs)
BaseWriteMutation.assign_graphql_fields_to_model_instance(
model_instance=model_instance,
fields=kwargs,
skip_field_validation=cls.skip_field_validation,
)
except ValidationError as exc:
logger.info(
"Attempt to mutate an model, but it's invalid",
Expand All @@ -220,9 +216,6 @@ def mutate_and_get_payload(cls, root, info, **kwargs):
raise GraphQLValidationException.from_validation_error(
exc, model_name=cls.model_class.__name__
)

try:
model_instance.save()
except IntegrityError as exc:
logger.info(
"Attempt to mutate an model, but it's not unique",
Expand Down Expand Up @@ -251,6 +244,23 @@ def mutate_and_get_payload(cls, root, info, **kwargs):
def is_update(cls, data):
return "id" in data

@staticmethod
def assign_graphql_fields_to_model_instance(
model_instance: models.Model,
fields: dict[str, Any],
skip_field_validation: Optional[list[str]] = None,
):
for attr, value in fields.items():
if isinstance(value, enum.Enum):
value = value.value
setattr(model_instance, attr, value)

clean_args = {}
if skip_field_validation is not None:
clean_args["exclude"] = skip_field_validation
model_instance.full_clean(**clean_args)
model_instance.save()

@staticmethod
def remove_null_fields(kwargs, options=[str]):
"""It seems like for some fields, if the frontend does not pass an argument, the
Expand Down
109 changes: 105 additions & 4 deletions terraso_backend/apps/graphql/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1644,6 +1644,7 @@ type Mutations {
markProjectSeen(input: ProjectMarkSeenMutationInput!): ProjectMarkSeenMutationPayload!
updateSoilData(input: SoilDataUpdateMutationInput!): SoilDataUpdateMutationPayload!
updateDepthDependentSoilData(input: DepthDependentSoilDataUpdateMutationInput!): DepthDependentSoilDataUpdateMutationPayload!
pushSoilData(input: SoilDataPushInput!): SoilDataPushPayload!
updateSoilDataDepthInterval(input: SoilDataUpdateDepthIntervalMutationInput!): SoilDataUpdateDepthIntervalMutationPayload!
deleteSoilDataDepthInterval(input: SoilDataDeleteDepthIntervalMutationInput!): SoilDataDeleteDepthIntervalMutationPayload!
updateProjectSoilSettings(input: ProjectSoilSettingsUpdateMutationInput!): ProjectSoilSettingsUpdateMutationPayload!
Expand Down Expand Up @@ -2220,7 +2221,7 @@ type SoilDataUpdateMutationPayload {
}

input SoilDataUpdateMutationInput {
siteId: ID!
depthIntervalPreset: SoilIdSoilDataDepthIntervalPresetChoices
downSlope: SoilIdSoilDataDownSlopeChoices
crossSlope: SoilIdSoilDataCrossSlopeChoices
bedrock: Int
Expand All @@ -2238,7 +2239,7 @@ input SoilDataUpdateMutationInput {
soilDepthSelect: SoilIdSoilDataSoilDepthSelectChoices
landCoverSelect: SoilIdSoilDataLandCoverSelectChoices
grazingSelect: SoilIdSoilDataGrazingSelectChoices
depthIntervalPreset: SoilIdSoilDataDepthIntervalPresetChoices
siteId: ID!
clientMutationId: String
}

Expand All @@ -2249,7 +2250,6 @@ type DepthDependentSoilDataUpdateMutationPayload {
}

input DepthDependentSoilDataUpdateMutationInput {
siteId: ID!
depthInterval: DepthIntervalInput!
texture: SoilIdDepthDependentSoilDataTextureChoices
clayPercent: Int
Expand All @@ -2273,17 +2273,117 @@ input DepthDependentSoilDataUpdateMutationInput {
soilOrganicMatterTesting: SoilIdDepthDependentSoilDataSoilOrganicMatterTestingChoices
sodiumAbsorptionRatio: Decimal
carbonates: SoilIdDepthDependentSoilDataCarbonatesChoices
siteId: ID!
clientMutationId: String
}

type SoilDataPushPayload {
errors: GenericScalar
results: [SoilDataPushEntry!]!
clientMutationId: String
}

type SoilDataPushEntry {
siteId: ID!
result: SoilDataPushEntryResult!
}

union SoilDataPushEntryResult = SoilDataPushEntrySuccess | SoilDataPushEntryFailure

type SoilDataPushEntrySuccess {
site: SiteNode!
}

type SoilDataPushEntryFailure {
reason: SoilDataPushFailureReason!
}

enum SoilDataPushFailureReason {
DOES_NOT_EXIST
NOT_ALLOWED
INVALID_DATA
}

input SoilDataPushInput {
soilDataEntries: [SoilDataPushInputEntry!]!
clientMutationId: String
}

input SoilDataPushInputEntry {
siteId: ID!
soilData: SoilDataPushInputSoilData!
}

input SoilDataPushInputSoilData {
depthIntervalPreset: SoilIdSoilDataDepthIntervalPresetChoices
downSlope: SoilIdSoilDataDownSlopeChoices
crossSlope: SoilIdSoilDataCrossSlopeChoices
bedrock: Int
slopeLandscapePosition: SoilIdSoilDataSlopeLandscapePositionChoices
slopeAspect: Int
slopeSteepnessSelect: SoilIdSoilDataSlopeSteepnessSelectChoices
slopeSteepnessPercent: Int
slopeSteepnessDegree: Int
surfaceCracksSelect: SoilIdSoilDataSurfaceCracksSelectChoices
surfaceSaltSelect: SoilIdSoilDataSurfaceSaltSelectChoices
floodingSelect: SoilIdSoilDataFloodingSelectChoices
limeRequirementsSelect: SoilIdSoilDataLimeRequirementsSelectChoices
surfaceStoninessSelect: SoilIdSoilDataSurfaceStoninessSelectChoices
waterTableDepthSelect: SoilIdSoilDataWaterTableDepthSelectChoices
soilDepthSelect: SoilIdSoilDataSoilDepthSelectChoices
landCoverSelect: SoilIdSoilDataLandCoverSelectChoices
grazingSelect: SoilIdSoilDataGrazingSelectChoices
depthDependentData: [SoilDataPushInputDepthDependentData!]!
depthIntervals: [SoilDataPushInputDepthInterval!]!
deletedDepthIntervals: [DepthIntervalInput!]!
}

input SoilDataPushInputDepthDependentData {
depthInterval: DepthIntervalInput!
texture: SoilIdDepthDependentSoilDataTextureChoices
clayPercent: Int
rockFragmentVolume: SoilIdDepthDependentSoilDataRockFragmentVolumeChoices
colorHue: Float
colorValue: Float
colorChroma: Float
colorPhotoUsed: Boolean
colorPhotoSoilCondition: SoilIdDepthDependentSoilDataColorPhotoSoilConditionChoices
colorPhotoLightingCondition: SoilIdDepthDependentSoilDataColorPhotoLightingConditionChoices
conductivity: Decimal
conductivityTest: SoilIdDepthDependentSoilDataConductivityTestChoices
conductivityUnit: SoilIdDepthDependentSoilDataConductivityUnitChoices
structure: SoilIdDepthDependentSoilDataStructureChoices
ph: Decimal
phTestingSolution: SoilIdDepthDependentSoilDataPhTestingSolutionChoices
phTestingMethod: SoilIdDepthDependentSoilDataPhTestingMethodChoices
soilOrganicCarbon: Decimal
soilOrganicMatter: Decimal
soilOrganicCarbonTesting: SoilIdDepthDependentSoilDataSoilOrganicCarbonTestingChoices
soilOrganicMatterTesting: SoilIdDepthDependentSoilDataSoilOrganicMatterTestingChoices
sodiumAbsorptionRatio: Decimal
carbonates: SoilIdDepthDependentSoilDataCarbonatesChoices
}

input SoilDataPushInputDepthInterval {
label: String
depthInterval: DepthIntervalInput!
soilTextureEnabled: Boolean
soilColorEnabled: Boolean
carbonatesEnabled: Boolean
phEnabled: Boolean
soilOrganicCarbonMatterEnabled: Boolean
electricalConductivityEnabled: Boolean
sodiumAdsorptionRatioEnabled: Boolean
soilStructureEnabled: Boolean
}

type SoilDataUpdateDepthIntervalMutationPayload {
errors: GenericScalar
soilData: SoilDataNode
clientMutationId: String
}

input SoilDataUpdateDepthIntervalMutationInput {
siteId: ID!
label: String
depthInterval: DepthIntervalInput!
soilTextureEnabled: Boolean
Expand All @@ -2294,6 +2394,7 @@ input SoilDataUpdateDepthIntervalMutationInput {
electricalConductivityEnabled: Boolean
sodiumAdsorptionRatioEnabled: Boolean
soilStructureEnabled: Boolean
siteId: ID!
applyToIntervals: [DepthIntervalInput!] = null
clientMutationId: String
}
Expand Down
2 changes: 2 additions & 0 deletions terraso_backend/apps/graphql/schema/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
SoilDataUpdateDepthIntervalMutation,
SoilDataUpdateMutation,
)
from apps.soil_id.graphql.soil_data.push_mutation import SoilDataPush
from apps.soil_id.graphql.soil_id.queries import soil_id
from apps.soil_id.graphql.soil_project.mutations import (
ProjectSoilSettingsDeleteDepthIntervalMutation,
Expand Down Expand Up @@ -191,6 +192,7 @@ class Mutations(graphene.ObjectType):
mark_project_seen = ProjectMarkSeenMutation.Field()
update_soil_data = SoilDataUpdateMutation.Field()
update_depth_dependent_soil_data = DepthDependentSoilDataUpdateMutation.Field()
push_soil_data = SoilDataPush.Field()
update_soil_data_depth_interval = SoilDataUpdateDepthIntervalMutation.Field()
delete_soil_data_depth_interval = SoilDataDeleteDepthIntervalMutation.Field()
update_project_soil_settings = ProjectSoilSettingsUpdateMutation.Field()
Expand Down
65 changes: 8 additions & 57 deletions terraso_backend/apps/soil_id/graphql/soil_data/mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@
from apps.project_management.permission_rules import Context
from apps.project_management.permission_table import SiteAction, check_site_permission
from apps.soil_id.graphql.soil_data.queries import (
DepthDependentSoilDataNode,
SoilDataDepthIntervalNode,
SoilDataNode,
)
from apps.soil_id.graphql.soil_data.types import (
SoilDataDepthDependentInputs,
SoilDataDepthIntervalInputs,
SoilDataInputs,
)
from apps.soil_id.graphql.types import DepthIntervalInput
from apps.soil_id.models.depth_dependent_soil_data import DepthDependentSoilData
from apps.soil_id.models.soil_data import SoilData, SoilDataDepthInterval
Expand All @@ -36,18 +40,8 @@ class SoilDataUpdateDepthIntervalMutation(BaseWriteMutation):
model_class = SoilDataDepthIntervalNode
result_class = SoilData

class Input:
class Input(SoilDataDepthIntervalInputs):
site_id = graphene.ID(required=True)
label = graphene.String()
depth_interval = graphene.Field(DepthIntervalInput, required=True)
soil_texture_enabled = graphene.Boolean()
soil_color_enabled = graphene.Boolean()
carbonates_enabled = graphene.Boolean()
ph_enabled = graphene.Boolean()
soil_organic_carbon_matter_enabled = graphene.Boolean()
electrical_conductivity_enabled = graphene.Boolean()
sodium_adsorption_ratio_enabled = graphene.Boolean()
soil_structure_enabled = graphene.Boolean()
apply_to_intervals = graphene.Field(graphene.List(graphene.NonNull(DepthIntervalInput)))

@classmethod
Expand Down Expand Up @@ -135,26 +129,8 @@ class SoilDataUpdateMutation(BaseWriteMutation):
soil_data = graphene.Field(SoilDataNode)
model_class = SoilData

class Input:
class Input(SoilDataInputs):
site_id = graphene.ID(required=True)
down_slope = SoilDataNode.down_slope_enum()
cross_slope = SoilDataNode.cross_slope_enum()
bedrock = graphene.Int()
slope_landscape_position = SoilDataNode.slope_landscape_position_enum()
slope_aspect = graphene.Int()
slope_steepness_select = SoilDataNode.slope_steepness_enum()
slope_steepness_percent = graphene.Int()
slope_steepness_degree = graphene.Int()
surface_cracks_select = SoilDataNode.surface_cracks_enum()
surface_salt_select = SoilDataNode.surface_salt_enum()
flooding_select = SoilDataNode.flooding_enum()
lime_requirements_select = SoilDataNode.lime_requirements_enum()
surface_stoniness_select = SoilDataNode.surface_stoniness_enum()
water_table_depth_select = SoilDataNode.water_table_depth_enum()
soil_depth_select = SoilDataNode.soil_depth_enum()
land_cover_select = SoilDataNode.land_cover_enum()
grazing_select = SoilDataNode.grazing_enum()
depth_interval_preset = SoilDataNode.depth_interval_preset_enum()

@classmethod
def mutate_and_get_payload(cls, root, info, site_id, **kwargs):
Expand Down Expand Up @@ -184,33 +160,8 @@ class DepthDependentSoilDataUpdateMutation(BaseWriteMutation):
model_class = DepthDependentSoilData
result_class = SoilData

class Input:
class Input(SoilDataDepthDependentInputs):
site_id = graphene.ID(required=True)
depth_interval = graphene.Field(DepthIntervalInput, required=True)
texture = DepthDependentSoilDataNode.texture_enum()
clay_percent = graphene.Int()
rock_fragment_volume = DepthDependentSoilDataNode.rock_fragment_volume_enum()
color_hue = graphene.Float()
color_value = graphene.Float()
color_chroma = graphene.Float()
color_photo_used = graphene.Boolean()
color_photo_soil_condition = DepthDependentSoilDataNode.color_photo_soil_condition_enum()
color_photo_lighting_condition = (
DepthDependentSoilDataNode.color_photo_lighting_condition_enum()
)
conductivity = graphene.Decimal()
conductivity_test = DepthDependentSoilDataNode.conductivity_test_enum()
conductivity_unit = DepthDependentSoilDataNode.conductivity_unit_enum()
structure = DepthDependentSoilDataNode.structure_enum()
ph = graphene.Decimal()
ph_testing_solution = DepthDependentSoilDataNode.ph_testing_solution_enum()
ph_testing_method = DepthDependentSoilDataNode.ph_testing_method_enum()
soil_organic_carbon = graphene.Decimal()
soil_organic_matter = graphene.Decimal()
soil_organic_carbon_testing = DepthDependentSoilDataNode.soil_organic_carbon_testing_enum()
soil_organic_matter_testing = DepthDependentSoilDataNode.soil_organic_matter_testing_enum()
sodium_absorption_ratio = graphene.Decimal()
carbonates = DepthDependentSoilDataNode.carbonates_enum()

@classmethod
def mutate_and_get_payload(cls, root, info, site_id, depth_interval, **kwargs):
Expand Down
Loading
Loading