장고 - annotate를 이용한 숙소 정렬 기능 구현하기

  • 이번 포스트에서는 annotate를 이용하여 원하는 기준으로 정렬하는 방법에 대해 알아볼 것이다.

ex) yanoljamvp_project

1. models.py 작성

  • 경로 : stay > models.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    # 숙소 정보 입력할 모델
    class Stay(models.Model):
    # 모텔, 호텔,리조트, 펜션/풀빌라, 게스트하우스 선택
    category = models.ForeignKey(Category, on_delete=models.SET, null=True, blank=True, related_name="stays")

    # 전경 사진(대표 사진) - 이미지 1개
    mainImage = models.CharField(max_length=200, blank=True, null=True)

    # 방 모든 사진 - 이미지 여러개
    urlImage = models.TextField(blank=True, null=True)

    # 숙소 이름(ex. 역삼 바레)
    name = models.CharField(max_length=50)

    # 유저 아이디(ex. positipman)
    username = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name="stays")

    # 숙소 위치(ex. 서울특별시 강남구 봉은사로 428)
    location = models.CharField(max_length=100)

    # 숙소 소개
    introduce = models.TextField(blank=True)

    # 초특가호텔 --> 초특가할인 변경 (사장이 당일특가로 가격 40% 이상 할인 시, 초특가할인 true로 설정)
    # ForeignKey로 연결되어 있는 아래의 Room 모델에서 초특가할인 여부 확인

    # 편의시설 및 서비스 항목 (상기 SERVICE_CHOICES 참고)
    serviceKinds = MultiSelectField(choices=SERVICE_CHOICES, null=True, blank=True)

    # 편의시설 및 서비스 설명
    serviceIntroduce = models.TextField(blank=True)

    # 이용안내
    serviceNotice = models.TextField(blank=True)

    # 픽업안내
    pickupNotice = models.TextField(blank=True)

    # 숙소 리스트에 보여질 위치
    directions = models.TextField(blank=True)

    # 찾아오시는 길
    route = models.TextField(blank=True, null=True)

    # 찜하기
    like = models.ManyToManyField(User, blank=True, related_name="like_stay")

    # 검색 관련 키워드
    keywords = TaggableManager()

    # Stay 객체 생성일 자동 저장
    created = models.DateTimeField(auto_now_add=True)

    # Stay 객체 수정일 자동 저장
    updated = models.DateTimeField(auto_now=True)

    # 관리자페이지에서 저장될 객체의 순서 기준 설정
    class Meta:
    ordering = ['category']

    # 관리자페이지에서 보여지는 객체 이름 설정
    def __str__(self):
    return f"{self.category} - {self.name}"


    # room --> 멀티 이미지 구현 필요
    class Room(models.Model):
    # 숙소 선택(호텔, 모텔, 펜션 외)
    stay = models.ForeignKey(Stay, on_delete=models.CASCADE, related_name="rooms")

    # 룸 이름
    name = models.CharField(max_length=50)

    urlImage = models.TextField(blank=True, null=True)

    # 누가 어느 방을 예약했는지 확인
    reserved = models.ManyToManyField(User, blank=True, related_name="reserved")

    booker_set = models.ManyToManyField(User, through='Reservation')

    # 대실 이용시간(ex. 3) --> 0이면 숙박만 가능
    hoursAvailable = models.IntegerField(default=0, blank=True, null=True)

    # 대실 운영시간(ex. 23) --> ~23:00
    hoursUntil = models.IntegerField(default=0, blank=True, null=True)

    # 숙박 체크인 가능 시간(ex. 22(오후 10시))
    daysCheckIn = models.IntegerField(default=0)

    # 숙박 체크아웃 마감시간(ex. 11(오전 11시))
    daysCheckOut = models.IntegerField(default=0)

    # 기준 인원
    standardPersonnel = models.IntegerField()

    # 최대 인원
    maximumPersonnel = models.IntegerField()

    # 대실 예약가
    hoursPrice = models.CharField(max_length=50, blank=True)

    # 숙박 예약가
    daysPrice = models.CharField(max_length=50)

    # 데이터 있으면, 해당 할인가로 표시
    # views.py에서 할인률 40% 이상이면, 초특가할인으로 지정하도록 구현할 것

    # 대실 예약가 할인
    saleHoursPrice = models.CharField(max_length=50, blank=True)

    # 숙박 예약가 할인
    saleDaysPrice = models.CharField(max_length=50, blank=True)

    # 기본정보
    basicInfo = models.TextField(blank=True)

    # 예약공지
    reservationNotice = models.TextField(blank=True)

    # 취소규정
    cancelRegulation = models.TextField(null=True)

    def __str__(self):
    return f"{self.stay} - {self.name}"


    # 댓글 저장할 모델
    class Comment(models.Model):
    stay = models.ForeignKey(Stay, on_delete=models.CASCADE, related_name='comments')

    room = models.ForeignKey(Room, on_delete=models.SET_NULL, blank=True, null=True)

    reservation = models.OneToOneField('Reservation', on_delete=models.SET_NULL, blank=True, null=True)

    # 로그인한 유저
    username = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="comments")

    # 댓글 내용
    text = models.TextField(default="")

    # 댓글 작성 시, 자동으로 댓글 작성한 날짜 저장
    created = models.DateTimeField(auto_now_add=True)

    # 댓글 수정 시, 자동으로 댓글 수정한 날짜 저장
    updated = models.DateTimeField(auto_now=True)

    # 대댓글 기능 구현 위해 대댓글 작성할 특정 댓글 선택
    parentComment = models.ForeignKey("self", on_delete=models.CASCADE, default="", blank=True, null=True)

    # 평가항목 별 점수 선택
    evaluationItems1 = models.IntegerField(default=5) # 친절도
    evaluationItems2 = models.IntegerField(default=5) # 청결도
    evaluationItems3 = models.IntegerField(default=5) # 편의성
    evaluationItems4 = models.IntegerField(default=5) # 서비스 만족도

    def __str__(self):
    return f"{self.stay}-{self.username}님의 댓글"


    # 유저가 예약할 때 필요한 정보 저장
    class Reservation(models.Model):
    # 예약할 룸을 가진 숙소
    stay = models.ForeignKey(Stay, on_delete=models.SET_NULL, null=True, blank=True, related_name="reservations")

    # 예약할 룸
    room = models.ForeignKey(Room, on_delete=models.SET_NULL, null=True, blank=True, related_name="reservations")

    # 최종 결제금액
    finalPrice = models.CharField(max_length=50, default="")

    # 대실일 경우, views.py에서 현재 시간이 만약 14시 30분이라면 15시부터 선택가능하도록
    checkIn = models.DateTimeField(blank=True, null=True)

    # 대실일 경우, views.py에서 해당 룸의 대실시간 고려하여 checkOut 시간 자동 저장
    checkOut = models.DateTimeField(blank=True, null=True)

    # 로그인한 유저아이디
    username = models.ForeignKey(User, on_delete=models.CASCADE, related_name="reservations")

    # 예약자 이름(views.py에서 default로 유저 이름 자동 설정) --> 변경 가능
    booker = models.CharField(max_length=20)

    # 예약자 폰 번호(views.py에서 default로 유저 폰번호 자동 설정) --> 변경 가능
    phoneNumber = models.CharField(max_length=30)

    wayToGo = models.CharField(max_length=10, blank=True, null=True)

    # 대실, 숙박 선택(대실 혹은 숙박 예약하기 클릭 시, 해당 항목 True로 자동 변경)
    checkHours = models.BooleanField(default=False)
    checkDays = models.BooleanField(default=False)

    # 예약한 날짜 자동 저장
    created = models.DateTimeField(auto_now_add=True)

    def __str__(self):
    return f"{self.username.username} has reserved the {self.room} in {self.stay}"

