from django.db.models.query_utils import Q
from django.http import JsonResponse
from django.views import View
from product.models import MainCategory, Product
class ProductFilter(View):
def get(self, request):
sub_category_products = request.GET.get('products_list')
sort = request.GET.get('sort')
search = request.GET.get('search')
sort_set = {
'best' : 'id',
'price_ascending' : 'price',
'price_descending' : '-price',
'name_ascending' : 'korea_name',
'name_descending' : '-korea_name',
'created_ascending': 'created_at',
'created_decending': '-created_at',
}
product_list = Q()
if sub_category_products:
product_list.add(Q(sub_category=sub_category_products), Q.AND)
if search:
product_list.add(Q(korea_name__icontains=search)\
|Q(sub_category__name__icontains=search), Q.AND)
products = Product.objects.filter(product_list)\
.order_by(sort_set.get(sort, 'id'))
results = [{
'product_id' : product.id,
'korea_name' : product.korea_name,
'foreign_name': product.foreign_name,
'price' : product.price,
'information' : product.information,
'is_deleted' : product.is_deleted,
'sizes' : [
{
"id" : size.id,
"width" : size.width,
"length": size.length
} for size in product.product_sizes.all()
],
'images': [
{
"product_image": product_images.product_image,
} for product_images in product.product_images.all()
]
} for product in products]
return JsonResponse({'results': results }, status = 200)
FE에서 요청한 데이터를 보내주기 위해서는
먼저 프론트에서 어떤 요구사항을 보냈는지 확인해야 합니다.
프론트에서는 query string으로 아래와 같은 URI 형태로 요청을 보냅니다.
/products?products_list=1
이 요청은 다음과 같은 형태로 받을 수 있습니다.
sub_category_products = request.GET.get('products_list')
GET[]
만을 이용해 request에서 QueryDict를 가져올 수 있지만
get()
을 추가로 사용함에 따라 매칭되는 key가 없어도 에러가 나지 않게 됩니다.
GET[]
은 request를 통해 전달되는 값을 dict 형태로 바꿔주고
그 안에서 []
안에 있는 내용과 dict 안에 key들을 매칭하기 때문에
해당하는 key가 없으면 에러가 나므로 get()
을 써줘야 합니다.
django.utils.datastructures.MultiValueDictKeyError: 'products_list'
get()
메소드는 해당하는 값이 없을 때 기본적으로 'None'을 반환하고
매칭값이 없어도 에러를 반환하지 않고 끝납니다.
# 이런 형태를
<WSGIRequest: GET '/products?products_list=1&sort=price_ascending'>
# 이렇런 식으로 바꿔줌
{
products_list: 1,
sort: "price_ascending"
}
# 정확히는 print()를 통해 이런 형태로 출력됨
<QueryDict: {
'products_list': ['1'],
'sort': ['price_ascending']
}>
장고의 ORM인 Q객체는 SQL문의 WHERE, LIKE, OR, AND와 대응됩니다.
product_list = Q()
if sub_category_products:
product_list.add(Q(sub_category=sub_category_products), Q.AND)
if search:
product_list.add(Q(korea_name__icontains=search)\
|Q(sub_category__name__icontains=search), Q.AND)
전달받은 query string과 일치하는 데이터들을 모아놓기 위해 빈 Q객체가
할당된 변수를 따로 만들어 놓습니다.
해당하는 query stirng을 받고싶은 경우 if문 등을 통해
Q(해당 테이블column(혹은 지정한 FK__column)=query담은 변수명)
을
위에서 지정한 빈 Q객체를 할당한 변수에 add
시켜주도록 세팅합니다.
그리고 query string으로 다음과 같은 값을 받으면
/products?products_lista=1&search=침대
Q객체에는 아래와 같은 형태로 값을 받아놓습니다.
(AND: (OR: ('korea_name__icontains', '침대'), ('sub_category__name__icontains', '침대')))
이들은 곧 filter()
에 전달될 것입니다.
Q객체를 모아놓은 변수를 filter의 매개변수로 넣어줍니다.
products = Product.objects.filter(product_list)\
.order_by(sort_set.get(sort, 'id'))
그럼 filter
는 데이터베이스에 접근하여 Product
테이블에서(그리고
지정한 연결된 테이블에서도) 일치하는 데이터를 수집해 옵니다.
# print(products)
<QuerySet [<Product: 노르들리>, <Product: 말름>... <Product: 횟스트베드>]>
# print(products.values().first())
{
'id': 1,
'created_at': datetime.datetime(2021, 10, 9, 17, 21, 39),
'updated_at': datetime.datetime(2021, 10, 9, 17, 21, 39),
'foreign_name': 'NORDLI',
'korea_name': '노르들리',
'price': Decimal('299000.00'),
'information': '수납침대프레임+수납상자2',
'description': '이 제품은 어쩌구~',
'is_deleted': False,
'sub_category_id': 1
}
이제 products
변수에는 수집한 데이터들이 담겨져있습니다.
search:
search 기능은 sort = request.GET.get('sort')
로 query를 받아오고
if문으로 Q(korea_name__icontains=search)
을 빈 Q객체에 넣으면 됩니다.
korea_name__icontains
는 korea_name
이라는 column에
__icontains
라는 장고 ORM을 적용한 것으로 SQL문에서 LIKE와 같은 의미입니다.
sort:
sort는 .order_by()
라는 django의 QuerySet API로 컨트롤 합니다.
sort = request.GET.get('sort')
...
sort_set = {
'best' : 'id',
'price_ascending' : 'price',
'price_descending' : '-price',
'name_ascending' : 'korea_name',
'name_descending' : '-korea_name',
'created_ascending': 'created_at',
'created_decending': '-created_at',
}
...
...filter(product_list).order_by(sort_set.get(sort, 'id'))
sorting할 종류를 dict 자료형으로 만들어놓고 그 안에서 FE로부터 받은
query stirng과 일치하는 대상을 찾아오면 .order_by()
는 그에 따라
오름차순과 내림차순으로 데이터를 정리해줍니다.
sort_set.get()
을 쓴 이유는 get()
의 두번째 인자는 첫번째 인자가
존재하지 않을 경우 반환하는 값으로 그 자리에 'id'를 넣음으로써
입력값이 없을 때 'id'를 기준으로한 오름차순, 즉 아무런 sorting값이 전달되지
않을 때를 대비하기 위함입니다.
이제 FE에서 원하는 데이터는 products
라는 모두 변수에 모여있습니다.
데이터를 그대로 보내면 안되고 FE와 합의된 형태로 데이터를 보내야 합니다.
results = [{
'product_id' : product.id,
'korea_name' : product.korea_name,
'foreign_name': product.foreign_name,
'price' : product.price,
'information' : product.information,
'is_deleted' : product.is_deleted,
'sizes' : [
{
"id" : size.id,
"width" : size.width,
"length": size.length
} for size in product.product_sizes.all()
],
'images': [
{
"product_image": product_images.product_image,
} for product_images in product.product_images.all()
]
} for product in products]
return JsonResponse({'results': results }, status = 200)
python의 list comprehension 문법을 하면 간단히 for문을 적용할 수 있으며
일반적인 for문에 비해 가독성이 좋아보입니다.
일반적인 for문을 쓰면 대략 다음과 같습니다.
results = []
for product in products:
sizes = []
images = []
for size in product.product_sizes.all():
sizes.append({
'id' : size.id,
'width': size.width,
})
for image in product.product_images.all():
images.append({
'image': image.product_image,
})
results.append({
'product_id' : product.id,
'sizes' : sizes,
'images' : images,
})
데이터를 많이 줄였는데도 코드가 길고 가독성이 좋지 못합니다.
for size in product.product_sizes.all():
에서
product.product_sizes
는 product테이블이 ProductSize테이블을
역참조하기 위해 model에서 FK를 지정할 때 지정한 related_name
을 쓴 것입니다.
related_name
을 지정하지 않았다면 productsize_set
문법으로 접근하면 됩니다.
이제 FE에서 원하는대로 요리된 데이터를 보내줄 수 있습니다!^^
함께 Django의 세계를 여행하시는 모든 분들 파이팅!!
감사합니다! 덕분에 프로젝트에 큰 도움이 되었습니다!