From 24e1589f775bb8b9574615abf84c89c55f1ce10f Mon Sep 17 00:00:00 2001 From: sandeshit Date: Wed, 15 Oct 2025 12:01:10 +0545 Subject: [PATCH 1/2] feat(graphql): add different type for public project --- apps/project/graphql/queries.py | 5 +- apps/project/graphql/types/public/__init__.py | 0 apps/project/graphql/types/public/types.py | 45 +++++++++++++++ apps/project/graphql/types/types.py | 1 + schema.graphql | 56 ++++++++++++++++++- 5 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 apps/project/graphql/types/public/__init__.py create mode 100644 apps/project/graphql/types/public/types.py diff --git a/apps/project/graphql/queries.py b/apps/project/graphql/queries.py index 482c82d8..116ad3dc 100644 --- a/apps/project/graphql/queries.py +++ b/apps/project/graphql/queries.py @@ -7,6 +7,7 @@ from apps.project.custom_options import get_custom_options from apps.project.graphql.inputs.inputs import ProjectNameInput +from apps.project.graphql.types.public.types import PublicProjectType from apps.project.models import Organization, Project, ProjectTypeEnum from utils.geo.raster_tile_server.config import RasterConfig, RasterTileServerNameEnum, RasterTileServerNameEnumWithoutCustom from utils.geo.vector_tile_server.config import VectorConfig, VectorTileServerNameEnum, VectorTileServerNameEnumWithoutCustom @@ -82,7 +83,7 @@ def default_custom_options(self, project_type: ProjectTypeEnum) -> list[CustomOp # Private -------------------- project: ProjectType = strawberry_django.field(extensions=[IsAuthenticated()]) - public_project: ProjectType = strawberry_django.field() + public_project: PublicProjectType = strawberry_django.field() project_asset: ProjectAssetType = strawberry_django.field(extensions=[IsAuthenticated()]) @@ -145,7 +146,7 @@ def projects( ).all() @strawberry_django.offset_paginated( - OffsetPaginated[ProjectType], + OffsetPaginated[PublicProjectType], order=ProjectOrder, filters=ProjectFilter, ) diff --git a/apps/project/graphql/types/public/__init__.py b/apps/project/graphql/types/public/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/project/graphql/types/public/types.py b/apps/project/graphql/types/public/types.py new file mode 100644 index 00000000..c447a957 --- /dev/null +++ b/apps/project/graphql/types/public/types.py @@ -0,0 +1,45 @@ +import datetime + +import strawberry +import strawberry_django + +from apps.project.graphql.types.types import GeometryType, OrganizationType, ProjectAssetType, ProjectExportAssetTypeMixin +from apps.project.models import Project + + +# Project +@strawberry_django.type(Project) +class PublicProjectType(ProjectExportAssetTypeMixin): + id: strawberry.ID + firebase_id: str + project_type: strawberry.auto + requesting_organization_id: strawberry.ID + requesting_organization: OrganizationType + region: strawberry.auto + description: strawberry.auto + image: ProjectAssetType | None + status: strawberry.auto + created_at: datetime.datetime + modified_at: datetime.datetime + + total_area: strawberry.auto + aoi_geometry: GeometryType | None + + number_of_contributor_users: strawberry.auto + + @strawberry_django.field( + description=str(Project._meta.get_field("progress").help_text), # type: ignore[reportAttributeAccessIssue] + ) + def progress(self, project: strawberry.Parent[Project]) -> float: + return project.progress / 100 + + @strawberry_django.field( + only=["topic", "region", "project_number", "requesting_organization__name", "project_type"], + annotate={"generated_name": Project.generate_name_query()}, + description="Project name generated from topic, region, project number, and requesting organization name.", + ) + def name(self, project: strawberry.Parent[Project]) -> str: + if getattr(project, "generated_name", None): + return project.generated_name # type: ignore[reportAttributeAccessIssue] + # This is used for mutation response + return project.generate_name() diff --git a/apps/project/graphql/types/types.py b/apps/project/graphql/types/types.py index a1ee2ed9..e60896b3 100644 --- a/apps/project/graphql/types/types.py +++ b/apps/project/graphql/types/types.py @@ -86,6 +86,7 @@ async def asset_type_specifics( # Project +# NOTE: This is also used for public project type. @strawberry.interface class ProjectExportAssetTypeMixin: @strawberry.field diff --git a/schema.graphql b/schema.graphql index 8ab6df98..30caf219 100644 --- a/schema.graphql +++ b/schema.graphql @@ -2003,6 +2003,58 @@ type ProjectVectorTileServerCustomConfig { url: String! } +"""Model representing the project.""" +type PublicProjectType implements ProjectExportAssetTypeMixin { + aoiGeometry: GeometryType + createdAt: DateTime! + description: String + exportAggregatedResults: ProjectAssetType + exportAggregatedResultsWithGeometry: ProjectAssetType + exportAreaOfInterest: ProjectAssetType + exportGroups: ProjectAssetType + exportHistory: ProjectAssetType + exportHotTaskingManagerGeometries: ProjectAssetType + exportModerateToHighAgreementYesMaybeGeometries: ProjectAssetType + exportResults: ProjectAssetType + exportTasks: ProjectAssetType + exportUsers: ProjectAssetType + firebaseId: String! + id: ID! + image: ProjectAssetType + modifiedAt: DateTime! + + """ + Project name generated from topic, region, project number, and requesting organization name. + """ + name: String! + + """Number of users who made contributions to this project""" + numberOfContributorUsers: Int! + + """Percentage of the required contribution that has been completed""" + progress: Float! + projectType: ProjectTypeEnum! + region: String! + + """Which group, institution or community is requesting this project?""" + requestingOrganization: OrganizationType! + + """Which group, institution or community is requesting this project?""" + requestingOrganizationId: ID! + status: ProjectStatusEnum! + totalArea: Float +} + +type PublicProjectTypeOffsetPaginated { + pageInfo: OffsetPaginationInfo! + + """List of paginated results.""" + results: [PublicProjectType!]! + + """Total count of existing results.""" + totalCount: Int! +} + type Query { communityFilteredStats(dateRange: DateRangeInput = null): CommunityFilteredStats! communityStats: CommunityStatsType! @@ -2033,8 +2085,8 @@ type Query { projects(includeAll: Boolean! = false, filters: ProjectFilter, order: ProjectOrder, pagination: OffsetPaginationInput): ProjectTypeOffsetPaginated! @isAuthenticated publicOrganization(id: ID!): OrganizationType! publicOrganizations(filters: OrganizationFilter, order: OrganizationOrder, pagination: OffsetPaginationInput): OrganizationTypeOffsetPaginated! - publicProject(id: ID!): ProjectType! - publicProjects(filters: ProjectFilter, order: ProjectOrder, pagination: OffsetPaginationInput): ProjectTypeOffsetPaginated! + publicProject(id: ID!): PublicProjectType! + publicProjects(filters: ProjectFilter, order: ProjectOrder, pagination: OffsetPaginationInput): PublicProjectTypeOffsetPaginated! tileServers: RasterTileServersType! @isAuthenticated tutorial(id: ID!): TutorialType! @isAuthenticated tutorialAsset(id: ID!): TutorialAssetType! @isAuthenticated From 165f0488518f239a2def89cda7083a6b3e9f47c2 Mon Sep 17 00:00:00 2001 From: sandeshit Date: Wed, 15 Oct 2025 16:00:06 +0545 Subject: [PATCH 2/2] fixup! feat(graphql): add different type for public project --- apps/project/graphql/types/public/types.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/project/graphql/types/public/types.py b/apps/project/graphql/types/public/types.py index c447a957..dfeee93b 100644 --- a/apps/project/graphql/types/public/types.py +++ b/apps/project/graphql/types/public/types.py @@ -39,7 +39,4 @@ def progress(self, project: strawberry.Parent[Project]) -> float: description="Project name generated from topic, region, project number, and requesting organization name.", ) def name(self, project: strawberry.Parent[Project]) -> str: - if getattr(project, "generated_name", None): - return project.generated_name # type: ignore[reportAttributeAccessIssue] - # This is used for mutation response - return project.generate_name() + return project.generated_name # type: ignore[reportAttributeAccessIssue]