2. apis.py 작성

  • 경로 : stay > apis.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class StayListApi(generics.ListAPI):
    serializer_class = StaySerializer
    permission_classes = [AllowAny,]
    def get_queryset(self):
    stays = Stay.objects.all()
    # 후기 많은 순 (review)
    if self.request.query_params.get('review', None):
    stays = stays.annotate(
    review=Count('comments')
    ).order_by('-review')

    # 찜 많은 순 (wish)
    if self.request.query_params.get('wish', None):
    stays = stays.annotate(
    wish=Count('like')
    ).order_by('-wish')

    # 예약가 낮은 순(priceLow)
    if self.request.query_params.get('priceLow', None):
    stays = stays.annotate(
    # daysPrice가 varchar 타입이므로, Integer 타입으로 변경하고, 해당 숙소에서 제일 최저가의 방의 숙박 예약가를 'price' 변수가 참조하게 한다.
    price=Min(Cast('rooms__daysPrice', IntegerField())),
    ).order_by('price')

    # 예약가 높은 순(priceHigh)
    if self.request.query_params.get('priceHigh', None):
    stays = stays.annotate(
    price=Min(Cast('rooms__daysPrice', IntegerField())),
    ).order_by('-price')

    queryset = stays

    return queryset

3. serializers.py

  • 경로 : stay > serializers.py
    1
    2
    3
    4
    class StaySerializer(serializers.ModelSerializer):
    class Meta:
    model = Stay
    fields = '__all__'

4. urls.py

  • 경로 : stay > urls.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from django.urls import path, include
    from .views import *
    from .apis import *

    app_name = "stay"

    urlpatterns_apis = [
    ...
    path('stay/', StayListApi.as_view()),
    ...
    ]

    urlpatterns = [
    ...
    path('api/', include(urlpatterns_apis)),
    ]