diff --git a/src/config/settings.py b/src/config/settings.py index 11732d2..2da0f7f 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -56,7 +56,7 @@ REST_FRAMEWORK = { "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", "DEFAULT_PAGINATION_CLASS": "core.paginations.PageNumberPagination", - "EXCEPTION_HANDLER": "rest_framework.views.exception_handler", + "EXCEPTION_HANDLER": "core.middlewares.exception_handler.custom_exception_handler", "PAGE_SIZE_QUERY_PARAM": "page_size", "PAGE_SIZE": 10, "MAX_PAGE_SIZE": 100, diff --git a/src/faq/views.py b/src/faq/views.py index dc52f05..97032d2 100644 --- a/src/faq/views.py +++ b/src/faq/views.py @@ -1,7 +1,6 @@ from drf_spectacular.utils import extend_schema_view from rest_framework.viewsets import GenericViewSet -from core.errors import NotExistException from core.responses.base import BaseResponse from .models import FAQ @@ -18,8 +17,6 @@ def get_queryset(self): def list(self, request, *args, **kwargs) -> BaseResponse: queryset = self.get_queryset() - if not queryset.exists(): - raise NotExistException() page = self.paginate_queryset(queryset) # ✅ 페이지네이션 적용 serializer = self.get_serializer(page or queryset, many=True) return self.get_paginated_response(serializer.data) diff --git a/src/merchandise/admin.py b/src/merchandise/admin.py index 0c02bde..340496f 100644 --- a/src/merchandise/admin.py +++ b/src/merchandise/admin.py @@ -7,4 +7,4 @@ @admin.register(Merchandise) class MerchandiseAdmin(admin.ModelAdmin): - list_display = ("product_name", "image") + list_display = ("name", "image") diff --git a/src/merchandise/migrations/0001_initial.py b/src/merchandise/migrations/0001_initial.py index 6d760ab..64a5863 100644 --- a/src/merchandise/migrations/0001_initial.py +++ b/src/merchandise/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.5 on 2025-02-24 12:32 +# Generated by Django 5.1.5 on 2025-02-25 18:42 from django.db import migrations, models @@ -10,19 +10,17 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name="Md", + name="Merchandise", fields=[ ( "id", models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" ), ), - ("product_name", models.CharField(max_length=255)), - ("image", models.ImageField(upload_to="images/")), + ("name", models.CharField(max_length=255, verbose_name="상품 이름")), + ("description", models.TextField(blank=True, null=True, verbose_name="상품 설명")), + ("image", models.ImageField(upload_to="images/", verbose_name="썸네일 이미지")), ], ), ] diff --git a/src/merchandise/migrations/0002_merchandise_delete_md.py b/src/merchandise/migrations/0002_merchandise_delete_md.py deleted file mode 100644 index 06245ee..0000000 --- a/src/merchandise/migrations/0002_merchandise_delete_md.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 5.1.5 on 2025-02-25 14:22 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("merchandise", "0001_initial"), - ] - - operations = [ - migrations.CreateModel( - name="Merchandise", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("product_name", models.CharField(max_length=255)), - ("product_info", models.TextField(blank=True, null=True)), - ("image", models.ImageField(upload_to="images/")), - ], - ), - migrations.DeleteModel( - name="Md", - ), - ] diff --git a/src/merchandise/models.py b/src/merchandise/models.py index 6dcd55b..2606c61 100644 --- a/src/merchandise/models.py +++ b/src/merchandise/models.py @@ -8,9 +8,9 @@ class Merchandise(models.Model): ### MD 필드 정의 """ - product_name = models.CharField(max_length=255) - product_info = models.TextField(null=True, blank=True) - image = models.ImageField(upload_to="images/") + name = models.CharField("상품 이름", max_length=255) + description = models.TextField("상품 설명", null=True, blank=True) + image = models.ImageField("썸네일 이미지", upload_to="images/") def __str__(self): - return self.product_name + return self.name diff --git a/src/merchandise/swagger.py b/src/merchandise/swagger.py new file mode 100644 index 0000000..a80ad47 --- /dev/null +++ b/src/merchandise/swagger.py @@ -0,0 +1,66 @@ +from drf_spectacular.utils import OpenApiExample, OpenApiResponse + +from core.responses.serializer import ( + ErrorResponseSerializer, + ListSuccessResponseSerializer, +) +from core.swagger import SwaggerSchema + + +class MerchandiseAPIDocs(SwaggerSchema): + @classmethod + def list(cls): + responses = { + "성공": OpenApiResponse( + response=ListSuccessResponseSerializer, + description="다중 응답 성공", + examples=[ + OpenApiExample( + name="상품 목록 조회 (페이지네이션 있음)", + value={ + "status": "SUCCESS", + "data": [ + { + "id": 1, + "product_name": "상품1", + "product_info": "상품 설명입니다.", + "image": "http://localhost:8000/media/merchandise/1.jpg", + }, + ], + "pagination": { + "count": 20, + "next": "http://localhost:8000/api/v1/merchandise/?page=2", + "previous": "http://localhost:8000/api/v1/merchandise/?page=1", + }, + }, + ), + OpenApiExample( + name="상품 목록 조회 (데이터 있음)", + value={ + "status": "SUCCESS", + "data": [ + { + "id": 1, + "product_name": "상품1", + "product_info": "상품 설명입니다.", + "image": "http://localhost:8000/media/merchandise/1.jpg", + }, + ], + "pagination": {"count": 1, "next": None, "previous": None}, + }, + ), + OpenApiExample( + name="Merchandise 목록 조회 (데이터 없음)", + value={ + "status": "SUCCESS", + "data": [], + "pagination": {"count": 0, "next": None, "previous": None}, + }, + ), + ], + ), + "에러": OpenApiResponse(response=ErrorResponseSerializer, description="응답 에러"), + } + return cls.generate_schema( + operation_id="merchandise_list", description="모든 상품 목록 조회", responses=responses + ) diff --git a/src/merchandise/views.py b/src/merchandise/views.py index ac8ff00..302e7da 100644 --- a/src/merchandise/views.py +++ b/src/merchandise/views.py @@ -1,44 +1,20 @@ -from rest_framework import status -from rest_framework.response import Response -from rest_framework.viewsets import ReadOnlyModelViewSet +from drf_spectacular.utils import extend_schema_view +from rest_framework.viewsets import GenericViewSet + +from core.responses.base import BaseResponse from .models import Merchandise from .serializers import MerchandiseSerializer +from .swagger import MerchandiseAPIDocs -class MerchandiseViewSet(ReadOnlyModelViewSet): - """ - MD 페이지는 상세보기 페이지가 존재하지 않으므로 retrieve는 생략되었습니다. - """ - +@extend_schema_view(list=MerchandiseAPIDocs.list()) +class MerchandiseViewSet(GenericViewSet): queryset = Merchandise.objects.all() serializer_class = MerchandiseSerializer - def list(self, request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) - if not queryset.exists(): - return Response( - { - "status_code": "200", - "body": { - "code": "success", - "message": "MD 데이터가 존재하지 않습니다", - "data": [], - "pagination": {}, - }, - }, - status=status.HTTP_200_OK, - ) - serializer = self.get_serializer(queryset, many=True) - return Response( - { - "status_code": "200", - "body": { - "code": "success", - "message": "성공", - "data": serializer.data, - "pagination": {}, - }, - }, - status=status.HTTP_200_OK, - ) + def list(self, request, *args, **kwargs) -> BaseResponse: + queryset = self.get_queryset() + page = self.paginate_queryset(queryset) + serializer = self.get_serializer(page or queryset, many=True) + return self.get_paginated_response(serializer.data)