본문 바로가기

인턴 프로젝트

미니 요기요 프로젝트(2) - 레스토랑, 레스토랑 디테일

기획 의도, 화면, 코드, 테스트 코드, 어려웠던 점, 해결방법, 느낀점 등을 설명한다.

 

1. 레스토랑

  • 기획 의도
    • 특정 카테고리를 클릭하면 그에 맞는 레스토랑 리스트가 출력되는 것이 목표
  • 화면

치킨 카테고리를 클릭하면 나오는 치킨 관련 레스토랑 리스트

  • 코드

카테고리를 클릭하면 'category/<int:category_id>/restaurant/'가 호출되고 레스토랑 리스트 화면이 나온다.

저 경로로 요청이 들어오면 템플릿 렌더링을 할 때 category_id를 html 파일에 넘겨준다. 그 html 파일에선 ajax로 api 호출을 하는데 "/api/category/" + {{ category_id }} +"/restaurant/" 이렇게 호출한다. 그러면 api 디렉토리의 urls.py에선 패턴을 찾고 일치하는 게 있으면 views.RestaurantListAPIView.as_view(), 이렇게 클래스 기반 뷰를 함수로 호출한다.

class RestaurantListAPIView(View):
    def get(self, request, *args, **kwargs):
        category_id = self.kwargs['category_id']
        if category_id == CategoryNum.ALL_ID:
            restaurant_list = Restaurant.objects.all().values(
                'pk', 'title', 'min_order_price', 'estimated_delivery_time', 'img',
            )
        else:
            restaurant_list = Restaurant.objects.filter(category=category_id).values(
                'pk', 'title', 'min_order_price', 'estimated_delivery_time', 'img',
            )
        if not restaurant_list:
            return HttpResponse(
                json.dumps({"message": "레스토랑 리스트가 존재하지 않습니다.", }),
                status=HTTPStatus.NOT_FOUND,
                content_type='application/json',
            )
        restaurant_list = list(restaurant_list, )
        json_data = {
            'restaurant_list': restaurant_list,
            'category_id': category_id,
        }
        json_data = json.dumps(json_data, cls=DateTimeEncoder)

        return HttpResponse(
            json_data,
            content_type='application/json',
        )

category_id가 key가 되고 값이 value인 dict가 넘어온다. 그래서 값을 받을 때 kwargs(keyword arguments)에서 key(category_id)를 사용한다.

카테고리 ID에 맞는 레스토랑들을 찾아서 restaurant_list에 넣는다.

  • 테스트 코드

레스토랑 단순 보여주는 부분이라 안짠듯

  • 어려웠던 점

배달예상시간의 타입은 datetimefield인데 출력해야할 형식은 H:M이었다.

 

  • 해결 방법

json.dumps()에서 DateTimeEncoder를 사용했다.

class DateTimeEncoder(json.JSONEncoder):
    def default(self, obj):
        return obj.strftime('%H:%M')
  • 느낀점

인코더 클래스를 사용해서 문자열 형식의 시간을 반환해주는 게 멋졌다.

 

2. 레스토랑 디테일

  • 기획 의도

특정 레스토랑을 클릭하면 레스토랑 디테일 페이지가 나오고 정보가 나온다.

  • 화면

레스토랑 디테일

  • 코드

레스토랑을 누르면 'category/<int:category_id>/restaurant/<int:restaurant_id>/'이게 호출되고 레스토랑 기본키 값으로 DB에서 레스토랑을 찾게 된다.

class RestaurantDetailAPIView(View):
    def get(self, request, *args, **kwargs):
        user = request.user
        restaurant_id = self.kwargs['restaurant_id']
        try:
            restaurant_detail = Restaurant.objects.filter(id=restaurant_id).values(
                'name', 'img', 'min_order_price', 'order_way', 'owner', 'title', 'operation_start_hour',
                'operation_end_hour',
                'tel', 'origin', 'delivery_charge', 'info', 'estimated_delivery_time',
            )

            if not restaurant_detail:
                return HttpResponse(
                    json.dumps({"message": "해당 레스토랑이 없습니다.", }),
                    status=HTTPStatus.NOT_FOUND,
                    content_type='application/json',
                )

            restaurant_detail= json.dumps(list(restaurant_detail, ), cls=DateTimeEncoder)

            if request.user.is_authenticated:
                if user.subscribed_restaurants.filter(pk=restaurant_id):
                    subscribe_flag = False
                    json_data = {
                        'restaurant_detail': restaurant_detail,
                        'user_addr': request.user.address,
                        'subscribe_flag': subscribe_flag,
                    }
                else:
                    subscribe_flag = True
                    json_data = {
                        'restaurant_detail': restaurant_detail,
                        'user_addr': request.user.address,
                        'subscribe_flag': subscribe_flag,
                    }
            else:
                json_data = {
                    'restaurant_detail': restaurant_detail,
                }

            return HttpResponse(
                json.dumps(json_data),
                content_type='application/json',
            )
        except Restaurant.DoesNotExist:
            json_data = {
                "message": "레스토랑이 없습니다.",
            }
            return JsonResponse(
                json_data,
                status=HTTPStatus.BAD_REQUEST,
            )
  • 테스트 코드
class RestaurantTestClass(TestCase):
    def setUp(self):
        self.category=Category.objects.create(name='치킨',img='restaurant/chicken.jpg')
        self.restaurant=Restaurant.objects.create(
            name='굽내치킨',title='굽내치킨-서초점', estimated_delivery_time='2019-01-01 00:20',
            min_order_price=10000, delivery_charge=1000, operation_end_hour='2019-01-01 10:00',
            operation_start_hour='2019-01-01 10:00', )

    def test_restaurant_detail_page_should_be_opened_on_request(self):
        '''
        category id를 보낼 시 레스토랑 상세 페이지 열기
        '''
        # Given
        url = reverse("restaurant:restaurant_detail", kwargs={
            'category_id': self.category.id, 'restaurant_id': self.restaurant.id
        })
        # When
        response = self.client.get(url)
        # Then
        self.assertEqual(response.status_code, HTTPStatus.OK)

    def test_restaurant_detail_api_should_return_datas_on_valid_request(self):
        '''
        유효한 restaurant id 보낼 시 레스토랑에 관한 데이터 응답하기
        '''
        # Given
        url = reverse("restaurant_api:restaurant_detail_api", kwargs={
            'category_id': self.category.id, 'restaurant_id': self.restaurant.id
        })
        # When
        response = self.client.get(url)
        # Then
        self.assertEqual(response.status_code, HTTPStatus.OK)

    def test_restaurant_detail_api_should_return_404_on_invalid_request(self):
        '''
        유효하지 않은 restaurant id 보낼 시 404 에러 출력하기
        '''
        # Given
        not_exist_id = 99999
        url = reverse("restaurant_api:restaurant_detail_api", kwargs={
            'category_id': not_exist_id, 'restaurant_id': not_exist_id
        })
        # When
        response = self.client.get(url)
        # Then
        self.assertEqual(response.status_code, HTTPStatus.NOT_FOUND)
  • 어려웠던 점

try except로 예외처리를 해야 했는데 except에서 어떤 코드를 작성해서 예외를 표시해야 하는지 헷갈렸다. 

  • 해결방법

레스토랑에 대한 결과가 없는 경우 .DoesNotExist를 사용했다.

  • 느낀점

try except를 더 세분화해서 예외처리를 하면 나중에 오류가 났을 때 어디서 오류가 났는지 파악하기 쉽다.