feat: add hotel and vehicle option site modules

Add full support for hotel group and vehicle option site management features:
- Define SQLAlchemy models and alembic migration for the new database tables
- Add default sample content entries in content.py
- Extend admin and public API routes to support the new modules
- Update seed script to populate default hotel and vehicle data
- Update all relevant documentation and test cases
This commit is contained in:
duanshuwen
2026-07-03 20:26:44 +08:00
parent 72d388a047
commit 201e835eab
13 changed files with 426 additions and 34 deletions

View File

@@ -8,7 +8,7 @@ from urllib.parse import quote
from sqlalchemy import delete, select
from sqlalchemy.orm import Session
from .auth import hash_password
from .content import ALIASES, CAMPAIGNS, CTAS, DESTINATIONS, HERO_SLIDES, THEMES
from .content import ALIASES, CAMPAIGNS, CTAS, DESTINATIONS, HERO_SLIDES, HOTEL_GROUPS, THEMES, VEHICLE_OPTIONS
from .database import Base, SessionLocal, engine
from .models import (
AdminUser,
@@ -18,6 +18,7 @@ from .models import (
Destination,
DestinationAlias,
HeroSlide,
HotelGroup,
MediaAsset,
Product,
ProductImage,
@@ -25,6 +26,7 @@ from .models import (
RouteSectionProduct,
SiteVersion,
ThemeCard,
VehicleOption,
utc_now,
)
from .route_sections import ROUTE_SECTION_DEFAULTS
@@ -100,7 +102,7 @@ def load_products() -> list[dict]:
def reset_guizhou_content(db: Session) -> dict:
products = load_products()
for model in [SiteVersion, CampaignProduct, RouteSectionProduct, Campaign, RouteSection, ProductImage, Product, CtaBanner, ThemeCard, HeroSlide, DestinationAlias, Destination, MediaAsset]:
for model in [SiteVersion, CampaignProduct, RouteSectionProduct, Campaign, RouteSection, ProductImage, Product, VehicleOption, HotelGroup, CtaBanner, ThemeCard, HeroSlide, DestinationAlias, Destination, MediaAsset]:
db.execute(delete(model))
db.flush()
@@ -136,6 +138,27 @@ def reset_guizhou_content(db: Session) -> dict:
create_media(db, image, "cta", alt)
db.add(CtaBanner(alt=alt, image=image, targetType=target_type, targetValue=target_value, sortOrder=index))
for index, group in enumerate(HOTEL_GROUPS):
create_media(db, group["image"], "hotel-group", group["title"])
db.add(
HotelGroup(
title=group["title"],
description=group.get("description"),
image=group.get("image"),
sortOrder=index,
)
)
for index, option in enumerate(VEHICLE_OPTIONS):
create_media(db, option["image"], "vehicle-option", option["title"])
db.add(
VehicleOption(
title=option["title"],
description=option.get("description"),
image=option.get("image"),
sortOrder=index,
)
)
for product in products:
create_media(db, product.get("image"), "product", product["title"])
matched_destination = product.get("destinationName") if product.get("destinationName") in destination_map else None
@@ -203,6 +226,8 @@ def reset_guizhou_content(db: Session) -> dict:
"themeCards": len(THEMES),
"products": len(products),
"routeSections": len(ROUTE_SECTION_DEFAULTS),
"hotelGroups": len(HOTEL_GROUPS),
"vehicleOptions": len(VEHICLE_OPTIONS),
},
)
db.add(snapshot)
@@ -214,6 +239,8 @@ def reset_guizhou_content(db: Session) -> dict:
"ctaBanners": len(CTAS),
"products": len(products),
"routeSections": len(ROUTE_SECTION_DEFAULTS),
"hotelGroups": len(HOTEL_GROUPS),
"vehicleOptions": len(VEHICLE_OPTIONS),
"siteVersionId": snapshot.id,
}