Skip to content

Commit 37d7a7d

Browse files
matusdrobuliak66Matus Drobuliak
andauthored
🎨 Adding filtering for template type + extend patching (#7689)
Co-authored-by: Matus Drobuliak <[email protected]>
1 parent b86f633 commit 37d7a7d

File tree

14 files changed

+337
-49
lines changed

14 files changed

+337
-49
lines changed

packages/models-library/src/models_library/api_schemas_webserver/projects.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ class ProjectPatch(InputSchema):
261261
),
262262
] = None
263263
quality: dict[str, Any] | None = None
264+
template_type: ProjectTemplateType | None = None
264265

265266
def to_domain_model(self) -> dict[str, Any]:
266267
return self.model_dump(exclude_unset=True, by_alias=False)

packages/models-library/src/models_library/projects.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44

55
from datetime import datetime
6-
from enum import Enum, auto
6+
from enum import Enum
77
from typing import Annotated, Any, Final, TypeAlias
88
from uuid import UUID
99

@@ -54,9 +54,9 @@ class ProjectType(str, Enum):
5454

5555

5656
class ProjectTemplateType(StrAutoEnum):
57-
TEMPLATE = auto()
58-
TUTORIAL = auto()
59-
HYPERTOOL = auto()
57+
TEMPLATE = "TEMPLATE"
58+
TUTORIAL = "TUTORIAL"
59+
HYPERTOOL = "HYPERTOOL"
6060

6161

6262
class BaseProjectModel(BaseModel):

