장고 - 체크인/체크아웃에 예약가능한 숙소 정보 가져오기

  • 이번 포스트에서는 필터 기능을 사용하여 유저가 선택한 체크인, 체크아웃 날짜에 예약 가능한 숙소를 검색할 수 있는 방법에 대해 알아볼 것이다.

ex) yanoljamvp_project

1. model 구현

  • 경로 : 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
    # 숙소 정보 입력할 모델
    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 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
    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
    class StayListApi(generics.ListAPIView):
    serializer_class = StaySerializer
    permission_classes = [AllowAny,]

    def get_queryset(self):
    stays = Stay.objects.all()
    ...
    # GET 요청으로 requestCheckIn(체크인날짜, string), requestCheckOut(체크아웃날짜, string)
    # ex) 2019-07-23+15:00:00
    if self.request.query_params.get('requestCheckIn', None) and self.request.query_params.get('requestCheckOut', None):
    # ---------메인페이지 검색페이지에서 사용자가 체크인/체크아웃 설정한 경우 해당 숙소 필터링하는 코드---------
    # default값으로 체크인 날짜는 현재일로 하여 1박2일 설정 필수 (프론트단에서 설정)
    # 프론트단으로부터 사용자의 체크인/체크아웃 데이터를 string 형태로 받는다.
    # str -> datetime 타입 변환 필요(ex. str = '2019-07-01') ex) 2019-07-01 11:00:00
    requestCheckIn = self.request.query_params.get('requestCheckIn')
    requestCheckOut = self.request.query_params.get('requestCheckOut')

    # '+'를 빼고 나머지 문자열 저장(ex. 2019-07-01+22:00:00 --> 2019-07-01 22:00:00)
    plus_sign = re.compile('[+]')
    requestCheckIn = plus_sign.sub(' ', requestCheckIn)
    requestCheckOut = plus_sign.sub(' ', requestCheckOut)

    # string -> datetime 타입 변환
    requestCheckIn = datetime.strptime(requestCheckIn, '%Y-%m-%d %H:%M:%S')
    requestCheckOut = datetime.strptime(requestCheckOut, '%Y-%m-%d %H:%M:%S')

    # 사용자가 요청한 체크아웃 시간이 체크인 시간보다 앞서 있을 때,
    # searchResult를 False 값으로 설정하여 전송
    if requestCheckOut < requestCheckIn:
    return None

    # 사용자가 요청한 체크인/체크아웃 시간에 예약 가능한 숙소 객체 선별
    finalStays = []
    for stay in stays:
    rooms = stay.rooms.all()
    for room in rooms:
    roomReservation = room.reservations.all()
    # 사용자가 요청한 체크인아웃 시간에 예약가능한 룸 --> 룸의 전체 checkInOut 객체수 == 사용자가 요청한 체크인/체크아웃시간과 겹치지 않는 룸의 checkInOut 객체 수
    if roomReservation.count() == roomReservation.filter \
    (Q(checkIn__gte=requestCheckOut) \
    | Q(checkOut__lte=requestCheckIn)).count():
    # 예약가능한 룸의 숙소 객체를 stay 변수에 저장
    stay = room.stay
    # finalObjects에 해당 숙소 객체 없다면 추가(숙소 객체 중복 방지)
    if stay not in finalStays:
    finalStays.append(stay)

    # list 를 queryset 형태로 변경
    stays = Stay.objects.filter(id__in=[object.id for object in finalStays])

    # queryset 존재하지 않는다면 에러 발생
    if not stays.exists():
    return None

    # requestCheckIn 또는 requestCheckOut 데이터가 전달되지 않은 경우 queryset으로 None 반환
    if self.request.query_params.get('requestCheckIn', None) is None or self.request.query_params.get('requestCheckOut') is None:
    return None

    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)),
    ]