diff --git a/apps/aquatics/urls.py b/apps/aquatics/urls.py index f79760a..a22eff5 100644 --- a/apps/aquatics/urls.py +++ b/apps/aquatics/urls.py @@ -6,6 +6,8 @@ FishtankBackgroundListView, ApplyFishtankBackgroundView, FishtankExportView, + FishtankSelectableFishView, + FishtankExportSelectionView, ) from .views_aquarium import * @@ -15,13 +17,17 @@ path("fishtank/backgrounds/", FishtankBackgroundListView.as_view()), path("fishtank//apply-background/", ApplyFishtankBackgroundView.as_view()), path("fishtank//export/", FishtankExportView.as_view()), - path("svg/", AquariumSVGView.as_view(), name="aquarium-svg"), + path("fishtank//selectable-fish/", FishtankSelectableFishView.as_view()), + path("fishtank//export-selection/", FishtankExportSelectionView.as_view()), + path("aquarium/", AquariumDetailView.as_view()), path("aquarium/my-fishes/", MyUnlockedFishListView.as_view()), - path("aquarium/add-fish/", AquariumAddFishView.as_view()), - path("aquarium/remove-fish//", AquariumRemoveFishView.as_view()), + #path("aquarium/add-fish/", AquariumAddFishView.as_view()), + #path("aquarium/remove-fish//", AquariumRemoveFishView.as_view()), path("aquarium/backgrounds/", AquariumBackgroundListView.as_view()), path("aquarium/apply-background/", AquariumApplyBackgroundView.as_view()), path("aquarium/export/", AquariumExportView.as_view()), path("aquarium/svg/", AquariumSVGView.as_view()), + path("aquarium/selectable-fish/", AquariumSelectableFishView.as_view()), + path("aquarium/export-selection/", AquariumExportSelectionView.as_view()), ] diff --git a/apps/aquatics/views_aquarium.py b/apps/aquatics/views_aquarium.py index 7f25d85..879bb95 100644 --- a/apps/aquatics/views_aquarium.py +++ b/apps/aquatics/views_aquarium.py @@ -192,3 +192,65 @@ class AquariumSVGView(APIView): def get(self, request): svg = render_aquarium_svg(request.user) return Response(svg, content_type="image/svg+xml") + + +# 9) 아쿠아리움 선택 가능한 물고기 목록 +class AquariumSelectableFishView(APIView): + permission_classes = [IsAuthenticated] + + @swagger_auto_schema( + operation_summary="선택 가능한 물고기 목록 조회", + operation_description="유저가 보유한 모든 ContributionFish를 selectable 형태로 반환합니다.", + responses={200: "Selectable fish list"} + ) + def get(self, request): + user = request.user + aquarium = user.aquarium + + fishes = ContributionFish.objects.filter(contributor__user=user) \ + .select_related("fish_species", "contributor__repository") + + data = [] + for f in fishes: + data.append({ + "id": f.id, + "species": f.fish_species.name, + "repo_name": f.contributor.repository.name, + "selected": (f.aquarium_id == aquarium.id) + }) + + return Response({"fishes": data}, status=200) +# 10) Export: 프론트 선택 상태를 실제 DB에 반영 +class AquariumExportSelectionView(APIView): + permission_classes = [IsAuthenticated] + + @swagger_auto_schema( + operation_summary="아쿠아리움 Export - 선택된 물고기 저장", + operation_description="프론트에서 최종 선택된 물고기 ID 목록을 받아 DB에 반영합니다.", + request_body=openapi.Schema( + type=openapi.TYPE_OBJECT, + properties={ + "fish_ids": openapi.Schema( + type=openapi.TYPE_ARRAY, + items=openapi.Items(type=openapi.TYPE_INTEGER) + ) + }, + required=["fish_ids"] + ), + responses={200: "Saved"} + ) + def post(self, request): + user = request.user + aquarium = user.aquarium + selected_ids = request.data.get("fish_ids", []) + + # 1) 유저가 소유한 모든 fish 가져오기 + all_my_fish = ContributionFish.objects.filter(contributor__user=user) + + # 2) 선택되지 않은 물고기 → aquarium에서 제거 + all_my_fish.exclude(id__in=selected_ids).update(aquarium=None) + + # 3) 선택된 물고기 → aquarium에 추가 + all_my_fish.filter(id__in=selected_ids).update(aquarium=aquarium) + + return Response({"detail": "Aquarium updated"}, status=200) diff --git a/apps/aquatics/views_fishtank.py b/apps/aquatics/views_fishtank.py index 394a97f..9394606 100644 --- a/apps/aquatics/views_fishtank.py +++ b/apps/aquatics/views_fishtank.py @@ -8,7 +8,7 @@ #fishtank views.py from apps.repositories.models import Repository -from apps.aquatics.models import Fishtank, FishtankSetting, OwnBackground +from apps.aquatics.models import Fishtank, FishtankSetting, OwnBackground,ContributionFish from apps.aquatics.serializers_fishtank import ( FishtankDetailSerializer, FishtankBackgroundSerializer, @@ -151,4 +151,71 @@ class FishtankExportView(APIView): def post(self, request, repo_id): # 저장 필드가 모델에 아직 없기 때문에, 저장 로직은 후에 추가 가능 - return Response({"detail": "Saved"}, status=200) \ No newline at end of file + return Response({"detail": "Saved"}, status=200) + +# 9) 피쉬탱크 선택 가능한 물고기 목록 +class FishtankSelectableFishView(APIView): + permission_classes = [IsAuthenticated] + + @swagger_auto_schema( + operation_summary="피쉬탱크 선택 가능한 물고기 목록 조회", + operation_description="레포지토리 내 ContributionFish 전체를 조회하고, is_visible 여부를 selected로 반환합니다.", + ) + def get(self, request, repo_id): + try: + fishtank = Fishtank.objects.get(repository_id=repo_id) + except Fishtank.DoesNotExist: + return Response({"detail": "Fishtank not found"}, status=404) + + fishes = ContributionFish.objects.filter( + contributor__repository_id=repo_id + ).select_related("fish_species", "contributor__user") + + data = [] + for f in fishes: + data.append({ + "id": f.id, + "username": f.contributor.user.username, + "species": f.fish_species.name, + "commit_count": f.contributor.commit_count, + "selected": f.is_visible, + }) + + return Response({"fishes": data}, status=200) +# 10) 피쉬탱크 Export → 선택 상태 실제 저장 +class FishtankExportSelectionView(APIView): + permission_classes = [IsAuthenticated] + + @swagger_auto_schema( + operation_summary="피쉬탱크 Export - 선택된 물고기 적용", + request_body=openapi.Schema( + type=openapi.TYPE_OBJECT, + properties={ + "fish_ids": openapi.Schema( + type=openapi.TYPE_ARRAY, + items=openapi.Items(type=openapi.TYPE_INTEGER) + ) + }, + required=["fish_ids"] + ), + responses={200: "Saved"} + ) + def post(self, request, repo_id): + selected_ids = request.data.get("fish_ids", []) + + try: + Fishtank.objects.get(repository_id=repo_id) + except Fishtank.DoesNotExist: + return Response({"detail": "Fishtank not found"}, status=404) + + fishes = ContributionFish.objects.filter( + contributor__repository_id=repo_id + ) + + # 1) 선택되지 않은 물고기 → 숨김 + fishes.exclude(id__in=selected_ids).update(is_visible=False) + + # 2) 선택된 물고기 → 표시 + fishes.filter(id__in=selected_ids).update(is_visible=True) + + return Response({"detail": "Fishtank updated"}, status=200)