Skip to content

Commit f857e3e

Browse files
committed
add SDG model and corresponding schema
1 parent 0885257 commit f857e3e

File tree

10 files changed

+354
-0
lines changed

10 files changed

+354
-0
lines changed

api/models/Collaborative.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class Collaborative(models.Model):
4040
sectors = models.ManyToManyField(
4141
"api.Sector", blank=True, related_name="collaboratives"
4242
)
43+
sdgs = models.ManyToManyField("api.SDG", blank=True, related_name="collaboratives")
4344
contributors = models.ManyToManyField(
4445
"authorization.User", blank=True, related_name="contributed_collaboratives"
4546
)
@@ -71,5 +72,9 @@ def sectors_indexing(self):
7172
def tags_indexing(self):
7273
return [tag.value for tag in self.tags.all()] # type: ignore
7374

75+
@property
76+
def sdgs_indexing(self):
77+
return [sdg.code for sdg in self.sdgs.all()] # type: ignore
78+
7479
class Meta:
7580
db_table = "collaborative"

api/models/SDG.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import uuid
2+
from typing import Any
3+
4+
from django.db import models
5+
from django.utils.text import slugify
6+
7+
8+
class SDG(models.Model):
9+
"""Model for Sustainable Development Goals (SDGs)."""
10+
11+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
12+
name = models.CharField(max_length=100, unique=True, null=False, blank=False)
13+
code = models.CharField(
14+
max_length=10, unique=True, null=False, blank=False
15+
) # e.g., "SDG1", "SDG2"
16+
description = models.TextField(null=True, blank=True)
17+
slug = models.SlugField(max_length=100, null=True, blank=False, unique=True)
18+
19+
def save(self, *args: Any, **kwargs: Any) -> None:
20+
if not self.slug:
21+
self.slug = slugify(self.name)
22+
super().save(*args, **kwargs)
23+
24+
def __str__(self) -> str:
25+
return f"{self.code} - {self.name}"
26+
27+
class Meta:
28+
db_table = "sdg"
29+
verbose_name = "SDG"
30+
verbose_name_plural = "SDGs"
31+
ordering = ["code"]

api/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from api.models.ResourceChartImage import ResourceChartImage
2323
from api.models.ResourceMetadata import ResourceMetadata
2424
from api.models.ResourceSchema import ResourceSchema
25+
from api.models.SDG import SDG
2526
from api.models.Sector import Sector
2627
from api.models.SerializableJSONField import SerializableJSONField
2728
from api.models.UseCase import UseCase

api/schema/collaborative_schema.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from strawberry_django.pagination import OffsetPaginationInput
1717

