Project : WASH Korea [API]제품리스트 (2)

GYUBIN ·2021년 11월 13일
0

위코드 1차 프로젝트


[API] 제품리스트


1. Q객체

쉽게 먼저 얘기하면 ProductListView 에서 Q() = Product.objects.all() 이다

q = Q() 선언 후 &= , |= 을 통해 필터링을 각각의 조건에 교집합, 합집합을 적용할 수 있다

왜 사용해야 하는지?

→ 1. 쿼리파라미터로 request.GET.get을 이용할 시 기본 url 을 통해 호출을 시도하면 for product in Product.objects.filter(sub_category_id__category_id = category)와 같은 방식으로 필터링 했을 때 (거의 없겠지만...) 필터 값이 None 값으로 넘어가기 때문에 빈 값이 반환된다

이 때, q = Q()를 먼저 선언하고 Product.objects.filter(q)로 하면 기본 url 일 때 전체 값을 받아올 수 있다

→ 2. & , | 을 통해 복잡한 필터를 걸 수 있다 !! ( 여기가 핵심 ! )


2. Header image & description


각각의 제품리스트 페이지에는 카테고리 (전체) , 서브카테고리 ( 솝 , 샤워 젤 & 젤리 , 등 .. ) 마다 각각의 헤더 이미지와 설명을 가지고 있다

👏 멘토님 리뷰 👏

이것을 현재의 results에 같이 넣어서 정보를 준다면 제품의 개수만큼 반복된다
따라서 반복문을 돌릴 제품리스트와 헤더는 나눠주는 것이 좋다

if category:
    products = Product.objects.filter(sub_category_id__category_id = category)
    products_header = Category.objects.get(id=category)

if sub_category:
    products = Product.objects.filter(sub_category_id = sub_category)
    products_header = SubCategory.objects.get(id=sub_category)

results = {
    "data" : [{
	"id"            : product.id,
	"name"          : product.name,
	"sub_name"      : product.sub_name,
	"price"         : int(product.price),
	'tags'          : [tag.name for tag in product.tags.all()],
	"product_image" : product.images.first().url    
    } for product in Product.objects.filter(q).order_by(sort[sorting])],
    "products_header" : {
	"name"        : products_header.name,
	"image"       : products_header.image,
	"description" : products_header.description
    }
}

    return JsonResponse({'results' : results}, status = 200)


이렇게 따로 뺄 수 있다 ! 물론 프론트 쪽과 미리 데이터가 어떻게 넘어가는지 얘기 잘 하자.. 꼭 꼭
미리 안하면 통신할 때 난리난다 물론 프론트가..


3. CategoryListView / CategoryView


사진을 확인해보면 러쉬페이지의 경우 내비게이션 바에서 카테고리 / 서브카테고리 전체 내용을 필요로 하고 밑에 제품리스트 부분의 목록에선 하나의 카테고리와 그에 해당하는 서브카테고리 전체 내용이 필요하다

👏 멘토님 리뷰 👏

반복문을 통해 카테고리와 서브카테고리 전체를 한 번에 넘겨주고 프론트에서 각각 뽑아서 사용할 수 도 있겠지만 그럴 경우 제품리스트 부분의 목록에선 필요없는 데이터를 더 많이 받게 된다

그래서 CategoryListViewCategoryView로 카테고리 전체의 데이터를 주는 것과 선택한 카테고리에 대해서만 데이터를 주는 것을 구분하였다

CategoryListView의 경우 전체의 이름과 해당하는 id만 필요하다

class CategoryListView(View):
    def get(self, request):
        result = [{
            "category_id"    : category.id,
            "category_name"  : category.name,
            "sub_categories" : [{
                "sub_category_id"   : sub_category.id,
                "sub_category_name" : sub_category.name
            }for sub_category in category.subcategory_set.all()]
        }for category in Category.objects.all()]

        return JsonResponse({'results' : result}, status = 200)

CategoryView의 경우에는 이름과 해당하는 id, 추가로 필터링 된 제품의 개수가 필요하다
개수는 .count() 메소드를 사용했다

이 때 !!!!! 중요한 점은 전체를 불러오는 것이 아닌 필터를 통해 일부를 가져오는 것이기 때문에 필터링 했을 때 해당하는 값이 없을 경우 에러메시지를 줘야한다

그것이 바로 'DOES NOT EXISTS'
꼭꼭꼭 적자

class CategoryView(View):
    def get(self, request, category_id):
        if not Category.objects.filter(id=category_id).exists():
            return JsonResponse({'message' : 'DOES NOT EXISTS'}, status=404)

        category = Category.objects.get(id=category_id)

        result = {
            "category_id"   : category.id,
            "category"      : category.name,
            "count"         : Product.objects.filter(sub_category_id__category_id=category).count(),
            "subcategories" : [{
                "sub_category_id" : sub_category.id,
                "name" : sub_category.name,
                "count" : sub_category.product_set.count()
            }for sub_category in category.subcategory_set.all()]
        }

        return JsonResponse({'results' : result}, status = 200)

4. 검색 기능

필터링에 대해 열심히 공부 / 코드 작성을보니 조금만 더 응용하면 검색기능으로 쓸 수 있을 것 같아서 검색 기능도 추가해보았다

여기서는 검색에 대한 필터링으로 or 가 필요하기에 Q객체를 사용해보았다

검색 페이지에서는 header로 이미지, 설명은 없고 입력된 검색어와 해당하는 제품의 개수를 포함해야 한다
검색 결과로 나오는 제품은 기존의 ProductListView와 동일하다

class SearchView(View):
    def get(self, request):
        search_word = request.GET.get('search_word')
        sorting     = request.GET.get('sort', 'id')

        q = Q()
        
        if search_word:
            q = Q(sub_category__category__name__contains = search_word)|\
                Q(sub_category__name__contains = search_word)|\
                Q(name__contains = search_word)|\
                Q(tags__name__contains = search_word)

        sort = {
                "price"  : "price",
                "-price" : "-price",
                "id"     : "id"
            }

        results = {
            "data" : [{
                "id"            : product.id,
                "name"          : product.name,
                "price"         : int(product.price),
                "sub_name"      : product.sub_name,
                'tags'          : [tag.name for tag in product.tags.all()],
                "product_image" : [image.url for image in product.images.all()]
            } for product in Product.objects.filter(q).distinct().order_by(sort[sorting])],
            "search_hedaline"   : {
                "word"  : search_word,
                "count" : Product.objects.filter(q).distinct().order_by(sort[sorting]).count()
            }
        }
        return JsonResponse({'results' : results}, status = 200)

여기서 .distinct()라는 메소드를 쓴 이유는 현재 각각의 제품마다 tags가 여러 개 달려있기 때문에 태그의 개수만큼 출력되는 문제가 생겨서다

터미널 창으로 확인해보자

http -v GET 127.0.0.1:8000/products/search search_word==펌킨 / .distinct() 없이

http -v GET 127.0.0.1:8000/products/search search_word==펌킨 / .distinct() 포함


5. 에러코드 추가

필터링 하는 곳은 'DOES NOT EXISTS' / 키 에러가 나는 곳은 'KEY_ERROR' 을 넣어주자

이왕 넣는거 if문 말고 try / except를 사용해서 넣어주면 보기 더 좋다


이상으로 ProductListView는 마무리 !!!!

0개의 댓글