packages/postgres-database/src/simcore_postgres_database/webserver_models.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
""" Facade for webserver service
1+
"""Facade for webserver service
22
3-
Facade to direct access to models in the database by
4-
the webserver service
3+
Facade to direct access to models in the database by
4+
the webserver service
55
66
"""
7+
78
from .models.api_keys import api_keys
89
from .models.classifiers import group_classifiers
910
from .models.comp_pipeline import StateType, comp_pipeline
1011
from .models.comp_tasks import DB_CHANNEL_NAME, NodeClass, comp_tasks
1112
from .models.confirmations import ConfirmationAction, confirmations
1213
from .models.groups import GroupType, groups, user_to_groups
1314
from .models.products import products
14-
from .models.projects import ProjectType, projects
15+
from .models.projects import ProjectTemplateType, ProjectType, projects
1516
from .models.projects_nodes import projects_nodes
1617
from .models.projects_tags import projects_tags
1718
from .models.projects_to_wallet import projects_to_wallet
@@ -35,6 +36,7 @@
3536
"projects",
3637
"projects_nodes",
3738
"ProjectType",
39+
"ProjectTemplateType",
3840
"scicrunch_resources",
3941
"StateType",
4042
"projects_tags",

services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3962,6 +3962,14 @@ paths:
39623962
schema:
39633963
$ref: '#/components/schemas/ProjectTypeAPI'
39643964
default: all
3965+
- name: template_type
3966+
in: query
3967+
required: false
3968+
schema:
3969+
anyOf:
3970+
- $ref: '#/components/schemas/ProjectTemplateType'
3971+
- type: 'null'
3972+
title: Template Type
39653973
- name: show_hidden
39663974
in: query
39673975
required: false
@@ -14868,6 +14876,10 @@ components:
1486814876
- type: object
1486914877
- type: 'null'
1487014878
title: Quality
14879+
templateType:
14880+
anyOf:
14881+
- $ref: '#/components/schemas/ProjectTemplateType'
14882+
- type: 'null'
1487114883
type: object
1487214884
title: ProjectPatch
1487314885
ProjectPermalink:

services/web/server/src/simcore_service_webserver/projects/_controller/_rest_exceptions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from ..exceptions import (
2828
ClustersKeeperNotAvailableError,
2929
DefaultPricingUnitNotFoundError,
30+
InsufficientRoleForProjectTemplateTypeUpdateError,
3031
NodeNotFoundError,
3132
ParentNodeNotFoundError,
3233
ProjectDeleteError,
@@ -40,6 +41,7 @@
4041
ProjectOwnerNotFoundInTheProjectAccessRightsError,
4142
ProjectStartsTooManyDynamicNodesError,
4243
ProjectTooManyProjectOpenedError,
44+
ProjectTypeAndTemplateIncompatibilityError,
4345
ProjectWalletPendingTransactionError,
4446
WrongTagIdsInQueryError,
4547
)
@@ -88,6 +90,10 @@
8890
status.HTTP_403_FORBIDDEN,
8991
"Do not have sufficient access rights on project {project_uuid} for this action",
9092
),
93+
InsufficientRoleForProjectTemplateTypeUpdateError: HttpErrorInfo(
94+
status.HTTP_403_FORBIDDEN,
95+
"Do not have sufficient access rights on updating project template type",
96+
),
9197
ProjectInvalidUsageError: HttpErrorInfo(
9298
status.HTTP_422_UNPROCESSABLE_ENTITY,
9399
"Invalid usage for project",
@@ -124,6 +130,10 @@
124130
status.HTTP_400_BAD_REQUEST,
125131
"Wrong tag IDs in query",
126132
),
133+
ProjectTypeAndTemplateIncompatibilityError: HttpErrorInfo(
134+
status.HTTP_400_BAD_REQUEST,
135+
"Wrong project type and template type combination: {reason}",
136+
),
127137
}
128138

129139

services/web/server/src/simcore_service_webserver/projects/_controller/projects_rest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ async def list_projects(request: web.Request):
153153
user_id=req_ctx.user_id,
154154
product_name=req_ctx.product_name,
155155
project_type=query_params.project_type,
156+
template_type=query_params.template_type,
156157
show_hidden=query_params.show_hidden,
157158
trashed=query_params.filters.trashed,
158159
folder_id=query_params.folder_id,

services/web/server/src/simcore_service_webserver/projects/_controller/projects_rest_schemas.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from models_library.basic_types import IDStr
44
from models_library.folders import FolderID
5-
from models_library.projects import ProjectID
5+
from models_library.projects import ProjectID, ProjectTemplateType
66
from models_library.projects_nodes_io import NodeID
77
from models_library.rest_base import RequestParameters
88
from models_library.rest_filters import Filters, FiltersQueryParameters
@@ -136,6 +136,7 @@ class ProjectFilters(Filters):
136136

137137
class ProjectsListExtraQueryParams(RequestParameters):
138138
project_type: Annotated[ProjectTypeAPI, Field(alias="type")] = ProjectTypeAPI.all
139+
template_type: ProjectTemplateType | None = None
139140
show_hidden: Annotated[
140141
bool, Field(description="includes projects marked as hidden in the listing")
141142
] = False
@@ -167,6 +168,20 @@ def _search_check_empty_string(cls, v):
167168
return None
168169
return v
169170

171+
_template_type_null_or_none_str_to_none_validator = field_validator(
172+
"template_type", mode="before"
173+
)(null_or_none_str_to_none_validator)
174+
175+
@model_validator(mode="after")
176+
def _check_template_type_compatibility(self):
177+
if (
178+
self.project_type in [ProjectTypeAPI.all, ProjectTypeAPI.user]
179+
and self.template_type is not None
180+
):
181+
msg = f"When {self.project_type=} is `all` or `user` the {self.template_type=} should be None"
182+
raise ValueError(msg)
183+
return self
184+
170185
_null_or_none_str_to_none_validator = field_validator("folder_id", mode="before")(
171186
null_or_none_str_to_none_validator
172187
)

services/web/server/src/simcore_service_webserver/projects/_crud_api_read.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@
1010

1111
from aiohttp import web
1212
from models_library.folders import FolderID, FolderQuery, FolderScope
13-
from models_library.projects import ProjectID
13+
from models_library.projects import ProjectID, ProjectTemplateType
1414
from models_library.rest_ordering import OrderBy
1515
from models_library.users import UserID
1616
from models_library.workspaces import WorkspaceID, WorkspaceQuery, WorkspaceScope
1717
from pydantic import NonNegativeInt
1818
from servicelib.utils import logged_gather
1919
from simcore_postgres_database.models.projects import ProjectType
20+
from simcore_postgres_database.webserver_models import (
21+
ProjectTemplateType as ProjectTemplateTypeDB,
22+
)
2023
from simcore_postgres_database.webserver_models import ProjectType as ProjectTypeDB
2124

2225
from ..catalog import catalog_service
@@ -91,6 +94,7 @@ async def list_projects( # pylint: disable=too-many-arguments
9194
folder_id: FolderID | None,
9295
# attrs filter
9396
project_type: ProjectTypeAPI,
97+
template_type: ProjectTemplateType | None,
9498
show_hidden: bool, # NOTE: Be careful, this filters only hidden projects
9599
trashed: bool | None,
96100
# search
@@ -148,6 +152,9 @@ async def list_projects( # pylint: disable=too-many-arguments
148152
),
149153
# attrs
150154
filter_by_project_type=ProjectTypeAPI.to_project_type_db(project_type),
155+
filter_by_template_type=(
156+
ProjectTemplateTypeDB(template_type) if template_type else None
157+
),
151158
filter_by_services=user_available_services,
152159
filter_trashed=trashed,
153160
filter_hidden=show_hidden,

services/web/server/src/simcore_service_webserver/projects/_projects_repository_legacy.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
from simcore_postgres_database.aiopg_errors import UniqueViolation
4141
from simcore_postgres_database.models.groups import user_to_groups
4242
from simcore_postgres_database.models.project_to_groups import project_to_groups
43-
from simcore_postgres_database.models.projects import ProjectTemplateType
4443
from simcore_postgres_database.models.projects_nodes import projects_nodes
4544
from simcore_postgres_database.models.projects_tags import projects_tags
4645
from simcore_postgres_database.models.projects_to_folders import projects_to_folders
@@ -58,7 +57,12 @@
5857
ProjectNodeCreate,
5958
ProjectNodesRepo,
6059
)
61-
from simcore_postgres_database.webserver_models import ProjectType, projects, users
60+
from simcore_postgres_database.webserver_models import (
61+
ProjectTemplateType,
62+
ProjectType,
63+
projects,
64+
users,
65+
)
6266
from sqlalchemy import func, literal_column, sql
6367
from sqlalchemy.dialects.postgresql import BOOLEAN, INTEGER
6468
from sqlalchemy.dialects.postgresql import insert as pg_insert
@@ -583,6 +587,7 @@ def _create_shared_workspace_query(
583587
def _create_attributes_filters(
584588
*,
585589
filter_by_project_type: ProjectType | None,
590+
filter_by_template_type: ProjectTemplateType | None,
586591
filter_hidden: bool | None,
587592
filter_published: bool | None,
588593
filter_trashed: bool | None,
@@ -595,6 +600,11 @@ def _create_attributes_filters(
595600
if filter_by_project_type is not None:
596601
attributes_filters.append(projects.c.type == filter_by_project_type.value)
597602

603+
if filter_by_template_type is not None:
604+
attributes_filters.append(
605+
projects.c.template_type == filter_by_template_type.value
606+
)
607+
598608
if filter_hidden is not None:
599609
attributes_filters.append(projects.c.hidden.is_(filter_hidden))
600610

@@ -647,6 +657,7 @@ async def list_projects_dicts( # pylint: disable=too-many-arguments,too-many-st
647657
folder_query: FolderQuery,
648658
# attribute filters
649659
filter_by_project_type: ProjectType | None = None,
660+
filter_by_template_type: ProjectTemplateType | None = None,
650661
filter_by_services: list[dict] | None = None,
651662
filter_published: bool | None = None,
652663
filter_hidden: bool | None = False,
@@ -694,6 +705,7 @@ async def list_projects_dicts( # pylint: disable=too-many-arguments,too-many-st
694705

695706
attributes_filters = self._create_attributes_filters(
696707
filter_by_project_type=filter_by_project_type,
708+
filter_by_template_type=filter_by_template_type,
697709
filter_hidden=filter_hidden,
698710
filter_published=filter_published,
699711
filter_trashed=filter_trashed,

services/web/server/src/simcore_service_webserver/projects/_projects_service.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
from .exceptions import (
140140
ClustersKeeperNotAvailableError,
141141
DefaultPricingUnitNotFoundError,
142+
InsufficientRoleForProjectTemplateTypeUpdateError,
142143
InvalidEC2TypeInResourcesSpecsError,
143144
InvalidKeysInResourcesSpecsError,
144145
NodeNotFoundError,
@@ -150,6 +151,7 @@
150151
ProjectOwnerNotFoundInTheProjectAccessRightsError,
151152
ProjectStartsTooManyDynamicNodesError,
152153
ProjectTooManyProjectOpenedError,
154+
ProjectTypeAndTemplateIncompatibilityError,
153155
)
154156
from .models import ProjectDict, ProjectPatchInternalExtended
155157
from .settings import ProjectsSettings, get_plugin_settings
@@ -327,7 +329,27 @@ async def patch_project(
327329
if new_prj_access_rights[_prj_owner_primary_group] != _prj_required_permissions:
328330
raise ProjectOwnerNotFoundInTheProjectAccessRightsError
329331

330-
# 4. Patch the project
332+
# 4. If patching template type
333+
if new_template_type := patch_project_data.get("template_type"):
334+
# 4.1 Check if user is a tester
335+
current_user: dict = await get_user(app, user_id)
336+
if UserRole(current_user["role"]) < UserRole.TESTER:
337+
raise InsufficientRoleForProjectTemplateTypeUpdateError
338+
# 4.2 Check the compatibility of the template type with the project
339+
if project_db.type == ProjectType.STANDARD and new_template_type is not None:
340+
raise ProjectTypeAndTemplateIncompatibilityError(
341+
project_uuid=project_uuid,
342+
project_type=project_db.type,
343+
project_template=new_template_type,
344+
)
345+
if project_db.type == ProjectType.TEMPLATE and new_template_type is None:
346+
raise ProjectTypeAndTemplateIncompatibilityError(
347+
project_uuid=project_uuid,
348+
project_type=project_db.type,
349+
project_template=new_template_type,
350+
)
351+
352+
# 5. Patch the project
331353
await _projects_repository.patch_project(
332354
app=app,
333355
project_uuid=project_uuid,

0 commit comments

Comments
 (0)