Skip to content

Commit efa68e1

Browse files
committed
feat(street): Add tutorial schema for street
1 parent b8c9df6 commit efa68e1

File tree

6 files changed

+234
-8
lines changed

6 files changed

+234
-8
lines changed

apps/project/graphql/inputs/inputs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .project_types.compare import CompareProjectPropertyInput
1919
from .project_types.completeness import CompletenessProjectPropertyInput
2020
from .project_types.find import FindProjectPropertyInput
21+
from .project_types.street import StreetProjectPropertyInput
2122
from .project_types.validate import ValidateProjectPropertyInput
2223
from .project_types.validate_image import ValidateImageProjectPropertyInput
2324

@@ -51,6 +52,7 @@ class ProjectTypeSpecificInput:
5152
completeness: CompletenessProjectPropertyInput | None = strawberry.UNSET
5253
validate: ValidateProjectPropertyInput | None = strawberry.UNSET
5354
validate_image: ValidateImageProjectPropertyInput | None = strawberry.UNSET
55+
street: StreetProjectPropertyInput | None = strawberry.UNSET
5456

5557

5658
# NOTE: Make sure this matches with the serializers ../serializers.py

apps/project/graphql/inputs/project_types/street.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@
33
from project_types.street import project as street_project
44

55

6+
@strawberry.experimental.pydantic.input(model=street_project.StreetMappilaryImageFilters, all_fields=True)
7+
class StreetMappilaryImageFiltersInput: ...
8+
9+
610
@strawberry.experimental.pydantic.input(model=street_project.StreetProjectProperty, all_fields=True)
711
class StreetProjectPropertyInput: ...

apps/project/tests/mutation_test.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from PIL import Image
99
from ulid import ULID
1010

