From ba5c3b6a4992f784ca7c3ee8cd0bbeb46a36f34b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Kv=C3=A1=C4=8Dek?= Date: Tue, 27 Jun 2023 22:45:03 +0200 Subject: [PATCH] Uprava nahravani, zmeny v modelech --- src/database/models.py | 11 ++-- src/dependencies/db.py | 1 + src/graphql_schema/entities/aircraft.py | 7 +-- src/graphql_schema/entities/flight.py | 33 +++++++++++- src/graphql_schema/entities/photo.py | 52 +++++++++++++++++++ src/graphql_schema/mutation.py | 3 +- .../sqlalchemy_to_strawberry_type.py | 25 +++++++-- src/main.py | 2 +- src/upload_utils.py | 7 ++- 9 files changed, 124 insertions(+), 17 deletions(-) create mode 100644 src/graphql_schema/entities/photo.py diff --git a/src/database/models.py b/src/database/models.py index 3e83da7..811b34c 100644 --- a/src/database/models.py +++ b/src/database/models.py @@ -1,6 +1,6 @@ from __future__ import annotations import datetime -from typing import Set +from typing import Set, List from sqlalchemy import String, DateTime, ForeignKey, Text, Integer, func, Table, Column, Boolean, select from sqlalchemy.orm import Mapped, relationship, as_declarative, mapped_column from database.custom_types import Point @@ -9,6 +9,8 @@ from sqlalchemy.ext.asyncio import AsyncSession @as_declarative() class BaseModel: + # __mapper_args__ = {"eager_defaults": True} + excluded_columns_in_dict = tuple() def as_dict(self): @@ -26,7 +28,8 @@ class BaseModel: async def create(cls, db_session: AsyncSession, data: dict): model = cls(**data) db_session.add(model) - await db_session.commit() + # await db_session.flush() + # await db_session.refresh(model) return model @@ -111,7 +114,7 @@ class Photo(BaseModel): created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id')) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) - flight: Mapped['Flight'] = relationship(back_populates="photos") + flight: Mapped['Flight'] = relationship() created_by: Mapped['User'] = relationship() @@ -202,7 +205,7 @@ class Flight(BaseModel): landing_airport: Mapped['Airport'] = relationship(foreign_keys=[landing_airport_id]) copilot: Mapped['Copilot'] = relationship(back_populates="flights") aircraft: Mapped['Aircraft'] = relationship(back_populates="flights") - photos: Mapped[Set['Photo']] = relationship() + photos: Mapped[List['Photo']] = relationship() user: Mapped['User'] = relationship(back_populates="flights") created_by: Mapped['User'] = relationship() diff --git a/src/dependencies/db.py b/src/dependencies/db.py index bbbaefe..25144e1 100644 --- a/src/dependencies/db.py +++ b/src/dependencies/db.py @@ -5,3 +5,4 @@ async def db_session(): async with async_session() as session: async with session.begin(): yield session + await session.commit() \ No newline at end of file diff --git a/src/graphql_schema/entities/aircraft.py b/src/graphql_schema/entities/aircraft.py index 1590dd5..e755243 100644 --- a/src/graphql_schema/entities/aircraft.py +++ b/src/graphql_schema/entities/aircraft.py @@ -5,7 +5,7 @@ from strawberry.file_uploads import Upload from sqlalchemy import select from database import models from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type, strawberry_sqlalchemy_input -from upload_utils import handle_img_upload, delete_file +from upload_utils import handle_file_upload, delete_file AIRCRAFT_UPLOAD_DEST_PATH = "/app/uploads/aircrafts/" @@ -52,13 +52,14 @@ class CreateAircraftMutation: class CreateAircraftInput: photo: Optional[Upload] + @strawberry.mutation async def create_aircraft(root, info, input: CreateAircraftInput) -> Aircraft: # TODO: kontrola organizace input_data = input.to_dict() if input.photo: - input_data['photo_filename'] = await handle_img_upload(input.photo, AIRCRAFT_UPLOAD_DEST_PATH) + input_data['photo_filename'] = await handle_file_upload(input.photo, AIRCRAFT_UPLOAD_DEST_PATH) return await models.Aircraft.create( info.context.db, @@ -86,7 +87,7 @@ class EditAircraftMutation: if input.photo: if aircraft.photo_filename: delete_file(AIRCRAFT_UPLOAD_DEST_PATH + "/" + aircraft.photo_filename, silent=True) - update_data['photo_filename'] = await handle_img_upload(input.photo, AIRCRAFT_UPLOAD_DEST_PATH) + update_data['photo_filename'] = await handle_file_upload(input.photo, AIRCRAFT_UPLOAD_DEST_PATH) return await models.Aircraft.update(info.context.db, obj=aircraft, data=update_data) diff --git a/src/graphql_schema/entities/flight.py b/src/graphql_schema/entities/flight.py index d8a8b99..31eda7b 100644 --- a/src/graphql_schema/entities/flight.py +++ b/src/graphql_schema/entities/flight.py @@ -12,6 +12,7 @@ from graphql_schema.entities.aircraft import Aircraft from graphql_schema.entities.airport import Airport from graphql_schema.entities.copilot import CopilotType from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type, strawberry_sqlalchemy_input +from upload_utils import handle_file_upload # Bude se hodit: https://strawberry.rocks/docs/types/lazy @@ -88,7 +89,37 @@ class CreateFlightMutation: async def create_flight(self, info, input: CreateFlightInput) -> Flight: input_data = input.to_dict() - return await models.Flight.create(info.context.db, data={ + flight = await models.Flight.create(info.context.db, data={ **input_data, + # "photos": [], # aby se nedelal select pri vytvareni fotek "created_by_id": info.context.user_id }) + + await info.context.db.flush() + # + # if input.photos: + # photos_dest = f"/app/uploads/photos/{flight.id}/" + # for photo in input.photos: + # filename = await handle_file_upload(photo, photos_dest) + # flight.photos.append(models.Photo(**{ + # "name": "", + # "filename": filename, + # "description": "", + # "created_by_id": info.context.user_id, + # })) + + return flight + + +@strawberry.type +class EditFlightMutation: + + @strawberry_sqlalchemy_input(models.Flight, exclude_fields=["id"], all_optional=True) + class EditFlightInput: + pass + + @strawberry.mutation + async def edit_flight(self, info, id: int, input: EditFlightInput) -> Flight: + input_data = input.to_dict() + + return await models.Flight.update(info.context.db, id=id, data=input_data) \ No newline at end of file diff --git a/src/graphql_schema/entities/photo.py b/src/graphql_schema/entities/photo.py new file mode 100644 index 0000000..d45523a --- /dev/null +++ b/src/graphql_schema/entities/photo.py @@ -0,0 +1,52 @@ +from typing import List, Optional +import strawberry +from sqlalchemy import select +from database import models +from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type, strawberry_sqlalchemy_input + + +@strawberry_sqlalchemy_type(models.Photo) +class Photo: + pass + + +def get_base_query(user_id: int): + return ( + select(models.Photo) + .filter(models.Photo.created_by_id == user_id) + .order_by(models.Photo.id.desc()) + ) + + +@strawberry.type +class PhotoQueries: + @strawberry.field + async def photos(root, info) -> List[Photo]: + query = get_base_query(info.context.user_id) + return (await info.context.db.scalars(query)).all() + + +@strawberry.type +class DeletePhotoMutation: + + @strawberry.input + class DeletePhotoInput: + id: int + + @strawberry_sqlalchemy_input(models.Photo, exclude_fields=[], all_optional=True) + class UpdatePhotoInput: + pass + + @strawberry.mutation + async def update_photo(self, info, input: UpdatePhotoInput) -> Photo: + query = get_base_query(info.context.user_id) + photo = info.context.db.scalars(query.filter(models.Photo.id == input.id)) + + update_data = input.to_dict() + photo.update(**update_data) + + return photo + + @strawberry.mutation + async def delete_photo(self, info, input: DeletePhotoInput) -> Photo: + pass diff --git a/src/graphql_schema/mutation.py b/src/graphql_schema/mutation.py index 15cc5d0..bcf3b2a 100644 --- a/src/graphql_schema/mutation.py +++ b/src/graphql_schema/mutation.py @@ -1,10 +1,11 @@ from strawberry.tools import merge_types from graphql_schema.entities.aircraft import CreateAircraftMutation, EditAircraftMutation, DeleteAircraftMutation -from graphql_schema.entities.flight import CreateFlightMutation +from graphql_schema.entities.flight import CreateFlightMutation, EditFlightMutation Mutation = merge_types("Mutation", ( CreateAircraftMutation, EditAircraftMutation, DeleteAircraftMutation, + EditFlightMutation, CreateFlightMutation, )) diff --git a/src/graphql_schema/sqlalchemy_to_strawberry_type.py b/src/graphql_schema/sqlalchemy_to_strawberry_type.py index 4a40674..cc212d4 100644 --- a/src/graphql_schema/sqlalchemy_to_strawberry_type.py +++ b/src/graphql_schema/sqlalchemy_to_strawberry_type.py @@ -18,7 +18,8 @@ def get_annotations_for_scalars(model: BaseModel, exclude_fields=None, force_opt for name, column in get_columns_from_model(model, exclude_fields): is_optional = column.nullable or force_optional try: - annotations_[name] = column.type.python_type if not is_optional else typing.Optional[column.type.python_type] + type_ = typing.Optional[column.type.python_type] if is_optional else column.type.python_type + annotations_[name] = type_ except NotImplementedError as e: print(f"Neimplementovano: {e}, {name=}") @@ -50,15 +51,29 @@ def strawberry_sqlalchemy_input( ignored_fields = exclude_fields + ["created_at", "created_by_id", "updated_by_id", "updated_at", "deleted"] def to_dict(self): - return {name: getattr(self, name) for name, _ in get_columns_from_model(model, ignored_fields)} + return {name: getattr(self, name) for name, _ in get_columns_from_model(model, ignored_fields) if getattr(self, name) is not None} def wrapper(cls): - cls.__annotations__.update(get_annotations_for_scalars( + annotations = get_annotations_for_scalars( model, exclude_fields=ignored_fields, force_optional=all_optional - )) + ) + + cls.__annotations__.update(annotations) cls.to_dict = to_dict - return strawberry.input(cls) + + for col, col_type in annotations.items(): + try: + print("AAAAAAAAAA", col_type) + if col_type._name == 'Optional': # noqa + setattr(cls, col, None) + print(" nastavuji") + except AttributeError: + pass + + input_cls = strawberry.input(cls) + + return input_cls return wrapper diff --git a/src/main.py b/src/main.py index 635a0d1..625d800 100644 --- a/src/main.py +++ b/src/main.py @@ -53,7 +53,7 @@ class App: if APP_DEBUG: @self.api_router.get("/graphql/autologin") async def autologin(): - access_token = self.access_security.create_access_token(subject={"id": 8, "name": "Franta Vomacka"}) + access_token = self.access_security.create_access_token(subject={"id": 18, "name": "Franta Vomacka"}) response = RedirectResponse(url="/graphql") self.access_security.set_access_cookie(response, access_token) diff --git a/src/upload_utils.py b/src/upload_utils.py index 1a1f9df..78c7941 100644 --- a/src/upload_utils.py +++ b/src/upload_utils.py @@ -9,7 +9,7 @@ def check_directories(path: str): os.makedirs(path) -async def handle_img_upload(file: Upload, path: str): +async def handle_file_upload(file: Upload, path: str): check_directories(path) filename = f"{uuid.uuid4()}-{file.filename}" @@ -19,5 +19,8 @@ async def handle_img_upload(file: Upload, path: str): image.write(content) image.close() + return filename + + def delete_file(path: str, silent: bool = False): - os.remove(path) \ No newline at end of file + os.remove(path)