✨ Trip: item image and gpx
This commit is contained in:
parent
65b1e35186
commit
7869ba9931
37
backend/trip/alembic/versions/8775a65d510f_tripitem_image.py
Normal file
37
backend/trip/alembic/versions/8775a65d510f_tripitem_image.py
Normal file
@ -0,0 +1,37 @@
|
||||
"""TripItem image
|
||||
|
||||
Revision ID: 8775a65d510f
|
||||
Revises: 7e331b851cb7
|
||||
Create Date: 2025-09-20 20:01:43.884115
|
||||
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel.sql.sqltypes
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "8775a65d510f"
|
||||
down_revision = "7e331b851cb7"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table("tripitem", schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column("image_id", sa.Integer(), nullable=True))
|
||||
batch_op.create_foreign_key(
|
||||
batch_op.f("fk_tripitem_image_id_image"), "image", ["image_id"], ["id"], ondelete="CASCADE"
|
||||
)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table("tripitem", schema=None) as batch_op:
|
||||
batch_op.drop_constraint(batch_op.f("fk_tripitem_image_id_image"), type_="foreignkey")
|
||||
batch_op.drop_column("image_id")
|
||||
|
||||
# ### end Alembic commands ###
|
||||
33
backend/trip/alembic/versions/8e12410a0b8e_tripitem_gpx.py
Normal file
33
backend/trip/alembic/versions/8e12410a0b8e_tripitem_gpx.py
Normal file
@ -0,0 +1,33 @@
|
||||
"""TripItem GPX
|
||||
|
||||
Revision ID: 8e12410a0b8e
|
||||
Revises: 8775a65d510f
|
||||
Create Date: 2025-09-20 19:21:18.294547
|
||||
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel.sql.sqltypes
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "8e12410a0b8e"
|
||||
down_revision = "8775a65d510f"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table("tripitem", schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column("gpx", sqlmodel.sql.sqltypes.AutoString(), nullable=True))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table("tripitem", schema=None) as batch_op:
|
||||
batch_op.drop_column("gpx")
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@ -75,6 +75,7 @@ class Image(ImageBase, table=True):
|
||||
categories: list["Category"] = Relationship(back_populates="image")
|
||||
places: list["Place"] = Relationship(back_populates="image")
|
||||
trips: list["Trip"] = Relationship(back_populates="image")
|
||||
tripitems: list["TripItem"] = Relationship(back_populates="image")
|
||||
|
||||
|
||||
class UserBase(SQLModel):
|
||||
@ -199,7 +200,6 @@ class Place(PlaceBase, table=True):
|
||||
class PlaceCreate(PlaceBase):
|
||||
image: str | None = None
|
||||
category_id: int
|
||||
gpx: str | None = None
|
||||
|
||||
|
||||
class PlacesCreate(PlaceBase):
|
||||
@ -393,6 +393,7 @@ class TripItemBase(SQLModel):
|
||||
price: float | None = None
|
||||
lng: float | None = None
|
||||
status: TripItemStatusEnum | None = None
|
||||
gpx: str | None = None
|
||||
|
||||
@field_validator("time", mode="before")
|
||||
def pad_mm_if_needed(cls, value: str) -> str:
|
||||
@ -407,6 +408,9 @@ class TripItem(TripItemBase, table=True):
|
||||
place_id: int | None = Field(default=None, foreign_key="place.id")
|
||||
place: Place | None = Relationship(back_populates="trip_items")
|
||||
|
||||
image_id: int | None = Field(default=None, foreign_key="image.id", ondelete="CASCADE")
|
||||
image: Image | None = Relationship(back_populates="tripitems")
|
||||
|
||||
day_id: int = Field(foreign_key="tripday.id", ondelete="CASCADE")
|
||||
day: TripDay | None = Relationship(back_populates="items")
|
||||
|
||||
@ -414,6 +418,7 @@ class TripItem(TripItemBase, table=True):
|
||||
class TripItemCreate(TripItemBase):
|
||||
place: int | None = None
|
||||
status: TripItemStatusEnum | None = None
|
||||
image: str | None = None
|
||||
|
||||
|
||||
class TripItemUpdate(TripItemBase):
|
||||
@ -422,6 +427,7 @@ class TripItemUpdate(TripItemBase):
|
||||
place: int | None = None
|
||||
day_id: int | None = None
|
||||
status: TripItemStatusEnum | None = None
|
||||
image: str | None = None
|
||||
|
||||
|
||||
class TripItemRead(TripItemBase):
|
||||
@ -429,6 +435,8 @@ class TripItemRead(TripItemBase):
|
||||
place: PlaceRead | None
|
||||
day_id: int
|
||||
status: TripItemStatusEnum | None
|
||||
image: str | None
|
||||
image_id: int | None
|
||||
|
||||
@classmethod
|
||||
def serialize(cls, obj: TripItem) -> "TripItemRead":
|
||||
@ -443,6 +451,9 @@ class TripItemRead(TripItemBase):
|
||||
day_id=obj.day_id,
|
||||
status=obj.status,
|
||||
place=PlaceRead.serialize(obj.place) if obj.place else None,
|
||||
image=_prefix_assets_url(obj.image.filename) if obj.image else None,
|
||||
image_id=obj.image_id,
|
||||
gpx=obj.gpx,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -346,6 +346,18 @@ def create_tripitem(
|
||||
status=item.status,
|
||||
)
|
||||
|
||||
if item.image:
|
||||
image_bytes = b64img_decode(item.image)
|
||||
filename = save_image_to_file(image_bytes, 0)
|
||||
if not filename:
|
||||
raise HTTPException(status_code=400, detail="Bad request")
|
||||
|
||||
image = Image(filename=filename, user=current_user)
|
||||
session.add(image)
|
||||
session.flush()
|
||||
session.refresh(image)
|
||||
new_item.image_id = image.id
|
||||
|
||||
if item.place is not None:
|
||||
place_in_trip = any(place.id == item.place for place in db_trip.places)
|
||||
if not place_in_trip:
|
||||
@ -382,6 +394,46 @@ def update_tripitem(
|
||||
raise HTTPException(status_code=400, detail="Bad request")
|
||||
|
||||
item_data = item.model_dump(exclude_unset=True)
|
||||
# TODO: Optimize logic; image=data: parse / image=none: remove / no image key: pass
|
||||
if "image" in item_data: # no image key: pass
|
||||
image_b64 = item_data.pop("image", None) # image=data: parse
|
||||
if image_b64:
|
||||
try:
|
||||
image_bytes = b64img_decode(image_b64)
|
||||
except Exception:
|
||||
raise HTTPException(status_code=400, detail="Bad request")
|
||||
|
||||
filename = save_image_to_file(image_bytes, 0)
|
||||
if not filename:
|
||||
raise HTTPException(status_code=400, detail="Bad request")
|
||||
|
||||
image = Image(filename=filename, user=current_user)
|
||||
session.add(image)
|
||||
session.flush()
|
||||
session.refresh(image)
|
||||
|
||||
if db_item.image_id:
|
||||
old_image = session.get(Image, db_item.image_id)
|
||||
try:
|
||||
remove_image(old_image.filename)
|
||||
session.delete(old_image)
|
||||
db_item.image_id = None
|
||||
session.refresh(db_item)
|
||||
except Exception:
|
||||
raise HTTPException(status_code=400, detail="Bad request")
|
||||
|
||||
db_item.image_id = image.id
|
||||
|
||||
else: # image=none: remove if previous
|
||||
if getattr(db_item, "image_id", None):
|
||||
old_image = session.get(Image, db_item.image_id)
|
||||
try:
|
||||
remove_image(old_image.filename)
|
||||
session.delete(old_image)
|
||||
db_item.image_id = None
|
||||
session.refresh(db_item)
|
||||
except Exception:
|
||||
raise HTTPException(status_code=400, detail="Bad request")
|
||||
|
||||
place_id = item_data.pop("place", None)
|
||||
db_item.place_id = place_id
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user