본문 바로가기
Django

[Django] DRF - 1차 프로젝트 리팩토링 (1) Product detail & list GET

by 혀넝 2022. 6. 7.

Udemy로 DRF 강의를 다 듣고서, 1차 프로젝트 코드를 DRF 코드로 변경하는 작업을 진행하고 있다. 

1. Product Detail GET

# serializer

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = '__all__'
# view

class ProductDetailGV(generics.RetrieveAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

product detail GET을 구현하기 위해서 generics.RetrieveAPIView를 사용했다. GET과 관련해서 ListAPIView도 있지만, 하나의 element를 리턴 받기 위해서는 RetrieveAPIView를 사용하고, 전체 element들을 리턴 받기 위해서는 ListAPIView를 사용해야 한다.

# url

...
path('/<int:pk>' , ProductDetailGV.as_view(), name='product-detail'),
...

처음에 url은 고치지 않고 원래 써져 있는 그대로인 '/<int:product_id>'로 사용했었는데, 그랬더니 아래와 같은 에러가 발생했다.

AssertionError at /products/1
Expected view ProductDetailGV to be called with a URL keyword argument named "pk". Fix your URL conf, or set the `.lookup_field` attribute on the view correctly.

그래서 product_id 부분을 pk로 바꾸니 정상적으로 작동됐다. 
왜 이런 에러가 나는지 알기 위해서 view에서 get method안에 있는 kwargs에 들어오는 값이 무엇인지 찍어보니, pk가 담겨 있었다. RetrieveAPIView는 GenericAPIView와 RetrieveModelMixin을 상속받고 있는데, 특히 RetrieveModelMixin은 넘겨받은 pk를 가지고 model instance를 반환한다. 따라서 여기서의 pk는 설정해둔 모델인 Product의 pk를 의미한다고 이해했다.

postman을 통해서 값을 받아보니 아래와 같이 나왔다.

{
    "id": 1,
    "created_at": "2022-04-06T11:47:03.504007",
    "updated_at": "2022-04-06T11:47:03.504035",
    "name": "사과 클렌저",
    "price": "32000.00",
    "size": "60ml",
    "description": "눈 주위의 민감한 피부를 달래고 진정시켜주는 마트리카리아가 포함된 부드러운 오일 제형의 아이 메이크업 리무브",
    "howtouse": {
        "scent": [
            "허브향",
            "플로랄",
            "너티"
        ],
        "texture": [
            "수용성",
            "오일"
        ],
        "사용량": "한 티스푼과 물 3~5방울",
        "사용법": "아침과 저녁, 손바닥에 물과 함께 섞어 밀키한 에멀젼으로 만들어 젖은 얼굴과 목에 마사지 한 후 물로 씻어냅니다."
    },
    "badge": "종환 MD 추천",
    "category": 1,
    "feelings": [
        6,
        3,
        3
    ],
    "skin_types": [
        10
    ]
}

현재 category, feelings, skin_types는 foreignkey로 물려있거나 many to many 관계에 있고, 그런 field는 값이 id 값으로 나오는 것을 확인할 수 있었다.

해당 값들을 id가 아닌 name으로 바꾸기 위해서 serializer에서 처리해주면 된다.

# serializer

class ProductFeelingsSerializer(serializers.ModelSerializer):
    feeling = serializers.CharField(source='name')

    class Meta:
        model = ProductFeelings
        fields = ["feeling"]

class ProductSkintypeSerializer(serializers.ModelSerializer):
    skin_type = serializers.CharField(source='name')

    class Meta:
        model = ProductSkintype
        fields = ['skin_type']

class ProductSerializer(serializers.ModelSerializer):
    category   = serializers.CharField(source='category.category_name')
    feelings   = ProductFeelingsSerializer(read_only=True, many=True)
    skin_types = ProductSkintypeSerializer(read_only=True, many=True)

    class Meta:
        model = Product
        fields = '__all__'

여기서 중요한 점은, ProductSerializer에서 설정하는 variable name과 model에 있는 field 명이 같아야 한다는 것이다.

바뀐 결과는 아래와 같다.

{
    "id": 1,
    "category": "클렌저",
    "feelings": [
        {
            "feeling": "유연"
        },
        {
            "feeling": "부드러운"
        },
        {
            "feeling": "부드러운"
        }
    ],
    "skin_types": [
        {
            "skin_type": "윤기없는 피부"
        }
    ],
    "created_at": "2022-04-06T11:47:03.504007",
    "updated_at": "2022-04-06T11:47:03.504035",
    "name": "사과 클렌저",
    "price": "32000.00",
    "size": "60ml",
    "description": "눈 주위의 민감한 피부를 달래고 진정시켜주는 마트리카리아가 포함된 부드러운 오일 제형의 아이 메이크업 리무브",
    "howtouse": {
        "scent": [
            "허브향",
            "플로랄",
            "너티"
        ],
        "texture": [
            "수용성",
            "오일"
        ],
        "사용량": "한 티스푼과 물 3~5방울",
        "사용법": "아침과 저녁, 손바닥에 물과 함께 섞어 밀키한 에멀젼으로 만들어 젖은 얼굴과 목에 마사지 한 후 물로 씻어냅니다."
    },
    "badge": "종환 MD 추천"
}

 

의문점

serializer에서 왜 source='feeling.name'이 안될까?
feeling과 skin_type은 many=True 설정을 해 둬서 여러 값이 나오는데, key값을 하나로 줄일 수는 없는 걸까?

 

2. Product List GET

위에서 얻어진 결과를 이전 1차 프로젝트 결과와 비교해보니 ingredient 정보가 반환되지 않고 있다. 펴보니 many to many field로 product model 안에 있어야 하는 게 없어서 serializer로 설정을 해도 나오지 않았다. 따라서 model을 바꾸고 serializer에 추가했다.

# model

class Product(TimeStamp):
    name        = models.CharField(max_length=45)
    price       = models.DecimalField(decimal_places=2, max_digits=10)
    size        = models.CharField(max_length=45)
    description = models.TextField(max_length=1000)
    category    = models.ForeignKey('Category', on_delete=models.CASCADE)
    howtouse    = models.JSONField()
    feeling     = models.ManyToManyField('Feeling', through="ProductFeelings")
    badge       = models.CharField(max_length=15, null=True)
    skin_type   = models.ManyToManyField('SkinType', through='ProductSkintype')
    ingredient  = models.ManyToManyField('Ingredient', through='ProductIngredient')
# serializer

class ProductIngredientSerializer(serializers.ModelSerializer):
    ingredient = serializers.CharField(source='name')

    class Meta:
        model = ProductIngredient
        fields = ['ingredient']


class ProductSerializer(serializers.ModelSerializer):
    category   = serializers.CharField(source='category.category_name')
    feelings   = ProductFeelingsSerializer(read_only=True, many=True)
    skin_types = ProductSkintypeSerializer(read_only=True, many=True)
    ingredient = ProductIngredientSerializer(read_only=True, many=True)
    # ingredient = serializers.CharField(source='productingredient.ingredient.name')

    class Meta:
        model = Product
        fields = '__all__'

 

이전 코드의 view를 보면, Q 객체를 사용해서 filtering을 적용시키고 있다. 리팩토링을 하면서는 DRF에서 제공하는 filters를 import 하고, SearchFilter를 활용하여 구현했다.

# view

from rest_framework import filters

class ProductListGV(generics.ListAPIView):
    queryset         = Product.objects.all()
    serializer_class = ProductSerializer
    filter_backends  = [filters.SearchFilter]
    search_fields    = ['^name', '=category__id', '^skin_type__name', 'feeling__name', '^ingredient__name']

 

느낀 점

강의를 봤을 땐 이해했다고 생각한 것들을 실제로 적용시켜 보니까 헷갈리는 부분도 있었고, 기억이 나지 않는 부분도 꽤나 있었다. 의문점에 써 놓은 내용은 아직 해결되지 않은 부분이기 때문에 DRF에 대해서 더 공부하면서 해결해야 할 과제이다.