11+
from apps.common.models import AssetMimetypeEnum, IconEnum
1112
from apps.contributor.factories import ContributorTeamFactory
1213
from apps.project.factories import OrganizationFactory, ProjectFactory
1314
from apps.project.models import (
@@ -23,6 +24,7 @@
2324
from apps.tutorial.models import Tutorial
2425
from apps.user.factories import UserFactory
2526
from main.tests import TestCase
27+
from project_types.street import project as street_project
2628
from project_types.tile_map_service.compare import project as compare_project
2729
from utils.geo.raster_tile_server.config import RasterTileServerNameEnum
2830

@@ -1319,3 +1321,103 @@ class TaskGroupType(typing.TypedDict):
13191321
assert resp_data["errors"] is None, content
13201322
assert resp_data["result"]["status"] == self.genum(Project.Status.PUBLISHED)
13211323
assert resp_data["result"]["processingStatus"] == self.genum(Project.ProcessingStatus.COMPLETED)
1324+
1325+
@patch("apps.project.serializers.process_project_task.delay")
1326+
def test_project_street(self, mock_requests):
1327+
self.force_login(self.user)
1328+
project_data = {
1329+
**self.project_data,
1330+
"clientId": str(ULID()),
1331+
"projectType": self.genum(ProjectTypeEnum.STREET),
1332+
}
1333+
content = self._create_project_mutation(project_data)
1334+
resp_data = content["data"]["createProject"]
1335+
assert resp_data["errors"] is None, content
1336+
1337+
project_id = resp_data["result"]["id"]
1338+
project_client_id = resp_data["result"]["clientId"]
1339+
1340+
# Creating AOI Project Asset
1341+
project_asset_data = {
1342+
"project": project_id,
1343+
"mimetype": self.genum(AssetMimetypeEnum.GEOJSON),
1344+
"clientId": str(ULID()),
1345+
}
1346+
1347+
content = self._create_project_aoi_asset(project_asset_data, assert_errors=True)
1348+
resp_data = content["data"]["createProjectAsset"]
1349+
assert resp_data["errors"] is None, content
1350+
aoi_geometry_asset = resp_data["result"]
1351+
1352+
# Creating Project Image Asset
1353+
project_asset_data = {
1354+
"project": project_id,
1355+
"mimetype": self.genum(AssetMimetypeEnum.IMAGE_JPEG),
1356+
"clientId": str(ULID()),
1357+
}
1358+
content = self._create_project_image_asset(project_asset_data, assert_errors=True)
1359+
resp_data = content["data"]["createProjectAsset"]
1360+
assert resp_data["errors"] is None, content
1361+
image_asset = resp_data["result"]
1362+
1363+
# Updating Project
1364+
project_data = {
1365+
"clientId": project_client_id,
1366+
"image": image_asset["id"],
1367+
"verificationNumber": 10,
1368+
"projectTypeSpecifics": {
1369+
"street": {
1370+
"aoiGeometry": aoi_geometry_asset["id"],
1371+
"customOptions": {
1372+
"clientId": str(ULID()),
1373+
"description": "Street project description",
1374+
"icon": self.genum(IconEnum.ADD_OUTLINE),
1375+
"iconColor": "#FF0000",
1376+
"title": "Street Project Title",
1377+
"value": 1,
1378+
},
1379+
"mapillaryImageFilters": {
1380+
"isPano": True,
1381+
"creatorId": None,
1382+
"organizationId": None,
1383+
"startTime": None,
1384+
"endTime": None,
1385+
"randomizeOrder": False,
1386+
"samplingThreshold": None,
1387+
},
1388+
},
1389+
},
1390+
}
1391+
content = self._update_project_mutation(project_id, project_data)
1392+
resp_data = content["data"]["updateProject"]
1393+
assert resp_data["errors"] is None, content
1394+
1395+
latest_project = Project.objects.get(pk=project_id)
1396+
assert latest_project.created_by_id == self.user.pk
1397+
assert latest_project.modified_by_id == self.user.pk
1398+
assert latest_project.image_id == int(image_asset["id"])
1399+
assert latest_project.project_type_specifics == {
1400+
"aoi_geometry": aoi_geometry_asset["id"],
1401+
}
1402+
1403+
street_project.StreetProjectProperty.model_validate(
1404+
latest_project.project_type_specifics,
1405+
context={"project_id": latest_project.pk},
1406+
)
1407+
1408+
# Updating Project:
1409+
# Test project processing
1410+
project_data = {
1411+
"clientId": project_client_id,
1412+
"status": self.genum(Project.Status.MARKED_AS_READY),
1413+
}
1414+
content = self._update_project_mutation(project_id, project_data)
1415+
resp_data = content["data"]["updateProject"]
1416+
assert resp_data["errors"] is None, content
1417+
assert resp_data["result"]["status"] == self.genum(Project.Status.MARKED_AS_READY)
1418+
assert resp_data["result"]["processingStatus"] is None
1419+
1420+
mock_requests.assert_called_once()
1421+
mock_requests.assert_has_calls([call(int(project_id))])
1422+
1423+
process_project_task(int(project_id))

project_types/street/project.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from main.bulk_managers import BulkCreateManager
1919
from project_types.base import project as base_project
2020
from project_types.street.api_calls import get_image_metadata
21-
from utils import fields as custom_fields
2221
from utils.common import create_json_dump
2322
from utils.custom_options.models import CustomOption
2423

@@ -65,7 +64,7 @@ class StreetMappilaryImageFilters(BaseModel):
6564

6665

6766
class StreetProjectProperty(base_project.BaseProjectProperty):
68-
aoi_geometry: custom_fields.PydanticId
67+
aoi_geometry: typing.Annotated[str, Field(strict=True, pattern=r"^\d+$")] | None = None
6968
custom_options: list[CustomOption] | None = None
7069
mappilary_image_filters: StreetMappilaryImageFilters
7170

@@ -174,6 +173,7 @@ def create_groups(self, resp: StreetRawGroupItem):
174173
self.project.update_processing_status(Project.ProcessingStatus.GENERATING_GROUPS_AND_TASKS, True)
175174
number_of_groups = math.ceil(len(resp["feature_ids"]) / self.project.group_size)
176175

176+
# FIXME (susilnem): We can directly use the firebase_id instead of total_tasks_accumulated
177177
total_tasks_accumulated: int = 0
178178

179179
for group_id in range(number_of_groups):

project_types/street/tutorial.py

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
from apps.tutorial.models import Tutorial
1+
import typing
2+
3+
from osgeo import ogr
4+
from pyfirebase_mapswipe import models as firebase_models
5+
6+
from apps.project.models import ProjectTypeEnum
7+
from apps.tutorial.models import Tutorial, TutorialTask
28
from project_types.base import tutorial as base_tutorial
39
from project_types.street.project import StreetProjectProperty
410

511

612
class StreetTutorialTaskProperty(base_tutorial.BaseTutorialTaskProperty):
7-
identifier: int
813
object_geometry: str
914

1015

@@ -19,3 +24,52 @@ class StreetTutorial(
1924

2025
def __init__(self, tutorial: Tutorial):
2126
super().__init__(tutorial)
27+
28+
@typing.override
29+
def get_task_specifics_for_firebase(self, task: TutorialTask, index: int):
30+
task_specifics = self.tutorial_task_property_class(
31+
**task.project_type_specifics,
32+
)
33+
34+
geometry_ogr = ogr.CreateGeometryFromJson(task_specifics.object_geometry)
35+
geometry_wkt = geometry_ogr.ExportToWkt()
36+
37+
return firebase_models.FbStreetTutorialTask(
38+
taskId=f"t{index}",
39+
geometry=geometry_wkt,
40+
screen=task.scenario.scenario_page_number,
41+
referenceAnswer=task.reference,
42+
)
43+
44+
@typing.override
45+
def get_tutorial_specifics_for_firebase(self):
46+
custom_opts = self.project_type_specifics.custom_options
47+
48+
projectType = ProjectTypeEnum.STREET.value
49+
assert projectType == 7, "Project Street should be 7"
50+
51+
return firebase_models.FbStreetTutorial(
52+
zoomLevel=14,
53+
projectType=projectType,
54+
customOptions=[
55+
firebase_models.FbObjCustomOption(
56+
title=opt.title,
57+
description=opt.description,
58+
value=opt.value,
59+
icon=str(opt.icon.label),
60+
iconColor=opt.icon_color,
61+
subOptions=[
62+
firebase_models.FbBaseObjCustomSubOption(
63+
value=sub_opt.value,
64+
description=sub_opt.description,
65+
)
66+
for sub_opt in opt.sub_options
67+
]
68+
if opt.sub_options is not None
69+
else None,
70+
)
71+
for opt in custom_opts
72+
]
73+
if custom_opts is not None
74+
else None,
75+
)

schema.graphql

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ type CompareProjectPropertyType {
294294
tileServerBProperty: ProjectRasterTileServerConfig!
295295
}
296296

297-
union CompareProjectPropertyTypeFindProjectPropertyTypeValidateProjectPropertyTypeValidateImageProjectPropertyTypeCompletenessProjectPropertyType = CompareProjectPropertyType | FindProjectPropertyType | ValidateProjectPropertyType | ValidateImageProjectPropertyType | CompletenessProjectPropertyType
297+
union CompareProjectPropertyTypeFindProjectPropertyTypeValidateProjectPropertyTypeValidateImageProjectPropertyTypeCompletenessProjectPropertyTypeStreetProjectPropertyType = CompareProjectPropertyType | FindProjectPropertyType | ValidateProjectPropertyType | ValidateImageProjectPropertyType | CompletenessProjectPropertyType | StreetProjectPropertyType
298298

299299
input CompareTutorialTaskPropertyInput {
300300
tileX: Int!
@@ -308,7 +308,7 @@ type CompareTutorialTaskPropertyType {
308308
tileZ: Int!
309309
}
310310

311-
union CompareTutorialTaskPropertyTypeFindTutorialTaskPropertyTypeValidateTutorialTaskPropertyTypeValidateImageTutorialTaskPropertyTypeCompletenessTutorialTaskPropertyType = CompareTutorialTaskPropertyType | FindTutorialTaskPropertyType | ValidateTutorialTaskPropertyType | ValidateImageTutorialTaskPropertyType | CompletenessTutorialTaskPropertyType
311+
union CompareTutorialTaskPropertyTypeFindTutorialTaskPropertyTypeValidateTutorialTaskPropertyTypeValidateImageTutorialTaskPropertyTypeCompletenessTutorialTaskPropertyTypeStreetTutorialTaskPropertyType = CompareTutorialTaskPropertyType | FindTutorialTaskPropertyType | ValidateTutorialTaskPropertyType | ValidateImageTutorialTaskPropertyType | CompletenessTutorialTaskPropertyType | StreetTutorialTaskPropertyType
312312

313313
input CompletenessProjectPropertyInput {
314314
zoomLevel: Int!
@@ -1609,7 +1609,7 @@ type ProjectType implements UserResourceTypeMixin & ProjectExportAssetTypeMixin
16091609
Project name generated from topic, region, project number, and requesting organization name.
16101610
"""
16111611
name: String!
1612-
projectTypeSpecifics: CompareProjectPropertyTypeFindProjectPropertyTypeValidateProjectPropertyTypeValidateImageProjectPropertyTypeCompletenessProjectPropertyType
1612+
projectTypeSpecifics: CompareProjectPropertyTypeFindProjectPropertyTypeValidateProjectPropertyTypeValidateImageProjectPropertyTypeCompletenessProjectPropertyTypeStreetProjectPropertyType
16131613
}
16141614

16151615
type ProjectTypeAreaStatsType {
@@ -1624,6 +1624,7 @@ enum ProjectTypeEnum {
16241624
VALIDATE_IMAGE
16251625
COMPARE
16261626
COMPLETENESS
1627+
STREET
16271628
}
16281629

16291630
input ProjectTypeEnumFilterLookup {
@@ -1696,6 +1697,7 @@ input ProjectTypeSpecificInput @oneOf {
16961697
completeness: CompletenessProjectPropertyInput
16971698
validate: ValidateProjectPropertyInput
16981699
validateImage: ValidateImageProjectPropertyInput
1700+
street: StreetProjectPropertyInput
16991701
}
17001702

17011703
type ProjectTypeSwipeStatsType {
@@ -1891,6 +1893,68 @@ input StrFilterLookup {
18911893
iRegex: String
18921894
}
18931895

1896+
type StreetMappilaryImageFilters {
1897+
"""Filter for images that are panoramas."""
1898+
isPano: Boolean
1899+
1900+
"""Filter for images created by a specific user."""
1901+
creatorId: Int
1902+
1903+
"""Filter for images that belong to a specific organization."""
1904+
organizationId: String
1905+
1906+
"""Filter for images captured after this timestamp."""
1907+
startTime: String
1908+
1909+
"""Filter for images captured before this timestamp."""
1910+
endTime: String
1911+
1912+
"""Randomize the order of the images."""
1913+
randomizeOrder: Boolean!
1914+
1915+
"""Sampling threshold for filtering images."""
1916+
samplingThreshold: Int
1917+
}
1918+
1919+
input StreetMappilaryImageFiltersInput {
1920+
"""Filter for images that are panoramas."""
1921+
isPano: Boolean = null
1922+
1923+
"""Filter for images created by a specific user."""
1924+
creatorId: Int = null
1925+
1926+
"""Filter for images that belong to a specific organization."""
1927+
organizationId: String = null
1928+
1929+
"""Filter for images captured after this timestamp."""
1930+
startTime: String = null
1931+
1932+
"""Filter for images captured before this timestamp."""
1933+
endTime: String = null
1934+
1935+
"""Randomize the order of the images."""
1936+
randomizeOrder: Boolean! = false
1937+
1938+
"""Sampling threshold for filtering images."""
1939+
samplingThreshold: Int = null
1940+
}
1941+
1942+
input StreetProjectPropertyInput {
1943+
aoiGeometry: String = null
1944+
customOptions: [CustomOptionInput!] = null
1945+
mappilaryImageFilters: StreetMappilaryImageFiltersInput!
1946+
}
1947+
1948+
type StreetProjectPropertyType {
1949+
aoiGeometry: String
1950+
customOptions: [ProjectCustomOption!]
1951+
mappilaryImageFilters: StreetMappilaryImageFilters!
1952+
}
1953+
1954+
type StreetTutorialTaskPropertyType {
1955+
objectGeometry: String!
1956+
}
1957+
18941958
"""
18951959
TutorialAsset(id, client_id, created_at, modified_at, created_by, modified_by, type, mimetype, file_size, marked_as_deleted, external_url, tutorial, input_type, file)
18961960
"""
@@ -2253,7 +2317,7 @@ type TutorialTaskType implements UserResourceTypeMixin {
22532317
id: ID!
22542318
scenarioId: ID!
22552319
reference: Int!
2256-
projectTypeSpecifics: CompareTutorialTaskPropertyTypeFindTutorialTaskPropertyTypeValidateTutorialTaskPropertyTypeValidateImageTutorialTaskPropertyTypeCompletenessTutorialTaskPropertyType
2320+
projectTypeSpecifics: CompareTutorialTaskPropertyTypeFindTutorialTaskPropertyTypeValidateTutorialTaskPropertyTypeValidateImageTutorialTaskPropertyTypeCompletenessTutorialTaskPropertyTypeStreetTutorialTaskPropertyType
22572321
}
22582322

22592323
"""

0 commit comments

Comments
 (0)