이번 프로젝트에서 레스토랑 정보를 평균 점수로 filtering, sorting하는 업무를 맡았었다.
메인화면에 이렇게 서브카테고리 별로 컨텐츠가 나열되어 있고 이 컨텐츠를 누르면 아래와 같은 화면으로 넘어가게하는 코드를 작성했다.
처음에는 아래와 같이 코드를 작성했다.
class RestaurantListView(View):
def get(self, request, sub_category_id):
try:
sub_categorys = SubCategory.objects.all()
sub_category_list = []
for sub_category in sub_categorys:
restaurants = sub_category.restaurants.all()
restaurant_list = []
for restaurant in restaurants:
restaurant_list.append({
"name" : restaurant.name,
"address" : restaurant.address,
"content" : restaurant.review_set.order_by('?')[0].content,
"profile_url" : restaurant.review_set.order_by('?')[0].user.profile_url,
"nickname" : restaurant.review_set.order_by('?')[0].user.nickname,
"image" : restaurant.foods.all()[0].images.all()[0].image_url,
"rating" : round(restaurant.review_set.all().aggregate(Avg('rating'))['rating__avg'], 1)
})
restaurant_list = sorted(restaurant_list, key=lambda x:x['rating'], reverse=True)
return JsonResponse({"message":"success", "result":restaurant_list}, status=200)
except KeyError:
return JsonResponse({"message":"KEY_ERROR"}, status=400)
우선 subcategory의 모든 내용이 필요했다. 그래고 이 서브카테고리 별로 음식점 정보를 가져왔어야했기 때문에 2중 for문을 돌려 리스트를 뽑아내려고 했다.
sub_categorys = SubCategory.objects.all() sub_category_list = [] for sub_category in sub_categorys: restaurants = sub_category.restaurants.all()
근데 문제가 3가지가 있었다.
1. 페이지가 넘어가는 요청을 생각하지 않았다는 점
2. 2중 for문을 돌려 불필요한 쿼리의 반복 횟수를 늘렸다는 점
3. 필요없는 list를 생성했다는 점
그래서 아래와 같이 코드를 작성했다.
class RestaurantView(View):
def get(self, request):
try:
ordering = request.GET.get("ordering", None)
sub_category = int(request.GET.get("sub_category_id", None))
if sub_category:
restaurants = Restaurant.objects.filter(sub_category_id=sub_category).annotate(average_rating=Avg("review__rating")).order_by("-"+ordering)
else:
restaurants = Restaurant.objects.annotate(average_rating=Avg("review__rating")).order_by("-"+ordering)
restaurant_list = []
for restaurant in restaurants:
restaurant_list.append({
"name" : restaurant.name,
"address" : restaurant.address,
"content" : restaurant.review_set.order_by('?')[0].content,
"profile_url" : restaurant.review_set.order_by('?')[0].user.profile_url,
"nickname" : restaurant.review_set.order_by('?')[0].user.nickname,
"image" : restaurant.foods.all()[0].images.all()[0].image_url,
"rating" : round(restaurant.average_rating, 1),
"restaurant_id" : restaurant.id
})
return JsonResponse({"message":"success", "result":restaurant_list[:5]}, status=200)
except Restaurant.DoesNotExist:
return JsonResponse({"message":"RESTAURANT_NOT_EXIST"}, status=404)
우선 request.get을 사용하여 사용자의 요청을 처리한다.
그리고 sub_category가 있으면 필러링을 거쳐 id에 맞는 정보를 제공해준다.
만약 없을 때는 그냥 요청을 평균순으로 제공해줬다.
근데 이 코드에도 많은 리펙토링이 필요하다는 것을 깨달았다. insight를 주신 병민 멘토님 감사합니다!👍
if, else 보다는 q 객체를 사용하는 것
이유 : filter, exclude, get 과 같은 조회함수와 함께 사용해서 복잡한 조건 검색을 효율적으로 할 수 있기 때문이다.
검증을 거치지않고 뇌피셜로 코드를 작성한 점
restaurant.review_set.order_by('?')[0].content,
restaurant.review_set.order_by('?')[0].user.profile_url,
restaurant.review_set.order_by('?')[0].user.nickname,
처음에 내가 이렇게 코드를 짠 이유는 레스토랑의 리뷰를 랜덤으로 가져오고 싶었다. 그리고 이 리뷰를 쓴 사람의 유저 정보를 매칭시켜 같이 가져오고 싶었다. 그것을 이어주는 것이 외래키이라고 생각했다.
하지만 조금만 생각해보면 이 생각이 완전 틀렸다는 것을 깨달을 수 있다. 댓글, 유저 사진, 닉네임이 다 랜덤으로 돌지않을까라는 재경님의 리뷰에 내가 완전 잘못 생각하고 있었구나라는 것을 깨달았다. 😅
몇 줄 안되는 코드이지만 이 안에서도 많은 리펙토링이 필요하다는 것을 깨달았다. 더욱 효율적이고 직관적인 코드를 짜기 위해 고민 또 고민을 해야겠다. 👍