Moznost nahravat fotky copilotem
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
"""add flight_has_copilot.token
|
||||
|
||||
Revision ID: 7dd139549851
|
||||
Revises: a410455fb3bb
|
||||
Create Date: 2024-03-02 15:02:26.594830
|
||||
|
||||
"""
|
||||
import random
|
||||
import string
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import text
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '7dd139549851'
|
||||
down_revision = 'a410455fb3bb'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def rand_str():
|
||||
alphabet = string.ascii_letters + string.digits
|
||||
return ''.join([random.choice(alphabet) for _ in range(64)])
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('flight_has_copilot', sa.Column('token', sa.String(length=64), nullable=True))
|
||||
|
||||
conn = op.get_bind()
|
||||
data = conn.execute(text("SELECT flight_id, copilot_id FROM flight_has_copilot"))
|
||||
for flight_id, copilot_id in data:
|
||||
print(flight_id, copilot_id)
|
||||
conn.execute(text(
|
||||
f"UPDATE flight_has_copilot SET token='{rand_str()}'"
|
||||
f" WHERE flight_id={flight_id} AND copilot_id={copilot_id}"
|
||||
))
|
||||
|
||||
|
||||
op.create_unique_constraint(None, 'flight_has_copilot', ['token'])
|
||||
op.add_column('photo', sa.Column('created_by_copilot_id', sa.Integer(), nullable=True))
|
||||
op.create_foreign_key(None, 'photo', 'copilot', ['created_by_copilot_id'], ['id'])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint('photo_ibfk_5', 'photo', type_='foreignkey')
|
||||
op.drop_column('photo', 'created_by_copilot_id')
|
||||
op.drop_constraint('token', 'flight_has_copilot', type_='unique')
|
||||
op.drop_column('flight_has_copilot', 'token')
|
||||
# ### end Alembic commands ###
|
||||
@@ -55,6 +55,7 @@ flight_has_copilot = Table(
|
||||
BaseModel.metadata,
|
||||
Column("flight_id", ForeignKey("flight.id"), primary_key=True),
|
||||
Column("copilot_id", ForeignKey("copilot.id"), primary_key=True),
|
||||
Column("token", String(64), unique=True)
|
||||
)
|
||||
|
||||
copilot_has_photo = Table(
|
||||
@@ -141,6 +142,7 @@ class Photo(BaseModel):
|
||||
point_of_interest_id: Mapped[int] = mapped_column(Integer, ForeignKey("point_of_interest.id"), nullable=True)
|
||||
flight_id: Mapped[int] = mapped_column(Integer, ForeignKey("flight.id"), nullable=False)
|
||||
created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id'))
|
||||
created_by_copilot_id: Mapped[int] = mapped_column(Integer, ForeignKey('copilot.id'), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
||||
|
||||
flight: Mapped['Flight'] = relationship(foreign_keys=[flight_id])
|
||||
|
||||
@@ -3,6 +3,10 @@ from fastapi import HTTPException
|
||||
from starlette.status import HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
def raise_unauthorized():
|
||||
raise HTTPException(HTTP_401_UNAUTHORIZED, "Not authorized")
|
||||
|
||||
|
||||
def allow_public(func):
|
||||
@wraps(func)
|
||||
async def decorator(*args, **kwargs):
|
||||
@@ -10,7 +14,7 @@ def allow_public(func):
|
||||
user_id = kwargs['info'].context.user_id
|
||||
public = kwargs.get('public')
|
||||
if not user_id and not public:
|
||||
raise HTTPException(HTTP_401_UNAUTHORIZED)
|
||||
raise_unauthorized()
|
||||
|
||||
return await func(*args, **kwargs)
|
||||
|
||||
@@ -24,7 +28,7 @@ def authenticated_user_only(raise_when_unauthorized: bool = True, return_value_u
|
||||
if 'info' in kwargs:
|
||||
if not kwargs['info'].context.user_id:
|
||||
if raise_when_unauthorized:
|
||||
raise HTTPException(HTTP_401_UNAUTHORIZED, "Not authorized")
|
||||
raise_unauthorized()
|
||||
else:
|
||||
return return_value_unauthorized
|
||||
return await func(*args, **kwargs)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from collections import defaultdict
|
||||
from typing import Type, List, Optional
|
||||
from logger import log
|
||||
from database import models, async_session
|
||||
from database.query_builder import QueryBuilder
|
||||
|
||||
@@ -9,7 +10,7 @@ class BaseDataloader:
|
||||
self,
|
||||
model: Type[models.BaseModel],
|
||||
relationship_column, filters: Optional[list] = None
|
||||
) -> None:
|
||||
):
|
||||
super().__init__()
|
||||
self.model = model
|
||||
self.query_builder = QueryBuilder(self.model)
|
||||
@@ -43,6 +44,7 @@ class MultiModelsDataloader(BaseDataloader):
|
||||
model: Type[models.BaseModel],
|
||||
relationship_column=None,
|
||||
filters: Optional[list] = None,
|
||||
extra_select: Optional[list] = None,
|
||||
extra_join: Optional[list] = None,
|
||||
order_by: Optional[list] = None,
|
||||
):
|
||||
@@ -52,31 +54,60 @@ class MultiModelsDataloader(BaseDataloader):
|
||||
extra_join = []
|
||||
self.extra_join = extra_join
|
||||
|
||||
if extra_select is None:
|
||||
extra_select = []
|
||||
self.extra_select = extra_select
|
||||
|
||||
if order_by is None:
|
||||
order_by = [model.id.desc()] # defaultne radit od nejnovejsich zaznamu
|
||||
self.order_by = order_by
|
||||
|
||||
async def load(self, ids: List[int]):
|
||||
async with async_session() as db:
|
||||
query = (
|
||||
self.query_builder.get_simple_query(
|
||||
extra_select=[self.relationship_column],
|
||||
order_by=self.order_by
|
||||
)
|
||||
.filter(self.relationship_column.in_(set(ids)))
|
||||
.filter(*self.filters)
|
||||
def get_query(self, ids: list[int]):
|
||||
query = (
|
||||
self.query_builder.get_simple_query(
|
||||
extra_select=[self.relationship_column] + self.extra_select,
|
||||
order_by=self.order_by
|
||||
)
|
||||
.filter(self.relationship_column.in_(set(ids)))
|
||||
.filter(*self.filters)
|
||||
)
|
||||
|
||||
for joined_table in self.extra_join:
|
||||
query = query.join(joined_table)
|
||||
for joined_table in self.extra_join:
|
||||
query = query.join(joined_table)
|
||||
|
||||
if self.filters:
|
||||
query = query.filter(*self.filters)
|
||||
if self.filters:
|
||||
query = query.filter(*self.filters)
|
||||
|
||||
return query
|
||||
|
||||
async def load(self, ids: List[int]):
|
||||
query = self.get_query(ids)
|
||||
|
||||
async with async_session() as db:
|
||||
data = (await db.execute(query)).all()
|
||||
|
||||
result_data = defaultdict(list)
|
||||
for item, rel_id in data:
|
||||
result_data[rel_id].append(item)
|
||||
|
||||
result_data = self.process_data(data)
|
||||
return [result_data[id_] for id_ in ids]
|
||||
|
||||
def process_data(self, data):
|
||||
result_data = defaultdict(list)
|
||||
for row in data:
|
||||
item, rel_id = row[0:2]
|
||||
extra = row[2:]
|
||||
if extra:
|
||||
log.warning(f"Override function process_data, extra params={extra} are going to be discarded!")
|
||||
|
||||
result_data[rel_id].append(item)
|
||||
return result_data
|
||||
|
||||
|
||||
class FlightCopilotDataloader(MultiModelsDataloader):
|
||||
def process_data(self, data):
|
||||
result_data = defaultdict(list)
|
||||
for row in data:
|
||||
item, rel_id = row[0:2]
|
||||
token = row[2]
|
||||
|
||||
item.token = token
|
||||
result_data[rel_id].append(item)
|
||||
|
||||
return result_data
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from strawberry.dataloader import DataLoader
|
||||
from database import models
|
||||
from graphql_schema.dataloaders.base import MultiModelsDataloader
|
||||
from graphql_schema.dataloaders.base import MultiModelsDataloader, FlightCopilotDataloader
|
||||
|
||||
aircrafts_from_organization_dataloader = DataLoader(
|
||||
load_fn=MultiModelsDataloader(
|
||||
@@ -12,9 +12,14 @@ aircrafts_from_organization_dataloader = DataLoader(
|
||||
)
|
||||
|
||||
flight_copilots_dataloader = DataLoader(
|
||||
load_fn=MultiModelsDataloader(
|
||||
load_fn=FlightCopilotDataloader(
|
||||
models.Copilot,
|
||||
relationship_column=models.Flight.id,
|
||||
extra_select=[models.flight_has_copilot.c.token],
|
||||
filters=[
|
||||
models.flight_has_copilot.c.flight_id == models.Flight.id,
|
||||
models.flight_has_copilot.c.copilot_id == models.Copilot.id,
|
||||
],
|
||||
extra_join=[models.Copilot.flights]).load,
|
||||
cache=False)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from typing import List, Optional
|
||||
import strawberry
|
||||
from graphql import GraphQLError
|
||||
from strawberry.types import Info
|
||||
from database import models
|
||||
from decorators.error_logging import error_logging
|
||||
@@ -27,12 +28,24 @@ class CopilotQueries:
|
||||
id: Optional[int] = None,
|
||||
url_slug: Optional[str] = None,
|
||||
pilot_username: Optional[str] = None,
|
||||
upload_token: Optional[str] = None,
|
||||
upload_flight_slug: Optional[str] = None,
|
||||
public: Optional[bool] = False
|
||||
) -> Copilot:
|
||||
filter_params = get_detail_filters(id, url_slug)
|
||||
filter_params = {}
|
||||
if id:
|
||||
filter_params['object_id'] = id
|
||||
if url_slug is not None:
|
||||
filter_params['url_slug'] = url_slug
|
||||
if upload_token and upload_flight_slug:
|
||||
filter_params['upload_token'] = upload_token
|
||||
filter_params['upload_flight_slug'] = upload_flight_slug
|
||||
if pilot_username:
|
||||
filter_params['pilot_username'] = pilot_username
|
||||
|
||||
if not filter_params:
|
||||
raise GraphQLError(f"Invalid identification supplied: {filter_params}")
|
||||
|
||||
return await CopilotQueryResolver().get_one(
|
||||
user_id=info.context.user_id,
|
||||
only_public=public,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from typing import Optional
|
||||
import strawberry
|
||||
from graphql import GraphQLError
|
||||
|
||||
from decorators.endpoints import authenticated_user_only, allow_public
|
||||
from decorators.error_logging import error_logging
|
||||
from graphql_schema.entities.resolvers.flight import FlightMutationResolver, FlightQueryResolver
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import List, Optional
|
||||
import strawberry
|
||||
from database import models
|
||||
from decorators.endpoints import authenticated_user_only, allow_public
|
||||
from decorators.endpoints import authenticated_user_only, allow_public, raise_unauthorized
|
||||
from decorators.error_logging import error_logging
|
||||
from graphql_schema.entities.resolvers.base import BaseQueryResolver
|
||||
from graphql_schema.entities.resolvers.photo import PhotoMutationResolver, PhotoQueryResolver
|
||||
@@ -18,6 +18,7 @@ class PhotoQueries:
|
||||
root, info,
|
||||
flight_id: Optional[int] = None,
|
||||
copilot_id: Optional[int] = None,
|
||||
uploaded_by_copilot_id: Optional[int] = None,
|
||||
point_of_interest_id: Optional[int] = None,
|
||||
aircraft_id: Optional[int] = None,
|
||||
public: Optional[bool] = False,
|
||||
@@ -27,6 +28,7 @@ class PhotoQueries:
|
||||
flight_id=flight_id,
|
||||
user_id=info.context.user_id,
|
||||
copilot_id=copilot_id,
|
||||
uploaded_by_copilot_id=uploaded_by_copilot_id,
|
||||
aircraft_id=aircraft_id,
|
||||
point_of_interest_id=point_of_interest_id,
|
||||
order_by=[models.Photo.exposed_at]
|
||||
@@ -47,8 +49,10 @@ class PhotoQueries:
|
||||
class PhotoMutation:
|
||||
@strawberry.mutation
|
||||
@error_logging
|
||||
@authenticated_user_only()
|
||||
async def upload_photo(self, info, input: UploadPhotoInput) -> Photo:
|
||||
if info.context.user_id is None and not input.copilot_upload_token:
|
||||
raise_unauthorized()
|
||||
|
||||
return await PhotoMutationResolver().upload(info, input)
|
||||
|
||||
@strawberry.mutation()
|
||||
@@ -59,11 +63,14 @@ class PhotoMutation:
|
||||
|
||||
@strawberry.mutation()
|
||||
@error_logging
|
||||
@authenticated_user_only()
|
||||
async def change_orientation(self, info, id: int, direction: str) -> Photo:
|
||||
async def change_orientation(self, info, id: int, direction: str, copilot_upload_token: Optional[str] = None) -> Photo:
|
||||
if info.context.user_id is None and not copilot_upload_token:
|
||||
raise_unauthorized()
|
||||
|
||||
return await PhotoMutationResolver().change_orientation(
|
||||
id=id,
|
||||
user_id=info.context.user_id,
|
||||
copilot_upload_token=copilot_upload_token,
|
||||
direction=direction,
|
||||
info=info
|
||||
)
|
||||
@@ -76,6 +83,8 @@ class PhotoMutation:
|
||||
|
||||
@strawberry.mutation()
|
||||
@error_logging
|
||||
@authenticated_user_only()
|
||||
async def delete_photo(self, info, id: int) -> Photo:
|
||||
async def delete_photo(self, info, id: int, copilot_upload_token: Optional[str] = None) -> Photo:
|
||||
if info.context.user_id is None and not copilot_upload_token:
|
||||
raise_unauthorized()
|
||||
|
||||
return await PhotoMutationResolver().delete(user_id=info.context.user_id, id=id)
|
||||
|
||||
@@ -120,7 +120,7 @@ class BaseMutationResolver(BaseResolver):
|
||||
item = await self._get_one(db, id, user_id)
|
||||
return await self._do_update(db, item, data.to_dict())
|
||||
|
||||
async def delete(self, user_id: int, id: int) -> GQL_TYPE:
|
||||
async def delete(self, user_id: int, id: int, **kwargs) -> GQL_TYPE:
|
||||
async with get_session() as db:
|
||||
model = await self._get_one(db, id, user_id)
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import and_
|
||||
|
||||
from database import models
|
||||
from graphql_schema.entities.resolvers.base import BaseQueryResolver
|
||||
from graphql_schema.entities.types.types import Copilot
|
||||
@@ -17,6 +20,8 @@ class CopilotQueryResolver(BaseQueryResolver):
|
||||
**kwargs
|
||||
):
|
||||
pilot_username = kwargs.pop("pilot_username", None)
|
||||
upload_token = kwargs.pop("upload_token", None)
|
||||
upload_flight_slug = kwargs.pop("upload_flight_slug", None)
|
||||
|
||||
query = super().get_query(
|
||||
user_id=user_id, object_id=object_id, order_by=order_by,
|
||||
@@ -31,4 +36,14 @@ class CopilotQueryResolver(BaseQueryResolver):
|
||||
.filter(models.User.public_username == pilot_username)
|
||||
)
|
||||
|
||||
if upload_token and upload_flight_slug:
|
||||
query = (
|
||||
query.join(models.Copilot.flights)
|
||||
.filter(and_(
|
||||
models.Flight.url_slug == upload_flight_slug,
|
||||
models.Flight.created_by_id == models.User.id
|
||||
))
|
||||
.filter(models.flight_has_copilot.c.token == upload_token)
|
||||
)
|
||||
|
||||
return query
|
||||
|
||||
@@ -17,6 +17,7 @@ from graphql_schema.entities.types.mutation_input import (
|
||||
from graphql_schema.entities.types.types import Flight
|
||||
from paths import FLIGHT_GPX_TRACK_PATH
|
||||
from utils.file import delete_file
|
||||
from utils.str_utils import random_str
|
||||
from utils.upload import handle_file_upload
|
||||
|
||||
|
||||
@@ -232,7 +233,11 @@ class FlightMutationResolver(BaseMutationResolver):
|
||||
])
|
||||
|
||||
for copilot_id in copilots:
|
||||
await db.execute(insert(flight_has_copilot).values(flight_id=flight_id, copilot_id=copilot_id))
|
||||
await db.execute(insert(flight_has_copilot).values(
|
||||
flight_id=flight_id,
|
||||
copilot_id=copilot_id,
|
||||
token=random_str(64)
|
||||
))
|
||||
|
||||
return await self._do_update(db, flight_data, data)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from time import time
|
||||
from typing import Optional
|
||||
from PIL import Image
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import delete, insert
|
||||
from sqlalchemy import delete, insert, select
|
||||
from background_jobs.elevation import add_terrain_elevation_to_photo
|
||||
from background_jobs.photo import generate_thumbnail, resize_photo
|
||||
from database import models
|
||||
@@ -54,6 +54,9 @@ class PhotoQueryResolver(BaseQueryResolver):
|
||||
.filter(models.copilot_has_photo.c.copilot_id == kwargs['copilot_id'])
|
||||
)
|
||||
|
||||
if kwargs.get("uploaded_by_copilot_id"):
|
||||
query = query.filter(models.Photo.created_by_copilot_id == kwargs['uploaded_by_copilot_id'])
|
||||
|
||||
return query
|
||||
|
||||
|
||||
@@ -76,9 +79,13 @@ class PhotoMutationResolver(BaseMutationResolver):
|
||||
|
||||
return original_filename
|
||||
|
||||
async def _get_photo_details(self, id: int, user_id: int):
|
||||
async def _get_photo_details(self, id: int, user_id: int, copilot_token: Optional[str]):
|
||||
async with get_session() as db:
|
||||
photo = await self._get_one(db, id, created_by_id=user_id)
|
||||
if not copilot_token:
|
||||
photo = await self._get_one(db, id, created_by_id=user_id)
|
||||
else:
|
||||
photo = await self._get_photo_info_by_copilot_token(db, photo_id=id, copilot_token=copilot_token)
|
||||
|
||||
flight_id = photo.flight_id
|
||||
filename = photo.filename + "." + photo.filename_extension
|
||||
|
||||
@@ -87,17 +94,48 @@ class PhotoMutationResolver(BaseMutationResolver):
|
||||
flight_id=flight_id,
|
||||
path=path,
|
||||
filename=filename,
|
||||
original_filename=self._copy_original(path, filename)
|
||||
original_filename=self._copy_original(path, filename) # TODO: tohle nedelat v getteru!
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def _get_photo_info_by_copilot_token(db, photo_id: int, copilot_token: Optional[str]):
|
||||
if not copilot_token:
|
||||
return
|
||||
|
||||
return (
|
||||
await db.scalars(
|
||||
select(models.Photo)
|
||||
.join(models.Photo.flight)
|
||||
.join(models.flight_has_copilot)
|
||||
.filter(models.flight_has_copilot.c.token == copilot_token)
|
||||
.filter(models.Photo.id == photo_id)
|
||||
|
||||
)
|
||||
).one()
|
||||
|
||||
@staticmethod
|
||||
async def _validate_copilot_token(flight_id: int, token: str) -> tuple[int, int]:
|
||||
async with get_session() as db:
|
||||
query = (
|
||||
select(models.Copilot.created_by_id, models.Copilot.id)
|
||||
.join(models.flight_has_copilot)
|
||||
.filter(models.flight_has_copilot.c.flight_id == flight_id)
|
||||
.filter(models.flight_has_copilot.c.token == token)
|
||||
)
|
||||
return (await db.execute(query)).fetchone()
|
||||
|
||||
async def upload(self, info, input: UploadPhotoInput) -> Photo:
|
||||
created_by_id = info.context.user_id
|
||||
copilot_id = None
|
||||
if input.copilot_upload_token:
|
||||
created_by_id, copilot_id = await self._validate_copilot_token(input.flight_id, input.copilot_upload_token)
|
||||
|
||||
path = get_photo_basepath(input.flight_id)
|
||||
img_name = await handle_file_upload(input.photo, path, uid_prefix=False, overwrite=False)
|
||||
|
||||
exif_info = await parse_exif_info(path, img_name)
|
||||
|
||||
img = Image.open(f"{path}/{img_name}")
|
||||
filename, filename_ext = os.path.splitext(img_name)
|
||||
exif_info = await parse_exif_info(path, img_name)
|
||||
|
||||
async with get_session() as db:
|
||||
photo = await PhotoMutationResolver()._do_create(
|
||||
@@ -115,7 +153,8 @@ class PhotoMutationResolver(BaseMutationResolver):
|
||||
"gps_latitude": exif_info.get("gps_latitude"),
|
||||
"gps_longitude": exif_info.get("gps_longitude"),
|
||||
"gps_altitude": exif_info.get("gps_altitude"),
|
||||
"created_by_id": info.context.user_id,
|
||||
"created_by_id": created_by_id,
|
||||
"created_by_copilot_id": copilot_id
|
||||
},
|
||||
)
|
||||
|
||||
@@ -148,8 +187,10 @@ class PhotoMutationResolver(BaseMutationResolver):
|
||||
|
||||
return await self._do_update(db, obj=photo, data=data)
|
||||
|
||||
async def change_orientation(self, id: int, user_id: int, direction: str, info):
|
||||
photo = await self._get_photo_details(id, user_id)
|
||||
async def change_orientation(
|
||||
self, id: int, user_id: int, direction: str, copilot_upload_token: Optional[str], info
|
||||
):
|
||||
photo = await self._get_photo_details(id, user_id, copilot_upload_token)
|
||||
|
||||
degrees_map = {
|
||||
"clockwise": 90,
|
||||
@@ -176,7 +217,7 @@ class PhotoMutationResolver(BaseMutationResolver):
|
||||
})
|
||||
|
||||
async def adjust(self, id: int, user_id: int, adjustment: AdjustmentInput, info):
|
||||
photo = await self._get_photo_details(id, user_id)
|
||||
photo = await self._get_photo_details(id, user_id, copilot_token=None)
|
||||
editor = (
|
||||
PhotoEditor(photo.path, photo.original_filename)
|
||||
.adjust(
|
||||
@@ -221,9 +262,12 @@ class PhotoMutationResolver(BaseMutationResolver):
|
||||
"cache_key": int(time())
|
||||
})
|
||||
|
||||
async def delete(self, user_id: int, id: int) -> Photo:
|
||||
photo = await super().delete(user_id, id)
|
||||
async def delete(self, user_id: int, id: int, **kwargs) -> Photo:
|
||||
copilot_upload_token = kwargs.get("copilot_upload_token")
|
||||
await self._get_photo_details(id, user_id, copilot_upload_token)
|
||||
# ^^ pokud neexistuje, vyhodi vyjimku
|
||||
|
||||
photo = await super().delete(user_id, id)
|
||||
base_path = get_photo_basepath(photo.flight_id)
|
||||
|
||||
files_to_delete = [
|
||||
|
||||
@@ -61,6 +61,7 @@ class UploadPhotoInput:
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
point_of_interest: Optional[ComboboxInput] = None
|
||||
copilot_upload_token: Optional[str] = None
|
||||
|
||||
|
||||
@strawberry.input
|
||||
|
||||
@@ -170,9 +170,16 @@ class Copilot:
|
||||
|
||||
return await dataloader.load(root.id)
|
||||
|
||||
def resolve_copilot_token(root, info):
|
||||
return getattr(root, "token")
|
||||
|
||||
flights: List[Flight] = strawberry.field(resolver=resolve_flights)
|
||||
photos: List[Photo] = strawberry.field(resolver=lambda root: photo_copilots_dataloader.load(root.id))
|
||||
title_photo: Optional[Photo] = strawberry.field(resolver=lambda root: photo_dataloader.load(root.title_photo_id))
|
||||
token: Optional[str] = strawberry.field(
|
||||
permission_classes=[IsAuthenticated],
|
||||
resolver=resolve_copilot_token
|
||||
)
|
||||
|
||||
|
||||
@strawberry_sqlalchemy_type(models.Aircraft)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import random
|
||||
import string
|
||||
|
||||
|
||||
def random_str(length: int) -> str:
|
||||
alphabet = string.ascii_letters + string.digits
|
||||
return ''.join([random.choice(alphabet) for _ in range(length)])
|
||||
Reference in New Issue
Block a user