1818
from api.models import (
19+
SDG,
1920
Collaborative,
2021
CollaborativeMetadata,
2122
CollaborativeOrganizationRelationship,
@@ -64,6 +65,7 @@ class UpdateCollaborativeMetadataInput:
6465
metadata: List[CollaborativeMetadataItemType]
6566
tags: Optional[List[str]]
6667
sectors: List[uuid.UUID]
68+
sdgs: Optional[List[uuid.UUID]]
6769

6870

6971
@strawberry_django.partial(Collaborative, fields="__all__", exclude=["datasets"])
@@ -77,6 +79,7 @@ class CollaborativeInputPartial:
7779
platform_url: Optional[str] = None
7880
tags: Optional[List[str]] = None
7981
sectors: Optional[List[uuid.UUID]] = None
82+
sdgs: Optional[List[uuid.UUID]] = None
8083
started_on: Optional[datetime.date] = None
8184
completed_on: Optional[datetime.date] = None
8285

@@ -219,6 +222,18 @@ def _update_collaborative_sectors(
219222
collaborative.save()
220223

221224

225+
@trace_resolver(
226+
name="update_collaborative_sdgs", attributes={"component": "collaborative"}
227+
)
228+
def _update_collaborative_sdgs(
229+
collaborative: Collaborative, sdgs: List[uuid.UUID]
230+
) -> None:
231+
sdgs_objs = SDG.objects.filter(id__in=sdgs)
232+
collaborative.sdgs.clear()
233+
collaborative.sdgs.add(*sdgs_objs)
234+
collaborative.save()
235+
236+
222237
@trace_resolver(
223238
name="add_update_collaborative_metadata",
224239
attributes={"component": "collaborative", "operation": "mutation"},
@@ -363,6 +378,8 @@ def add_update_collaborative_metadata(
363378
_update_collaborative_tags(collaborative, update_metadata_input.tags)
364379
_add_update_collaborative_metadata(collaborative, metadata_input)
365380
_update_collaborative_sectors(collaborative, update_metadata_input.sectors)
381+
if update_metadata_input.sdgs is not None:
382+
_update_collaborative_sdgs(collaborative, update_metadata_input.sdgs)
366383
return TypeCollaborative.from_django(collaborative)
367384

368385
@strawberry_django.mutation(handle_django_errors=False)

api/schema/schema.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import api.schema.resource_chart_schema
1818
import api.schema.resource_schema
1919
import api.schema.resoure_chart_image_schema
20+
import api.schema.sdg_schema
2021
import api.schema.sector_schema
2122
import api.schema.stats_schema
2223
import api.schema.tags_schema
@@ -59,6 +60,7 @@ def tags(self, info: Info) -> List[TypeTag]:
5960
api.schema.dataset_schema.Query,
6061
api.schema.resource_schema.Query,
6162
api.schema.access_model_schema.Query,
63+
api.schema.sdg_schema.Query,
6264
api.schema.sector_schema.Query,
6365
api.schema.resource_chart_schema.Query,
6466
api.schema.stats_schema.Query,
@@ -80,6 +82,7 @@ def tags(self, info: Info) -> List[TypeTag]:
8082
api.schema.dataset_schema.Mutation,
8183
api.schema.resource_schema.Mutation,
8284
api.schema.access_model_schema.Mutation,
85+
api.schema.sdg_schema.Mutation,
8386
api.schema.sector_schema.Mutation,
8487
api.schema.resource_chart_schema.Mutation,
8588
api.schema.usecase_schema.Mutation,

api/schema/sdg_schema.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import uuid
2+
from typing import Optional
3+
4+
import strawberry
5+
import strawberry_django
6+
from strawberry.types import Info
7+
from strawberry_django.pagination import OffsetPaginationInput
8+
9+
from api.models import SDG
10+
from api.types.type_sdg import SDGFilter, SDGOrder, TypeSDG
11+
12+
13+
@strawberry.input
14+
class SDGInput:
15+
name: str
16+
code: str
17+
description: Optional[str] = None
18+
19+
20+
@strawberry_django.partial(SDG)
21+
class SDGInputPartial:
22+
id: uuid.UUID
23+
name: Optional[str] = None
24+
code: Optional[str] = None
25+
description: Optional[str] = None
26+
slug: Optional[str] = None
27+
28+
29+
@strawberry.type(name="Query")
30+
class Query:
31+
sdgs: list[TypeSDG] = strawberry_django.field()
32+
33+
@strawberry_django.field
34+
def sdg(self, info: Info, id: uuid.UUID) -> Optional[TypeSDG]:
35+
"""Get SDG by ID."""
36+
try:
37+
sdg = SDG.objects.get(id=id)
38+
return TypeSDG.from_django(sdg)
39+
except SDG.DoesNotExist:
40+
raise ValueError(f"SDG with ID {id} does not exist.")
41+
42+
@strawberry_django.field(
43+
filters=SDGFilter,
44+
pagination=True,
45+
order=SDGOrder,
46+
)
47+
def all_sdgs(
48+
self,
49+
info: Info,
50+
filters: Optional[SDGFilter] = strawberry.UNSET,
51+
pagination: Optional[OffsetPaginationInput] = strawberry.UNSET,
52+
order: Optional[SDGOrder] = strawberry.UNSET,
53+
) -> list[TypeSDG]:
54+
"""Get all SDGs."""
55+
queryset = SDG.objects.all()
56+
57+
# Apply filters if provided
58+
if filters is not strawberry.UNSET:
59+
queryset = strawberry_django.filters.apply(filters, queryset, info)
60+
61+
# Apply ordering if provided
62+
if order is not strawberry.UNSET:
63+
queryset = strawberry_django.ordering.apply(order, queryset, info)
64+
65+
# Apply pagination if provided
66+
if pagination is not strawberry.UNSET:
67+
queryset = strawberry_django.pagination.apply(pagination, queryset)
68+
69+
return [TypeSDG.from_django(instance) for instance in queryset]
70+
71+
72+
@strawberry.type
73+
class Mutation:
74+
@strawberry_django.mutation(handle_django_errors=True)
75+
def create_sdg(self, info: Info, input: SDGInput) -> TypeSDG:
76+
"""Create a new SDG."""
77+
sdg = SDG(
78+
name=input.name,
79+
code=input.code,
80+
description=input.description,
81+
)
82+
sdg.save()
83+
return TypeSDG.from_django(sdg)
84+
85+
@strawberry_django.mutation(handle_django_errors=True)
86+
def update_sdg(self, info: Info, input: SDGInputPartial) -> Optional[TypeSDG]:
87+
"""Update an existing SDG."""
88+
try:
89+
sdg = SDG.objects.get(id=input.id)
90+
91+
# Update fields if provided
92+
if input.name is not None:
93+
sdg.name = input.name
94+
if input.code is not None:
95+
sdg.code = input.code
96+
if input.description is not None:
97+
sdg.description = input.description
98+
if input.slug is not None:
99+
sdg.slug = input.slug
100+
101+
sdg.save()
102+
return TypeSDG.from_django(sdg)
103+
except SDG.DoesNotExist:
104+
raise ValueError(f"SDG with ID {input.id} does not exist.")
105+
106+
@strawberry_django.mutation(handle_django_errors=False)
107+
def delete_sdg(self, info: Info, sdg_id: uuid.UUID) -> bool:
108+
"""Delete an SDG."""
109+
try:
110+
sdg = SDG.objects.get(id=sdg_id)
111+
sdg.delete()
112+
return True
113+
except SDG.DoesNotExist:
114+
raise ValueError(f"SDG with ID {sdg_id} does not exist.")

api/types/type_collaborative.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
)
2020
from api.types.type_dataset import TypeDataset, TypeTag
2121
from api.types.type_organization import TypeOrganization
22+
from api.types.type_sdg import TypeSDG
2223
from api.types.type_sector import TypeSector
2324
from api.types.type_usecase import TypeUseCase
2425
from api.utils.enums import CollaborativeStatus, OrganizationRelationshipType
@@ -135,6 +136,17 @@ def sectors(self) -> Optional[List["TypeSector"]]:
135136
except Exception:
136137
return []
137138

139+
@strawberry.field(description="Get SDGs associated with this collaborative.")
140+
def sdgs(self) -> Optional[List["TypeSDG"]]:
141+
"""Get SDGs associated with this collaborative."""
142+
try:
143+
queryset = self.sdgs.all() # type: ignore
144+
if not queryset.exists():
145+
return []
146+
return TypeSDG.from_django_list(queryset)
147+
except Exception:
148+
return []
149+
138150
@strawberry.field(description="Get tags associated with this collaborative.")
139151
def tags(self) -> Optional[List["TypeTag"]]:
140152
"""Get tags associated with this collaborative."""

api/types/type_sdg.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from typing import Optional
2+
3+
import strawberry
4+
import strawberry_django
5+
from strawberry import auto
6+
7+
from api.models import SDG
8+
from api.types.base_type import BaseType
9+
10+
11+
@strawberry_django.filter(SDG)
12+
class SDGFilter:
13+
"""Filter class for SDG model."""
14+
15+
id: auto
16+
code: auto
17+
name: auto
18+
19+
20+
@strawberry_django.order(SDG)
21+
class SDGOrder:
22+
"""Order class for SDG model."""
23+
24+
code: auto
25+
name: auto
26+
27+
28+
@strawberry_django.type(
29+
SDG,
30+
pagination=True,
31+
fields="__all__",
32+
filters=SDGFilter,
33+
order=SDGOrder, # type: ignore
34+
)
35+
class TypeSDG(BaseType):
36+
"""GraphQL type for SDG model."""
37+
38+
pass

api/utils/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class MetadataModels(models.TextChoices):
4040
DATASET = "DATASET"
4141
RESEOURCE = "RESOURCE"
4242
USECASE = "USECASE"
43+
COLLABORATIVE = "COLLABORATIVE"
4344

4445

4546
class MetadataStandards(models.TextChoices):

0 commit comments

Comments
 (0)