fix: 完善商品—模特库-视频项目-自由创作等删除先进垃圾桶及可恢复等流程

This commit is contained in:
hh
2026-07-10 15:06:07 +08:00
parent 3e52942547
commit 564215dc53
28 changed files with 1086 additions and 190 deletions
+63 -7
View File
@@ -9,7 +9,7 @@ from rest_framework.test import APIClient
from apps.accounts.models import Team, TeamMember, User
from . import assets_client
from .models import Asset, FreeAsset, FreeAssetGroup, Model
from .models import Asset, AssetFile, FreeAsset, FreeAssetGroup, Model
def _mk_team(username, team_name):
@@ -56,6 +56,48 @@ class ModelLibraryApiTests(TestCase):
ids = {m["id"] for m in self.client.get("/api/models/").json()["results"]}
self.assertNotIn(str(self.mine.id), ids)
def test_trash_restore_and_purge_model_keeps_record_and_asset(self):
portrait = Asset.objects.create(team=self.teamA, name="模特形象", asset_type="image", source="upload")
portrait_file = AssetFile.objects.create(
asset=portrait,
object_key="models/portrait.png",
bucket="test",
content_type="image/png",
is_primary=True,
)
self.mine.portrait_asset = portrait
self.mine.save(update_fields=["portrait_asset"])
self.client.delete(f"/api/models/{self.mine.id}/")
trash_ids = {m["id"] for m in self.client.get("/api/models/trash/").json()["results"]}
self.assertIn(str(self.mine.id), trash_ids)
portrait.refresh_from_db()
self.assertFalse(portrait.is_deleted)
self.assertIsNone(portrait.purged_at)
restore = self.client.post(f"/api/models/{self.mine.id}/restore/")
self.assertEqual(restore.status_code, 200)
self.mine.refresh_from_db()
self.assertFalse(self.mine.is_deleted)
self.assertIsNone(self.mine.purged_at)
ids = {m["id"] for m in self.client.get("/api/models/?tab=mine").json()["results"]}
self.assertIn(str(self.mine.id), ids)
self.client.delete(f"/api/models/{self.mine.id}/")
purge = self.client.delete(f"/api/models/{self.mine.id}/purge/")
self.assertEqual(purge.status_code, 204)
self.mine.refresh_from_db()
self.assertTrue(self.mine.is_deleted)
self.assertIsNotNone(self.mine.purged_at)
self.assertTrue(Model.objects.filter(id=self.mine.id).exists())
self.assertTrue(Asset.objects.filter(id=portrait.id).exists())
portrait.refresh_from_db()
self.assertFalse(portrait.is_deleted)
self.assertIsNone(portrait.purged_at)
self.assertTrue(AssetFile.objects.filter(id=portrait_file.id).exists())
trash_ids = {m["id"] for m in self.client.get("/api/models/trash/").json()["results"]}
self.assertNotIn(str(self.mine.id), trash_ids)
def test_official_cannot_be_deleted(self):
res = self.client.delete(f"/api/models/{self.official.id}/")
self.assertEqual(res.status_code, 403)
@@ -108,7 +150,7 @@ class AssetSoftDeleteTests(TestCase):
class AssetTrashApiTests(TestCase):
"""R108 资产垃圾桶:trash 只列本团队软删资产;restore 后资产库重新可见;purge 硬删且被引用时 409。"""
"""R108 资产垃圾桶:trash 只列本团队软删资产;restore 后资产库重新可见;purge 二级软删。"""
def setUp(self):
self.user, self.team = _mk_team("utr", "TeamTR")
@@ -117,6 +159,13 @@ class AssetTrashApiTests(TestCase):
self.client.force_authenticate(self.user)
self.live = Asset.objects.create(team=self.team, name="活图", asset_type="image", source="ai_generated", category=Asset.Category.MODEL_TRYON)
self.dead = Asset.objects.create(team=self.team, name="删掉的图", asset_type="image", source="ai_generated", category=Asset.Category.MODEL_TRYON, is_deleted=True)
self.dead_file = AssetFile.objects.create(
asset=self.dead,
object_key="assets/deleted.png",
bucket="test",
content_type="image/png",
is_primary=True,
)
# 别队软删资产:不得出现在本队垃圾桶(team 隔离)
self.other_dead = Asset.objects.create(team=self.other_team, name="别队删图", asset_type="image", source="ai_generated", is_deleted=True)
@@ -134,19 +183,26 @@ class AssetTrashApiTests(TestCase):
# 恢复后不再在垃圾桶
self.assertEqual(self.client.get("/api/assets/trash/").json()["count"], 0)
def test_purge_hard_deletes(self):
def test_purge_soft_hides_from_trash(self):
res = self.client.delete(f"/api/assets/{self.dead.id}/purge/")
self.assertEqual(res.status_code, 204)
self.assertFalse(Asset.objects.filter(id=self.dead.id).exists())
self.dead.refresh_from_db()
self.assertIsNotNone(self.dead.purged_at)
self.assertTrue(Asset.objects.filter(id=self.dead.id).exists())
self.assertTrue(AssetFile.objects.filter(id=self.dead_file.id).exists())
self.assertEqual(self.client.get("/api/assets/trash/").json()["count"], 0)
def test_purge_referenced_asset_returns_409(self):
def test_purge_referenced_asset_keeps_record(self):
from apps.products.models import Product, ProductImage
product = Product.objects.create(team=self.team, created_by=self.user, title="P")
ProductImage.objects.create(product=product, asset=self.dead) # PROTECT 引用
res = self.client.delete(f"/api/assets/{self.dead.id}/purge/")
self.assertEqual(res.status_code, 409)
self.assertTrue(Asset.objects.filter(id=self.dead.id, is_deleted=True).exists()) # 留在垃圾桶可恢复
self.assertEqual(res.status_code, 204)
self.dead.refresh_from_db()
self.assertTrue(self.dead.is_deleted)
self.assertIsNotNone(self.dead.purged_at)
self.assertTrue(Asset.objects.filter(id=self.dead.id).exists())
def test_restore_rejects_live_asset(self):
# 未删资产不在 trash 态 queryset → 404(与商品垃圾桶口径一致)