From 6ff6dadff5aa3425149d6a7a20a0cb6c1d2ecc50 Mon Sep 17 00:00:00 2001 From: ANDRE Simon Date: Sat, 23 Sep 2023 19:15:11 +0200 Subject: [PATCH] [NEW] Location view/Lesson view --- skii/endpoint/api.py | 18 ++-- skii/endpoint/routers/__init__.py | 11 ++- skii/endpoint/routers/lesson.py | 101 +++++++++++++++++-- skii/endpoint/routers/location.py | 147 +++++++++++++++++++--------- skii/endpoint/routers/mixins.py | 17 ++-- skii/endpoint/routers/student.py | 32 +++--- skii/endpoint/routers/teacher.py | 38 ++++--- skii/endpoint/schemas/identifier.py | 13 ++- skii/platform/entities.py | 7 +- skii/platform/models/common.py | 20 ++++ skii/platform/models/event.py | 1 + skii/platform/schemas/agent.py | 5 +- skii/platform/schemas/common.py | 38 +++++-- skii/platform/schemas/entities.py | 9 ++ skii/platform/schemas/event.py | 17 +++- skii/platform/schemas/resource.py | 48 ++++++++- tests/endpoint/test_api.py | 4 +- 17 files changed, 401 insertions(+), 125 deletions(-) create mode 100644 skii/platform/schemas/entities.py diff --git a/skii/endpoint/api.py b/skii/endpoint/api.py index cfa1dd2..2f47679 100644 --- a/skii/endpoint/api.py +++ b/skii/endpoint/api.py @@ -1,7 +1,6 @@ import json import logging from ipaddress import IPv4Address, IPv6Address -from pprint import pformat from typing import Type, Mapping, Any, cast, List from django.core.serializers.json import DjangoJSONEncoder @@ -17,8 +16,12 @@ from ninja.types import DictStrAny from pydantic import BaseModel -from skii.endpoint.routers.student import sub_route as route_student -from skii.endpoint.routers.teacher import sub_route as route_teacher +from skii.endpoint.routers import ( + route_student, + route_teacher, + route_location, + route_lesson, +) # Get current package version from packaging.version import parse as parse_version @@ -29,6 +32,8 @@ current_package_name = __package__.split(".")[0] distrib_version = parse_version(__import__(current_package_name).__version__) + + logger.info(f"Package: {current_package_name}") logger.info(f"Distribution version: {distrib_version}") @@ -48,7 +53,6 @@ class SkiiJsonRenderer(BaseRenderer): json_dumps_params: Mapping[str, Any] = {} def render(self, request, data, *, response_status): - logger.debug(f"SkiiJsonRenderer: {pformat(data)}") return json.dumps(data, cls=self.encoder_class, **self.json_dumps_params) @@ -87,11 +91,9 @@ def configure_api_skii() -> NinjaAPI: new_api = NinjaAPI(**api_kwargs) new_api.add_router(prefix="student", router=route_student) new_api.add_router(prefix="teacher", router=route_teacher) + new_api.add_router(prefix="location", router=route_location) + new_api.add_router(prefix="lesson", router=route_lesson) - from .routers import LessonEventRouter, LocationResourceRouter - - LocationResourceRouter.link_with_api(api=new_api) - LessonEventRouter.link_with_api(api=new_api) return new_api diff --git a/skii/endpoint/routers/__init__.py b/skii/endpoint/routers/__init__.py index 4b9ec73..816d2f8 100644 --- a/skii/endpoint/routers/__init__.py +++ b/skii/endpoint/routers/__init__.py @@ -1,10 +1,11 @@ from .student import sub_route as route_student from .teacher import sub_route as route_teacher - -from .location import LocationResourceRouter -from .lesson import LessonEventRouter +from .location import router as route_location +from .lesson import router as route_lesson __all__ = [ - LocationResourceRouter, - LessonEventRouter, + route_location, + route_student, + route_teacher, + route_lesson, ] diff --git a/skii/endpoint/routers/lesson.py b/skii/endpoint/routers/lesson.py index 33b9142..40dc8bf 100644 --- a/skii/endpoint/routers/lesson.py +++ b/skii/endpoint/routers/lesson.py @@ -1,19 +1,100 @@ from typing import List -from django.db.models import Model +from django.http import HttpRequest +from django.shortcuts import get_object_or_404 +from ninja import Router -from skii.endpoint.routers.abstract import RestRouterProducer +from apps.base.schemas import FormInvalidResponseContract +from skii.endpoint.schemas.identifier import IntStrUUID4 +from skii.endpoint.schemas.response import SkiiMsgContract +from skii.platform.models.common import GeoCoordinate from skii.platform.models.event import LessonEvent +from skii.platform.schemas.event import LessonContract, LessonSaveContract -class AutomatedLessonRouter(RestRouterProducer): - class Config(RestRouterProducer.Config): - model: Model = LessonEvent - name: str = "lesson" - operation: List[str] = ["create", "read", "update", "delete", "list"] - tags = ["lesson"] +# Create a django ninja API router dedicated to the student +router = Router(tags=["lesson"]) -LessonEventRouter = AutomatedLessonRouter() +RouterContract = LessonContract +RouterSaveContract = LessonSaveContract +RouterModel = LessonEvent +RouterListContract = List[RouterContract] -__all__ = [LessonEventRouter] + +@router.post( + path="/create/", + response={ + 200: RouterContract, + 422: FormInvalidResponseContract, + }, +) +def record_create(request: HttpRequest, payload: RouterSaveContract): + record_payload = payload.dict() + record = RouterModel(**record_payload) + record.save() + record.refresh_from_db() + return 200, record + + +@router.get( + path="/read/{pk}/", + response={ + 200: RouterContract, + 422: FormInvalidResponseContract, + }, +) +def record_read(request: HttpRequest, pk: IntStrUUID4): + return 200, get_object_or_404(RouterModel, pk=pk) + + +@router.post( + path="/update/{pk}/", + response={ + 200: RouterContract, + 422: FormInvalidResponseContract, + }, +) +def record_update(request: HttpRequest, pk: IntStrUUID4, payload: RouterSaveContract): + record_payload = payload.dict() + if "coordinate" in record_payload: + geo_coordinate = record_payload["coordinate"] + del record_payload["coordinate"] + geo_coordinate_obj, created = GeoCoordinate.objects.update_or_create( + geo_coordinate, **geo_coordinate + ) + record_payload["coordinate"] = geo_coordinate_obj + record = get_object_or_404(RouterModel, pk=pk) + for attr, value in record_payload.items(): + setattr(record, attr, value) + record.save() + record.refresh_from_db() + return 200, record + + +@router.get( + path="/delete/{pk}/", + response={ + 200: SkiiMsgContract, + 422: FormInvalidResponseContract, + }, +) +def record_delete(request: HttpRequest, pk: IntStrUUID4): + qs = RouterModel.objects.all().filter(pk=pk) + if qs.exists(): + qs.delete() + return 200, SkiiMsgContract(message="Record deleted") + + +@router.get( + path="/list/", + response={ + 200: RouterListContract, + 422: FormInvalidResponseContract, + }, +) +def record_list(request: HttpRequest): + return 200, RouterModel.objects.all() + + +__all__ = [router] diff --git a/skii/endpoint/routers/location.py b/skii/endpoint/routers/location.py index 9ba4b43..60c4226 100644 --- a/skii/endpoint/routers/location.py +++ b/skii/endpoint/routers/location.py @@ -1,48 +1,107 @@ -from typing import List, Any +from typing import List -from django.db.models import Model -from ninja import Schema +from django.http import HttpRequest +from django.shortcuts import get_object_or_404 +from ninja import Router -from skii.endpoint.routers.abstract import RestRouterProducer +from apps.base.schemas import FormInvalidResponseContract from skii.endpoint.schemas.identifier import IntStrUUID4 +from skii.endpoint.schemas.response import SkiiMsgContract +from skii.platform.models.common import GeoCoordinate from skii.platform.models.resource import LocationResource -from skii.platform.schemas.common import CountryContract, GeoCoordinateContract - - -class AutomatedLocationRouter(RestRouterProducer): - class Config(RestRouterProducer.Config): - # Model Config - model: Model = LocationResource - name: str = "location" - # Router config - operation: List[str] = ["create", "read", "update", "delete", "list"] - base_class: Schema = IntStrUUID4 - tags = ["lesson"] - # Introspection config - depth: int = 1 - save_depth: int = 0 - # Fields config/tweak - fields: List[str] | None = None - save_fields: List[str] | None = None - exclude_fields: List[str] | None = ["uuid"] - save_exclude_fields: List[str] | None = ["uuid"] + ["created", "last_modified"] - custom_fields: List[tuple[Any, Any, Any]] | None = [ - ( - "country", - CountryContract, - CountryContract.parse_obj(dict(flag="", code="", name="")), - ), - ( - "coordinate", - GeoCoordinateContract, - GeoCoordinateContract.parse_obj( - dict(latitude=25.4536, longitude=70.4457) - ), - ), - ] - save_custom_fields: List[tuple[Any, Any, Any]] | None = None - - -LocationResourceRouter = AutomatedLocationRouter() - -__all__ = [LocationResourceRouter] +from skii.platform.schemas.resource import LocationContract, LocationSaveContract + + +# Create a django ninja API router dedicated to the student +router = Router(tags=["location"]) + + +RouterContract = LocationContract +RouterSaveContract = LocationSaveContract +RouterModel = LocationResource +RouterListContract = List[RouterContract] + + +@router.post( + path="/create/", + response={ + 200: RouterContract, + 422: FormInvalidResponseContract, + }, +) +def record_create(request: HttpRequest, payload: RouterSaveContract): + record_payload = payload.dict() + if "coordinate" in record_payload: + geo_coordinate = record_payload["coordinate"] + del record_payload["coordinate"] + geo_coordinate_obj, created = GeoCoordinate.objects.update_or_create( + geo_coordinate, **geo_coordinate + ) + record_payload["coordinate"] = geo_coordinate_obj + record = RouterModel(**record_payload) + record.save() + record.refresh_from_db() + return 200, record + + +@router.get( + path="/read/{pk}/", + response={ + 200: RouterContract, + 422: FormInvalidResponseContract, + }, +) +def record_read(request: HttpRequest, pk: IntStrUUID4): + return 200, get_object_or_404(RouterModel, pk=pk) + + +@router.post( + path="/update/{pk}/", + response={ + 200: RouterContract, + 422: FormInvalidResponseContract, + }, +) +def record_update(request: HttpRequest, pk: IntStrUUID4, payload: RouterSaveContract): + record_payload = payload.dict() + if "coordinate" in record_payload: + geo_coordinate = record_payload["coordinate"] + del record_payload["coordinate"] + geo_coordinate_obj, created = GeoCoordinate.objects.update_or_create( + geo_coordinate, **geo_coordinate + ) + record_payload["coordinate"] = geo_coordinate_obj + record = get_object_or_404(RouterModel, pk=pk) + for attr, value in record_payload.items(): + setattr(record, attr, value) + record.save() + record.refresh_from_db() + return 200, record + + +@router.get( + path="/delete/{pk}/", + response={ + 200: SkiiMsgContract, + 422: FormInvalidResponseContract, + }, +) +def record_delete(request: HttpRequest, pk: IntStrUUID4): + qs = RouterModel.objects.all().filter(pk=pk) + if qs.exists(): + qs.delete() + return 200, SkiiMsgContract(message="Record deleted") + + +@router.get( + path="/list/", + response={ + 200: RouterListContract, + 422: FormInvalidResponseContract, + }, +) +def record_list(request: HttpRequest): + return 200, RouterModel.objects.all() + + +__all__ = [router] diff --git a/skii/endpoint/routers/mixins.py b/skii/endpoint/routers/mixins.py index 2bd16a7..c775fa1 100644 --- a/skii/endpoint/routers/mixins.py +++ b/skii/endpoint/routers/mixins.py @@ -82,7 +82,9 @@ def add_view_update(self): 422: FormInvalidResponseContract, }, ) - def record_update(request: HttpRequest, pk: IntStrUUID4, payload: save_contract): + def record_update( + request: HttpRequest, pk: IntStrUUID4, payload: save_contract + ): record_payload = payload.dict() record = get_object_or_404(router_model, pk=pk) for attr, value in record_payload.items(): @@ -135,7 +137,7 @@ class Config: # Introspection config depth: int = 0 save_depth: int = 0 - base_class: Schema = IntStrUUID4 + base_class: Schema = None # Fields config/tweak fields: List[str] | None = None save_fields: List[str] | None = None @@ -147,13 +149,16 @@ class Config: def __init__(self, *args, **kwargs): """Autogenerate contract/schema from django models.""" res = super().__init__(*args, **kwargs) + logger.debug("Now we generate standard read contract") self.contract = self.create_contract( fields=self.Config.fields, exclude_fields=self.Config.exclude_fields, custom_fields=self.Config.custom_fields, depth=self.Config.depth, ) + logger.debug("Now we define List of read contract") self.list_contract = List[self.contract] + logger.debug("Now we define the save contract") self.save_contract = self.create_contract( fields=self.Config.save_fields, exclude_fields=self.Config.save_exclude_fields, @@ -178,7 +183,7 @@ def create_contract( contract = create_schema( model=self.Config.model, name=self.Config.name + "-contract", - base_class=self.Config.base_class, + # base_class=self.Config.base_class, depth=depth, exclude=exclude_fields, custom_fields=custom_fields, @@ -193,11 +198,5 @@ def create_contract( logger.debug( f"{self.Config.name} Contract have required fields {contract_schema['required']}" ) - logger.debug( - f"{self.Config.name} Save Contract have fields {contract_schema['properties'].keys()}" - ) - logger.debug( - f"{self.Config.name} Save Contract have required fields {contract_schema['required']}" - ) return contract diff --git a/skii/endpoint/routers/student.py b/skii/endpoint/routers/student.py index e3a5582..b1f8ac9 100644 --- a/skii/endpoint/routers/student.py +++ b/skii/endpoint/routers/student.py @@ -6,10 +6,8 @@ from django.shortcuts import get_object_or_404 from ninja import Router from django.http import HttpRequest - from apps.base.schemas import FormInvalidResponseContract from skii.endpoint.schemas.identifier import IntStrUUID4 -from skii.endpoint.utils import devtools_debug from skii.platform.models.agent import StudentAgent from skii.platform.schemas.agent import StudentContract, StudentSaveContract, UserSchema from skii.endpoint.schemas.response import SkiiMsgContract @@ -74,11 +72,21 @@ def delete(request: HttpRequest, pk: IntStrUUID4): }, ) def update(request: HttpRequest, pk: IntStrUUID4, payload: SubRouteSaveContract): - record_obj = StudentAgent.objects.get(pk) - record_obj.update(payload.dict()) - record_obj.save() - record_obj.refresh_db() - return 200, record_obj + record_payload = payload.dict() + if "user" in record_payload: + user_payload = record_payload["user"] + user = UserModel.objects.get(pk=get_object_or_404(StudentAgent, pk=pk).user.pk) + for attr, value in user_payload.items(): + setattr(user, attr, value) + user.save() + user.refresh_from_db() + record_payload["user"] = user + record = get_object_or_404(StudentAgent, pk=pk) + for attr, value in record_payload.items(): + setattr(record, attr, value) + record.save() + record.refresh_from_db() + return 200, record @sub_route.post( @@ -88,13 +96,13 @@ def update(request: HttpRequest, pk: IntStrUUID4, payload: SubRouteSaveContract) 422: FormInvalidResponseContract, }, ) -@devtools_debug def create(request: HttpRequest, payload: SubRouteSaveContract): record_payload = payload.dict() user_payload = record_payload.pop("user") user_obj = UserModel(**user_payload) user_obj.save() - record_obj = StudentAgent.objects.create( - record_payload, pk=record_payload.pk - ) - return 200, record_obj + record_payload["user"] = user_obj + record = StudentAgent(**record_payload) + record.save() + record.refresh_from_db() + return 200, record diff --git a/skii/endpoint/routers/teacher.py b/skii/endpoint/routers/teacher.py index 8debcdd..ad1a3d6 100644 --- a/skii/endpoint/routers/teacher.py +++ b/skii/endpoint/routers/teacher.py @@ -1,13 +1,14 @@ +""" Profile and user are related by foreign key. +""" from typing import List +from django.contrib.auth import get_user_model from django.shortcuts import get_object_or_404 from ninja import Router from django.http import HttpRequest -from django.contrib.auth import get_user_model -from skii.endpoint.schemas.identifier import IntStrUUID4 - from apps.base.schemas import FormInvalidResponseContract -from skii.platform.models.agent import TeacherAgent, StudentAgent +from skii.endpoint.schemas.identifier import IntStrUUID4 +from skii.platform.models.agent import TeacherAgent from skii.platform.schemas.agent import TeacherContract, TeacherSaveContract from skii.endpoint.schemas.response import SkiiMsgContract @@ -71,11 +72,21 @@ def delete(request: HttpRequest, pk: IntStrUUID4): }, ) def update(request: HttpRequest, pk: IntStrUUID4, payload: SubRouteSaveContract): - record_obj = SubRouteModel.get(pk) - record_obj.update(payload.dict()) - record_obj.save() - record_obj.refresh_db() - return 200, record_obj + record_payload = payload.dict() + if "user" in record_payload: + user_payload = record_payload["user"] + user = UserModel.objects.get(pk=get_object_or_404(TeacherAgent, pk=pk).user.pk) + for attr, value in user_payload.items(): + setattr(user, attr, value) + user.save() + user.refresh_from_db() + record_payload["user"] = user + record = get_object_or_404(TeacherAgent, pk=pk) + for attr, value in record_payload.items(): + setattr(record, attr, value) + record.save() + record.refresh_from_db() + return 200, record @sub_route.post( @@ -91,8 +102,7 @@ def create(request: HttpRequest, payload: SubRouteSaveContract): user_obj = UserModel(**user_payload) user_obj.save() record_payload["user"] = user_obj - # record_obj.save() - record_obj, created = StudentAgent.objects.update_or_create( - record_payload, pk=record_payload["pk"] - ) - return 200, record_obj + record = TeacherAgent(**record_payload) + record.save() + record.refresh_from_db() + return 200, record diff --git a/skii/endpoint/schemas/identifier.py b/skii/endpoint/schemas/identifier.py index 0799d6c..abaf76a 100644 --- a/skii/endpoint/schemas/identifier.py +++ b/skii/endpoint/schemas/identifier.py @@ -1,14 +1,17 @@ -from typing import Literal +from typing import Type from ninja.schema import Schema -from pydantic import UUID4, EmailStr +from pydantic import UUID4 -IntStrUUID4: Schema = Literal[int, str, UUID4] +IntStrUUID4: Type = UUID4 | int | str + + +class IntStrUUID4Contract(Schema): + pk: IntStrUUID4 __all__ = [ + IntStrUUID4Contract, IntStrUUID4, - EmailStr, - UUID4, ] diff --git a/skii/platform/entities.py b/skii/platform/entities.py index 91edcb6..f25697a 100644 --- a/skii/platform/entities.py +++ b/skii/platform/entities.py @@ -108,7 +108,7 @@ class Meta: class ContentEntity(models.Model): - """To big text content.""" + """Main text content.""" class Meta: abstract = True @@ -164,6 +164,9 @@ class Meta: class GeoCoordinateEntity(models.Model): class Meta: abstract = True + index_together = [ + ["latitude", "longitude"], + ] latitude = models.DecimalField( validators=[ @@ -240,7 +243,7 @@ class Meta: class EventEntity(UUIDLabelEntity): - """ EventEntity to follow exchange of Resource between Agent.""" + """EventEntity to follow exchange of Resource between Agent.""" class Meta: abstract = True diff --git a/skii/platform/models/common.py b/skii/platform/models/common.py index 71fda5a..64e03b1 100644 --- a/skii/platform/models/common.py +++ b/skii/platform/models/common.py @@ -1,4 +1,6 @@ +from decimal import Decimal from django.db import models +from django.db.models import Manager from django.utils.translation import gettext_lazy as _ from skii.platform.entities import ( @@ -21,6 +23,10 @@ def get_default_album(): class VisualElement(VisualEntity): + @property + def picture_url(self): + return self.picture.url + class Meta: verbose_name = _("Visual Album Picture") verbose_name_plural = _("Visual Album Picture(s)") @@ -49,11 +55,25 @@ class Meta: ordering = ["-last_modified", "-created", "title"] +class GeoCoordinateManager(Manager): + def get_by_natural_key(self, latitude: Decimal, longitude: Decimal): + return self.get(latitude=latitude, longitude=longitude) + + class GeoCoordinate(GeoCoordinateEntity): class Meta: verbose_name = _("Geographic coordinate") verbose_name_plural = _("Geographic coordinate(s)") ordering = ["latitude", "longitude"] + objects = GeoCoordinateManager() + def __str__(self): return f"{self.latitude} / {self.longitude} " + + def natural_key(self) -> tuple[Decimal, Decimal]: + """Define a natural primary key. + + Limit id/uuid exchange between front/back. + """ + return self.latitude, self.longitude diff --git a/skii/platform/models/event.py b/skii/platform/models/event.py index 73af570..67d0212 100644 --- a/skii/platform/models/event.py +++ b/skii/platform/models/event.py @@ -47,6 +47,7 @@ def __str__(self) -> str: @property def gant_config(self): from skii.platform.schemas.vuejs import GanttConfigContract + return GanttConfigContract( **{ "start": self.start.strftime(format="%Y-%m-%d %H:%M"), diff --git a/skii/platform/schemas/agent.py b/skii/platform/schemas/agent.py index 6d37cbb..cca0846 100644 --- a/skii/platform/schemas/agent.py +++ b/skii/platform/schemas/agent.py @@ -9,6 +9,7 @@ class UserSchema(Schema): """DJ user schema used to read record""" + pk: IntStrUUID4 first_name: Optional[str] last_name: Optional[str] username: Optional[str] @@ -16,7 +17,7 @@ class UserSchema(Schema): class UserSaveSchema(ModelSchema): - """DJ user schema used to save record""" + """DJ user schema used to save record.""" class Config: model = get_user_model() @@ -25,10 +26,12 @@ class Config: class StudentContract(Schema): user: UserSchema + pk: IntStrUUID4 class TeacherContract(Schema): user: UserSchema + pk: IntStrUUID4 class StudentSaveContract(Schema): diff --git a/skii/platform/schemas/common.py b/skii/platform/schemas/common.py index 09261fa..b7a9924 100644 --- a/skii/platform/schemas/common.py +++ b/skii/platform/schemas/common.py @@ -1,7 +1,9 @@ -from decimal import Decimal from datetime import datetime, timedelta -from typing import Optional -from ninja import Schema +from typing import Optional, List +from ninja import Schema, ModelSchema + +from skii.endpoint.schemas.identifier import IntStrUUID4 +from skii.platform.models.common import GeoCoordinate class TimeRangeContract(Schema): @@ -10,9 +12,16 @@ class TimeRangeContract(Schema): delta: Optional[timedelta] -class GeoCoordinateContract(Schema): - latitude: Decimal - longitude: Decimal +class GeoCoordinateContract(ModelSchema): + class Config: + model = GeoCoordinate + model_fields = ["latitude", "longitude"] + + +class GeoCoordinateSaveContract(ModelSchema): + class Config: + model = GeoCoordinate + model_fields = ["latitude", "longitude"] class CountryContract(Schema): @@ -21,6 +30,23 @@ class CountryContract(Schema): flag: str +class CountrySaveContract(Schema): + code: str + + class VisualPictureContract(Schema): picture_url: str title: str + description: str + + +class VisualElementContract(Schema): + title: str + description: str + picture_url: str + + +class VisualAlbumContract(Schema): + title: str + description: str + items: List[VisualElementContract] diff --git a/skii/platform/schemas/entities.py b/skii/platform/schemas/entities.py new file mode 100644 index 0000000..9ece437 --- /dev/null +++ b/skii/platform/schemas/entities.py @@ -0,0 +1,9 @@ +from ninja import ModelSchema + +from skii.platform.entities import RecordIdentityHistory + + +class RecordIdentityHistotyContract(ModelSchema): + class Config: + model = RecordIdentityHistory + model_fields = ["created", "last_modified"] diff --git a/skii/platform/schemas/event.py b/skii/platform/schemas/event.py index 409b9b6..d8c9d65 100644 --- a/skii/platform/schemas/event.py +++ b/skii/platform/schemas/event.py @@ -1,6 +1,6 @@ from datetime import datetime -from typing import List, Optional -from uuid import UUID, uuid4 +from typing import List +from skii.endpoint.schemas.identifier import IntStrUUID4 from ninja import Schema @@ -10,13 +10,24 @@ from skii.platform.schemas.agent import ( TeacherContract, StudentContract, + TeacherSaveContract, ) class LessonContract(Schema): + pk: IntStrUUID4 gant_config: GanttConfigContract start: datetime stop: datetime teacher: TeacherContract students: List[StudentContract] = [] - uuid: Optional[UUID] = uuid4 + label: str + description: str + + +class LessonSaveContract(Schema): + label: str + description: str + start: datetime + stop: datetime + teacher: TeacherSaveContract diff --git a/skii/platform/schemas/resource.py b/skii/platform/schemas/resource.py index 3cc3608..3949c5a 100644 --- a/skii/platform/schemas/resource.py +++ b/skii/platform/schemas/resource.py @@ -1,18 +1,58 @@ -from ninja import Schema +from decimal import Decimal +from typing import Optional +from ninja import Schema, ModelSchema, Field + +from skii.endpoint.schemas.identifier import IntStrUUID4 +from skii.platform.models.resource import LocationResource from skii.platform.schemas.common import ( CountryContract, VisualPictureContract, GeoCoordinateContract, + GeoCoordinateSaveContract, + VisualAlbumContract, ) -class LocationContract(Schema): +class LocationContract(ModelSchema): + class Config: + model = LocationResource + model_fields = [ + "description", + "label", + "address1", + "address2", + "city", + "country", + "cover", + "illustration", + "coordinate", + "value", + ] + + pk: IntStrUUID4 country: CountryContract cover: VisualPictureContract | None + illustration: VisualAlbumContract | None coordinate: GeoCoordinateContract | None value: int = 1 -class LocationSaveContract(LocationContract): - country: str +class LocationSaveContract(ModelSchema): + class Config: + model = LocationResource + model_fields = [ + "description", + "label", + "address1", + "address2", + "city", + "coordinate", + "value", + ] + model_fields_optional = ["description", "address2"] + + coordinate: GeoCoordinateContract | None + country: str = Field(None, alias="country.code") + # cover: VisualPictureContract | None + value: int = 1 diff --git a/tests/endpoint/test_api.py b/tests/endpoint/test_api.py index 57949f9..d4ab11a 100644 --- a/tests/endpoint/test_api.py +++ b/tests/endpoint/test_api.py @@ -16,7 +16,7 @@ class TestApiTeacher(SkiiTestCase): api_factory = TeacherAgentFactory api_save_contract = TeacherSaveContract api_route_namespace = "teacher" - fields = ["pk", "user"] + fields = ["user", "pk"] def test_record_fetch(self): agent = self.api_factory.create() @@ -66,7 +66,7 @@ class TestApiStudent(TestApiTeacher): api_save_contract = StudentSaveContract api_route_namespace = "student" - fields = ["pk", "user"] + fields = ["user", "pk"] class TestApiLocation(TestApiTeacher):