feat: add map image site module
This commit is contained in:
28
alembic/versions/0002_add_map_image.py
Normal file
28
alembic/versions/0002_add_map_image.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
"""Add map image module table.
|
||||||
|
|
||||||
|
Revision ID: 0002_add_map_image
|
||||||
|
Revises: 0001_initial_schema
|
||||||
|
Create Date: 2026-07-01
|
||||||
|
"""
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
from app.models import MapImage
|
||||||
|
|
||||||
|
|
||||||
|
revision = "0002_add_map_image"
|
||||||
|
down_revision = "0001_initial_schema"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
bind = op.get_bind()
|
||||||
|
if "MapImage" not in set(inspect(bind).get_table_names()):
|
||||||
|
MapImage.__table__.create(bind)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
bind = op.get_bind()
|
||||||
|
if "MapImage" in set(inspect(bind).get_table_names()):
|
||||||
|
MapImage.__table__.drop(bind)
|
||||||
@@ -88,6 +88,16 @@ class DestinationAlias(Base):
|
|||||||
destination: Mapped[Destination] = relationship(back_populates="aliases")
|
destination: Mapped[Destination] = relationship(back_populates="aliases")
|
||||||
|
|
||||||
|
|
||||||
|
class MapImage(Base):
|
||||||
|
__tablename__ = "MapImage"
|
||||||
|
|
||||||
|
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
|
||||||
|
image: Mapped[str] = mapped_column(String, nullable=False)
|
||||||
|
isActive: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||||||
|
createdAt: Mapped[datetime] = mapped_column(DateTime, default=utc_now, nullable=False)
|
||||||
|
updatedAt: Mapped[datetime] = mapped_column(DateTime, default=utc_now, onupdate=utc_now, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class ThemeCard(Base):
|
class ThemeCard(Base):
|
||||||
__tablename__ = "ThemeCard"
|
__tablename__ = "ThemeCard"
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from ..models import (
|
|||||||
Destination,
|
Destination,
|
||||||
HeroSlide,
|
HeroSlide,
|
||||||
Lead,
|
Lead,
|
||||||
|
MapImage,
|
||||||
MediaAsset,
|
MediaAsset,
|
||||||
Product,
|
Product,
|
||||||
ProductImage,
|
ProductImage,
|
||||||
@@ -62,6 +63,16 @@ SITE_CONFIG_MODULES = {
|
|||||||
"none_to_empty": set(),
|
"none_to_empty": set(),
|
||||||
"create_defaults": {"isHot": False},
|
"create_defaults": {"isHot": False},
|
||||||
},
|
},
|
||||||
|
"map": {
|
||||||
|
"model": MapImage,
|
||||||
|
"entity": "map_image",
|
||||||
|
"primary": "image",
|
||||||
|
"fields": {"image", "isActive"},
|
||||||
|
"none_to_empty": set(),
|
||||||
|
"create_defaults": {},
|
||||||
|
"ordered": False,
|
||||||
|
"singleton": True,
|
||||||
|
},
|
||||||
"themes": {
|
"themes": {
|
||||||
"model": ThemeCard,
|
"model": ThemeCard,
|
||||||
"entity": "theme_card",
|
"entity": "theme_card",
|
||||||
@@ -302,14 +313,32 @@ def hero_slide_admin_dict(item: HeroSlide) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def map_image_admin_dict(item: MapImage) -> dict:
|
||||||
|
return {
|
||||||
|
"id": item.id,
|
||||||
|
"image": item.image or None,
|
||||||
|
"isActive": item.isActive,
|
||||||
|
"createdAt": encode_value(item.createdAt),
|
||||||
|
"updatedAt": encode_value(item.updatedAt),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def site_item_dict(module: str, item) -> dict:
|
def site_item_dict(module: str, item) -> dict:
|
||||||
if module == "heroSlides":
|
if module == "heroSlides":
|
||||||
return hero_slide_admin_dict(item)
|
return hero_slide_admin_dict(item)
|
||||||
|
if module == "map":
|
||||||
|
return map_image_admin_dict(item)
|
||||||
return destination_dict(item) if module == "destinations" else model_dict(item)
|
return destination_dict(item) if module == "destinations" else model_dict(item)
|
||||||
|
|
||||||
|
|
||||||
def module_items(db: Session, model) -> list:
|
def module_items(db: Session, config: dict) -> list:
|
||||||
return db.scalars(select(model).order_by(model.sortOrder.asc())).all()
|
model = config["model"]
|
||||||
|
stmt = select(model)
|
||||||
|
if config.get("ordered", True):
|
||||||
|
stmt = stmt.order_by(model.sortOrder.asc())
|
||||||
|
else:
|
||||||
|
stmt = stmt.order_by(model.createdAt.asc())
|
||||||
|
return db.scalars(stmt).all()
|
||||||
|
|
||||||
|
|
||||||
def normalize_site_sort_orders(items: list) -> None:
|
def normalize_site_sort_orders(items: list) -> None:
|
||||||
@@ -328,7 +357,7 @@ def site_create_payload(module: str, config: dict, body: SiteConfigPatchIn, db:
|
|||||||
for field, value in config["create_defaults"].items():
|
for field, value in config["create_defaults"].items():
|
||||||
payload.setdefault(field, value)
|
payload.setdefault(field, value)
|
||||||
payload["isActive"] = payload.get("isActive", True)
|
payload["isActive"] = payload.get("isActive", True)
|
||||||
if payload.get("sortOrder") is None:
|
if config.get("ordered", True) and payload.get("sortOrder") is None:
|
||||||
payload["sortOrder"] = next_site_sort_order(db, config["model"])
|
payload["sortOrder"] = next_site_sort_order(db, config["model"])
|
||||||
if module == "destinations":
|
if module == "destinations":
|
||||||
slug = clean_site_value(payload.get("slug"))
|
slug = clean_site_value(payload.get("slug"))
|
||||||
@@ -491,6 +520,10 @@ def admin_site_config(_user: AdminUser = Depends(require_admin), db: Session = D
|
|||||||
for item in db.scalars(select(HeroSlide).order_by(HeroSlide.sortOrder.asc())).all()
|
for item in db.scalars(select(HeroSlide).order_by(HeroSlide.sortOrder.asc())).all()
|
||||||
],
|
],
|
||||||
"destinations": [destination_dict(item) for item in destinations],
|
"destinations": [destination_dict(item) for item in destinations],
|
||||||
|
"map": [
|
||||||
|
map_image_admin_dict(item)
|
||||||
|
for item in db.scalars(select(MapImage).order_by(MapImage.createdAt.asc())).all()
|
||||||
|
],
|
||||||
"themes": [
|
"themes": [
|
||||||
model_dict(item)
|
model_dict(item)
|
||||||
for item in db.scalars(select(ThemeCard).order_by(ThemeCard.sortOrder.asc())).all()
|
for item in db.scalars(select(ThemeCard).order_by(ThemeCard.sortOrder.asc())).all()
|
||||||
@@ -511,6 +544,8 @@ def create_site_config(
|
|||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
config = site_module(module)
|
config = site_module(module)
|
||||||
|
if config.get("singleton") and module_items(db, config):
|
||||||
|
site_config_error(409, "地图图片已存在", "MAP_IMAGE_ALREADY_EXISTS", {"module": module})
|
||||||
item = config["model"](**site_create_payload(module, config, body, db))
|
item = config["model"](**site_create_payload(module, config, body, db))
|
||||||
db.add(item)
|
db.add(item)
|
||||||
db.flush()
|
db.flush()
|
||||||
@@ -529,7 +564,9 @@ def reorder_site_config(
|
|||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
config = site_module(module)
|
config = site_module(module)
|
||||||
items = module_items(db, config["model"])
|
if not config.get("ordered", True):
|
||||||
|
site_config_error(400, "模块不支持排序", "MODULE_CONFIG_REORDER_UNSUPPORTED", {"module": module})
|
||||||
|
items = module_items(db, config)
|
||||||
current_ids = [item.id for item in items]
|
current_ids = [item.id for item in items]
|
||||||
requested_ids = body.itemIds
|
requested_ids = body.itemIds
|
||||||
if len(set(requested_ids)) != len(requested_ids) or set(requested_ids) != set(current_ids):
|
if len(set(requested_ids)) != len(requested_ids) or set(requested_ids) != set(current_ids):
|
||||||
@@ -590,7 +627,8 @@ def delete_site_config(
|
|||||||
before = site_item_dict(module, item)
|
before = site_item_dict(module, item)
|
||||||
db.delete(item)
|
db.delete(item)
|
||||||
db.flush()
|
db.flush()
|
||||||
normalize_site_sort_orders(module_items(db, config["model"]))
|
if config.get("ordered", True):
|
||||||
|
normalize_site_sort_orders(module_items(db, config))
|
||||||
result = {"id": item_id}
|
result = {"id": item_id}
|
||||||
audit(db, get_actor_id(request), "delete", config["entity"], item_id, result, before)
|
audit(db, get_actor_id(request), "delete", config["entity"], item_id, result, before)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session, selectinload
|
from sqlalchemy.orm import Session, selectinload
|
||||||
from ..models import Campaign, CtaBanner, Destination, HeroSlide, Product, ThemeCard
|
from ..models import Campaign, CtaBanner, Destination, HeroSlide, MapImage, Product, ThemeCard
|
||||||
from ..serializers import destination_dict, model_dict
|
from ..serializers import destination_dict, model_dict
|
||||||
|
|
||||||
|
|
||||||
@@ -14,21 +14,25 @@ ROUTE_SECTION_LABELS = [
|
|||||||
def site_config(db: Session, active_only: bool, include_public_extras: bool = True) -> dict:
|
def site_config(db: Session, active_only: bool, include_public_extras: bool = True) -> dict:
|
||||||
hero_stmt = select(HeroSlide).order_by(HeroSlide.sortOrder.asc())
|
hero_stmt = select(HeroSlide).order_by(HeroSlide.sortOrder.asc())
|
||||||
destination_stmt = select(Destination).options(selectinload(Destination.aliases)).order_by(Destination.sortOrder.asc())
|
destination_stmt = select(Destination).options(selectinload(Destination.aliases)).order_by(Destination.sortOrder.asc())
|
||||||
|
map_stmt = select(MapImage).order_by(MapImage.createdAt.asc())
|
||||||
theme_stmt = select(ThemeCard).order_by(ThemeCard.sortOrder.asc())
|
theme_stmt = select(ThemeCard).order_by(ThemeCard.sortOrder.asc())
|
||||||
cta_stmt = select(CtaBanner).order_by(CtaBanner.sortOrder.asc())
|
cta_stmt = select(CtaBanner).order_by(CtaBanner.sortOrder.asc())
|
||||||
if active_only:
|
if active_only:
|
||||||
hero_stmt = hero_stmt.where(HeroSlide.isActive.is_(True))
|
hero_stmt = hero_stmt.where(HeroSlide.isActive.is_(True))
|
||||||
destination_stmt = destination_stmt.where(Destination.isActive.is_(True))
|
destination_stmt = destination_stmt.where(Destination.isActive.is_(True))
|
||||||
|
map_stmt = map_stmt.where(MapImage.isActive.is_(True))
|
||||||
theme_stmt = theme_stmt.where(ThemeCard.isActive.is_(True))
|
theme_stmt = theme_stmt.where(ThemeCard.isActive.is_(True))
|
||||||
cta_stmt = cta_stmt.where(CtaBanner.isActive.is_(True))
|
cta_stmt = cta_stmt.where(CtaBanner.isActive.is_(True))
|
||||||
|
|
||||||
hero_slides = db.scalars(hero_stmt).all()
|
hero_slides = db.scalars(hero_stmt).all()
|
||||||
destinations = db.scalars(destination_stmt).all()
|
destinations = db.scalars(destination_stmt).all()
|
||||||
|
map_images = db.scalars(map_stmt).all()
|
||||||
themes = db.scalars(theme_stmt).all()
|
themes = db.scalars(theme_stmt).all()
|
||||||
cta_banners = db.scalars(cta_stmt).all()
|
cta_banners = db.scalars(cta_stmt).all()
|
||||||
result = {
|
result = {
|
||||||
"heroSlides": [model_dict(item) for item in hero_slides],
|
"heroSlides": [model_dict(item) for item in hero_slides],
|
||||||
"destinations": [destination_dict(item) for item in destinations],
|
"destinations": [destination_dict(item) for item in destinations],
|
||||||
|
"map": [model_dict(item) for item in map_images],
|
||||||
"themes": [model_dict(item) for item in themes],
|
"themes": [model_dict(item) for item in themes],
|
||||||
"ctaBanners": [model_dict(item) for item in cta_banners],
|
"ctaBanners": [model_dict(item) for item in cta_banners],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from app import serializers
|
|||||||
from app.auth import hash_password, require_admin
|
from app.auth import hash_password, require_admin
|
||||||
from app.database import get_db
|
from app.database import get_db
|
||||||
from app.main import create_app
|
from app.main import create_app
|
||||||
from app.models import AdminUser, CtaBanner, Destination, DestinationAlias, HeroSlide, Lead, MediaAsset, Product, ProductImage, ThemeCard
|
from app.models import AdminUser, CtaBanner, Destination, DestinationAlias, HeroSlide, Lead, MapImage, MediaAsset, Product, ProductImage, ThemeCard
|
||||||
from app.routers import admin as admin_router
|
from app.routers import admin as admin_router
|
||||||
from app.routers.admin import normalize_detail_sections, normalize_images
|
from app.routers.admin import normalize_detail_sections, normalize_images
|
||||||
from app.schemas import (
|
from app.schemas import (
|
||||||
@@ -179,6 +179,16 @@ def make_cta_banner(**overrides):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def make_map_image(**overrides):
|
||||||
|
return MapImage(
|
||||||
|
id=overrides.get("id", "map-test"),
|
||||||
|
image=overrides.get("image", "https://cdn.example.test/map.webp"),
|
||||||
|
isActive=overrides.get("isActive", True),
|
||||||
|
createdAt=datetime(2026, 1, 1),
|
||||||
|
updatedAt=datetime(2026, 1, 2),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def make_admin_user():
|
def make_admin_user():
|
||||||
return AdminUser(
|
return AdminUser(
|
||||||
id="admin-test",
|
id="admin-test",
|
||||||
@@ -442,7 +452,7 @@ def test_site_config_create_requires_module_primary_field():
|
|||||||
|
|
||||||
def test_admin_site_config_hero_slides_use_dedicated_contract_without_targets():
|
def test_admin_site_config_hero_slides_use_dedicated_contract_without_targets():
|
||||||
hero_slide = make_hero_slide(targetType="campaign", targetValue="campaign-test")
|
hero_slide = make_hero_slide(targetType="campaign", targetValue="campaign-test")
|
||||||
fake_db = FakeDb(scalar_results=[[], [hero_slide], [], []])
|
fake_db = FakeDb(scalar_results=[[], [hero_slide], [], [], []])
|
||||||
app = authenticated_app(fake_db)
|
app = authenticated_app(fake_db)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -458,6 +468,32 @@ def test_admin_site_config_hero_slides_use_dedicated_contract_without_targets():
|
|||||||
assert "targetValue" not in hero
|
assert "targetValue" not in hero
|
||||||
|
|
||||||
|
|
||||||
|
def test_admin_site_config_includes_map_array_with_dedicated_contract():
|
||||||
|
map_image = make_map_image()
|
||||||
|
fake_db = FakeDb(scalar_results=[[], [], [map_image], [], []])
|
||||||
|
app = authenticated_app(fake_db)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = TestClient(app).get("/api/admin/site-config")
|
||||||
|
finally:
|
||||||
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
body = response.json()
|
||||||
|
assert body["map"] == [
|
||||||
|
{
|
||||||
|
"id": "map-test",
|
||||||
|
"image": "https://cdn.example.test/map.webp",
|
||||||
|
"isActive": True,
|
||||||
|
"createdAt": "2026-01-01T00:00:00",
|
||||||
|
"updatedAt": "2026-01-02T00:00:00",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
assert "sortOrder" not in body["map"][0]
|
||||||
|
assert "title" not in body["map"][0]
|
||||||
|
assert "targetType" not in body["map"][0]
|
||||||
|
|
||||||
|
|
||||||
def test_site_config_create_hero_slide_ignores_target_fields_and_returns_dedicated_contract():
|
def test_site_config_create_hero_slide_ignores_target_fields_and_returns_dedicated_contract():
|
||||||
fake_db = FakeDb(scalar_values=[0])
|
fake_db = FakeDb(scalar_values=[0])
|
||||||
app = authenticated_app(fake_db)
|
app = authenticated_app(fake_db)
|
||||||
@@ -487,6 +523,47 @@ def test_site_config_create_hero_slide_ignores_target_fields_and_returns_dedicat
|
|||||||
assert created.targetValue is None
|
assert created.targetValue is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_site_config_create_map_image_defaults_active_and_audits():
|
||||||
|
fake_db = FakeDb(scalar_results=[[]])
|
||||||
|
app = authenticated_app(fake_db)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = TestClient(app).post(
|
||||||
|
"/api/admin/site-config/map",
|
||||||
|
json={"image": " https://cdn.example.test/guizhou-map.webp ", "title": "ignored", "sortOrder": 99},
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
|
assert response.status_code == 201
|
||||||
|
body = response.json()
|
||||||
|
assert body["image"] == "https://cdn.example.test/guizhou-map.webp"
|
||||||
|
assert body["isActive"] is True
|
||||||
|
assert "title" not in body
|
||||||
|
assert "sortOrder" not in body
|
||||||
|
created = fake_db.added[0]
|
||||||
|
assert isinstance(created, MapImage)
|
||||||
|
assert created.image == "https://cdn.example.test/guizhou-map.webp"
|
||||||
|
assert created.isActive is True
|
||||||
|
assert fake_db.added[-1].entity == "map_image"
|
||||||
|
assert fake_db.committed
|
||||||
|
|
||||||
|
|
||||||
|
def test_site_config_create_map_image_rejects_duplicate_singleton():
|
||||||
|
fake_db = FakeDb(scalar_results=[[make_map_image()]])
|
||||||
|
app = authenticated_app(fake_db)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = TestClient(app).post("/api/admin/site-config/map", json={"image": "https://cdn.example.test/new.webp"})
|
||||||
|
finally:
|
||||||
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
|
assert response.status_code == 409
|
||||||
|
assert response.json()["code"] == "MAP_IMAGE_ALREADY_EXISTS"
|
||||||
|
assert not fake_db.added
|
||||||
|
assert not fake_db.committed
|
||||||
|
|
||||||
|
|
||||||
def test_site_config_patch_hero_slide_ignores_target_fields_and_returns_dedicated_contract():
|
def test_site_config_patch_hero_slide_ignores_target_fields_and_returns_dedicated_contract():
|
||||||
hero_slide = make_hero_slide(targetType="campaign", targetValue="campaign-test")
|
hero_slide = make_hero_slide(targetType="campaign", targetValue="campaign-test")
|
||||||
fake_db = FakeDb(get_result=hero_slide)
|
fake_db = FakeDb(get_result=hero_slide)
|
||||||
@@ -515,6 +592,30 @@ def test_site_config_patch_hero_slide_ignores_target_fields_and_returns_dedicate
|
|||||||
assert hero_slide.targetValue == "campaign-test"
|
assert hero_slide.targetValue == "campaign-test"
|
||||||
|
|
||||||
|
|
||||||
|
def test_site_config_patch_map_image_updates_allowed_fields_only():
|
||||||
|
map_image = make_map_image(isActive=True)
|
||||||
|
fake_db = FakeDb(get_result=map_image)
|
||||||
|
app = authenticated_app(fake_db)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = TestClient(app).patch(
|
||||||
|
"/api/admin/site-config/map/map-test",
|
||||||
|
json={"image": " https://cdn.example.test/new-map.webp ", "isActive": False, "targetType": "ignored", "sortOrder": 1},
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
body = response.json()
|
||||||
|
assert body["image"] == "https://cdn.example.test/new-map.webp"
|
||||||
|
assert body["isActive"] is False
|
||||||
|
assert "targetType" not in body
|
||||||
|
assert "sortOrder" not in body
|
||||||
|
assert not hasattr(map_image, "targetType")
|
||||||
|
assert fake_db.added[-1].action == "update"
|
||||||
|
assert fake_db.added[-1].entity == "map_image"
|
||||||
|
|
||||||
|
|
||||||
def test_site_config_patch_updates_destination_contract_fields():
|
def test_site_config_patch_updates_destination_contract_fields():
|
||||||
destination = make_destination()
|
destination = make_destination()
|
||||||
fake_db = FakeDb(get_result=destination)
|
fake_db = FakeDb(get_result=destination)
|
||||||
@@ -627,6 +728,38 @@ def test_site_config_reorder_rejects_duplicate_missing_and_unknown_ids(item_ids)
|
|||||||
assert not fake_db.committed
|
assert not fake_db.committed
|
||||||
|
|
||||||
|
|
||||||
|
def test_site_config_reorder_map_is_not_supported():
|
||||||
|
fake_db = FakeDb()
|
||||||
|
app = authenticated_app(fake_db)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = TestClient(app).patch("/api/admin/site-config/map/reorder", json={"itemIds": ["map-test"]})
|
||||||
|
finally:
|
||||||
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response.json()["code"] == "MODULE_CONFIG_REORDER_UNSUPPORTED"
|
||||||
|
assert not fake_db.committed
|
||||||
|
|
||||||
|
|
||||||
|
def test_site_config_delete_map_image_returns_json_and_audits_without_reorder():
|
||||||
|
map_image = make_map_image()
|
||||||
|
fake_db = FakeDb(get_result=map_image)
|
||||||
|
app = authenticated_app(fake_db)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = TestClient(app).delete("/api/admin/site-config/map/map-test")
|
||||||
|
finally:
|
||||||
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {"id": "map-test"}
|
||||||
|
assert fake_db.deleted == [map_image]
|
||||||
|
assert fake_db.added[-1].action == "delete"
|
||||||
|
assert fake_db.added[-1].entity == "map_image"
|
||||||
|
assert fake_db.committed
|
||||||
|
|
||||||
|
|
||||||
def test_admin_media_upload_streams_image_to_oss_records_asset_and_audits(monkeypatch):
|
def test_admin_media_upload_streams_image_to_oss_records_asset_and_audits(monkeypatch):
|
||||||
uploaded = {}
|
uploaded = {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user