1. Django Tutorial(Airbnb) - Search Bar 기능 구현

ID짱재·2021년 8월 10일
1

Django

목록 보기
18/43
post-thumbnail

🔥 수동으로 Search Bar 구현하기

🔥 Django From으로 Search Bar 구현하기

🔥 검색 결과에 Pagination 기능 추가


1. Search Bar 수작업으로 구현하기

1) form 속성

  • search bar는 목록, 상세보기 등 대부분 페이지에서 기능할 수 있도록 base.html에 만들어 볼께요.
  • method는 get방식으로 받고, action은 form의 전송될 위치입니다. 전송될 위치에 대한 경로는 namespace와 name을 통해 설정하면 간편합니다.
    • 🔎 <form method="get" action="{% url "rooms:search" %}"></form>
  • input의 name속성은 argument의 key값입니다.
  • 또한 form을 {% block search-bar % } ~ {% endblock search-bar %}으로 감싼 이유는 base.html은 모든 페이지에 적용되는데, 때론 search bar가 나타나지 않아야할 페이지도 필요하기 때문이에요. 필요하지 않은 페이지에서 {% block search-bar % } ~ {% endblock search-bar %}를 다시 선언하면 덮어씌어져서 해당 페이지에서는 나타나지 않는답니다.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block page_title %}{% endblock page_title %}| Nbnb</title>
</head>
<body>
    <header>
        {% include "partials/nav.html" %}
        {% block search-bar %}  # 👈 search-bar를 block으로 감싸줬어요!
        <form method="get" action="{% url "rooms:search" %}"> # 👈 입력을 받기 위한 form 추가
            <input name="city" placeholder="Search by City"> # 👈 값이 argument의 key값으로 사용됩니다.
        </form>
        {% endblock search-bar %}
    </header>
    {% block content %}{% endblock content %}
    {% include "partials/footer.html" %}
</body>
</html>

2) URL 및 View 기본 세팅

  • search bar에 입력 후 전송하면, views.py의 search함수가 작동하게 Url을 매핑하였어요.
# rooms/urls.py
from django.urls import path
from . import views
app_name = "rooms"
urlpatterns = [
    path("<int:pk>", views.RoomDetail.as_view(), name="detail"), # 👈 상세보기에 대한 url 매핑이에요.
    path("search/", views.search, name="search"), # 👈 views.search 함수와 매핑시켜줘요.
]
  • 템플릿에서 action의 경로 설정을 해줬기 때문에 form 양식에 입력한 결과를 전송하면 search 함수가 실행됩니다.
  • 사용자가 입력한 argument값을 get을 통해 url에서 views.py로 가져올 수 있어요.
    • 🔎 city = request.GET.get("city")
  • "seoul"을 입력하면, "http://127.0.0.1:8000/rooms/search/?city=seoul"로 이동하고, console에는 "Seoul"이 출력되는 것을 볼 수 있네요.
from django.shortcuts import render
from . import models
...
...
def search(request):
    city = request.GET.get("city")
    city = str.capitalize(city) # 👈 앞 단어 대문자로 처리 
    # print(request)
    # print(city)
    context = {"city": city}
    return render(request, "rooms/search.html", context)
  • form을 통한 입력값이 html로 render 되는지 확인해보기 위해 "rooms/search.html"를 간단히 작성해 볼께요. 사용자의 입력을 url에서 받아온 뒤, 다시 템플릿 변수로 전달하였어요.
  • {% block search-bar %} ~ {% endblock search-bar % }를 search.html에서 다시 지정한 이유는 search의 결과물인 페이지이기 때문에 search bar를 가리기 위해서에요. 덮어씌우면 기존 base.html의 serach bar는 나타나지 않아요.
# rooms/search.html
{% extends "base.html" %}
    {% block page_title %}
        Search
    {% endblock page_title %}
    {% block search-bar %} 👈 base.html에 search-bar를 덮어씌어 안보이게 처리할 수 있답니다:)
    {% endblock search-bar %}
    {% block content %}
    <h2>Search!</h2>
    <h4>Search By {{city}}</h4> 
    {% endblock content %}

3) 다중 검색

  • 도시, 국가, room_type 등을 동시에 검색해서 이에 해당하는 정보만 조회할 수 있게 하는 search bar를 만들려고 해요.
  • form을 통해 사용자가 입력 또는 선택한 정보들이 url의 query로 나타날 수 있게 해야겠죠.
  • Model에서 사용한 패키지인 Django_country를 가져올께요. Django_Counry는 바로 Context로 넘겨줄 수 있어요. console의 Object가 나타나는 것을 볼 수 있어요. import는 필요하니다.
    • 🔎 from django_countries import countries
  • RoomType 모델에서 모든 객체를 가져와서 context로 넘겨주면, 템플릿에서 RoomType를 선택 목록으로 사용할 수 있답니다.
from django.shortcuts import render
from django_countries import countries
from . import models
...
...
def search(request):
    city = request.GET.get("city", "Anywhere")
    city = str.capitalize(city)
    room_types = models.RoomType.objects.all()
    context = {
        "city": city,
        "countries": countries,
        "room_types": room_types,
    }
    return render(request, "rooms/search.html", context)
  • city는 사용자에게 직접 입력받지만, country와 room_type는 선택목록으로 만들었어요.
  • option 태그의 vaule값을 지정을 통해 선택된 값을 url에 argument로 나타낼 수 있어요. 또한 .code를 통해 DB의 값을 이용할 수 있답니다.
    • 🔎 <option value="{{country.code}}">{{country.name}}</option>
    • 🔎 <option value="{{room_type.pk}}">{{room_type.name}}</option>
# rooms/search.html
{% extends "base.html" %}
    {% block page_title %}
        Search
    {% endblock page_title %}
    {% block search-bar %} 👈 base.html의 search bar는 우선 숨기고 밑에서 test해볼께요:)
    {% endblock search-bar %}
    {% block content %}
    <h2>Search!</h2>
    <form method="get" action="{% url "rooms:search" %}">
        <div>
            <label for="city">City</label>
            <input value="{{city}}" id="city" name="city" placeholder="Search by City" type="text">
        </div>
        <div>
            <label for="country">Country</label>
            <select id="country" name="country">
                {% for country in countries %} 👈 django_counries를 목록으로 나타내줘요.
                    <option value="{{country.code}}">{{country.name}}</option>
                {% endfor %}
            </select>
        </div>
        <div>
            <label for="room_type">Room Types</label>
            <select id="room_type" name="room_type">
                {% for room_type in room_types %} 👈 room_types를 선택목록을 나타내줘요.
                    <option value="{{room_type.pk}}">{{room_type.name}}</option>
                {% endfor %}
            </select>
        </div>
        <button>Search</button>
    </form>
    {% endblock content %}

4) selected 속성

  • "seoul", "South Korea", "Hotel Room"을 선택하고 search 버튼을 누르면, 현재 form에 입력한 값이 url에 잘 표현되고 있어요. 참고로 form안에 button이 1개라면, submit과 같은 기능을합니다.
  • 문제는 전송버튼을 누르면, 선택된 값이 url에는 잘 나타나지만 form이 초기화되는 현상이 발생합니다. 현재 form에서는 선택된 값을 기억하지 못하기 때문이에요. 이를 보완해볼께요.
  • 우선 선택된 값이 url에는 잘 표시되기 때문에 views.py에서 해당 argument를 가져올께요. 값이 없는데 요구하면 오류가 발생하기 때문에 Default 값으로 "KR"과 0을 각 각 지정했어요. 또한 url의 값을 get으로 가져오면 str 형태에요,, room_types의 경우에는 int로 수정해주었어요!
    • 🔎 country = request.GET.get("country", "KR")
    • 🔎 room_type = int(request.GET.get("room_type", 0))
from django.shortcuts import render
from django_countries import countries
from . import models
...
def search(request):
    city = request.GET.get("city", "Anywhere")
    city = str.capitalize(city)
    country = request.GET.get("country", "KR")
    room_type = request.GET.get("room_type", 0)
    room_types = models.RoomType.objects.all()
    context = {
        "city": city,
        "countries": countries, # 👈 Django_Country의 국가정보
        "country": country,   # 👈 url에서 가져온 선택된 국가
        "room_types": room_types,  # 👈 Room_Type 모델의 객실 타입 정보
        "current_room_type": room_type, # 👈 url에서 가져온 선택된 객실타입
    }
    return render(request, "rooms/search.html", context)
  • 이름이 비슷해서 헷갈릴 수 있어요. 이를 좀 더 명확히 구분할 수 있도록 나눠볼께요.
  • context를 form과 choices로 분리시켯어요. form은 사용자가 입력 또는 선택한 값을 url로 받아온 것이고, choices는 선택 목록을 제공해주기 위한 변수입니다.
  • 이렇게 둘 이상의 변수를 병합하여 템플리 변수로 render하기 위해서 아래와 같이 unpack(**)을 사용하여 처리해줄 수 있어요.
    • return render(request, "rooms/search.html", {**form, **choices})
from django.shortcuts import render
from django_countries import countries
from . import models
...
def search(request):
    city = request.GET.get("city", "Anywhere")
    city = str.capitalize(city)
    country = request.GET.get("country", "KR")
    room_type = int(request.GET.get("room_type", 0)) 👈 url에서 넘어온 값을 int로 변환
    room_types = models.RoomType.objects.all()
    form = {
        "city": city,  # 👈 사용자가 form에 입력한 도시
        "s_country": country, # 👈 사용자가 form에 선택한 국가
        "s_room_type": room_type, # 👈 사용자가 form에 선택한 객실타입
    }
    choices = {  # 👈 선택 목록 제공
        "countries": countries,
        "room_types": room_types,
    }
    return render(request, "rooms/search.html", {**form, **choices})
  • {% if %} ~ {% endif %}를 통해 url의 값과 선택된 값이 일치하는 경우에 option 태그의 selected 속성을 활성화시켰어요.
  • "Any Kind"는 어떤 roon_type이라도 상관없을 경우를 대비해 0번을 value로하여 새로 생성해주었어요. 이처럼 선택할 수 있는 값을 더 제공하고 싶다면 수동으로도 생성해줄수도 있답니다.
{% extends "base.html" %}
    {% block page_title %}
        Search
    {% endblock page_title %}
    {% block search-bar %}
    {% endblock search-bar %}
    {% block content %}
    <h2>Search!</h2>
    <form method="get" action="{% url "rooms:search" %}">
        <div>
            <label for="city">City</label>
            <input value="{{city}}" id="city" name="city" placeholder="Search by City" type="text">
        </div>
        <div>
            <label for="country">Country</label>
            <select id="country" name="country">
                {% for country in countries %}
                    <option value="{{country.code}}" {% if  country.code == s_country %}selected{% endif %}>
                    {{country.name}}
                    </option>
                {% endfor %}
            </select>
        </div>
        <div>
            <label for="room_type">Room Types</label>
            <select id="room_type" name="room_type">
                <option value="0" {% if s_room_type == 0 %}selected{% endif %}>
                Any Kind
                </option>
                {% for room_type in room_types %}
                    <option value="{{room_type.pk}}" {% if s_room_type == room_type.pk %}selected{% endif %}>
                    {{room_type.name}}
                    </option>
                {% endfor %}
            </select>
        </div>
        <button>Search</button>
    </form>
    {% endblock content %}

5) checked

  • 선택목록은 selected를 통해 사용자가 선택한 값을 기억하게 했는데요,, checkbox는 checked를 통해 값을 기억하게 할 수 있어요.
  • 단, check 박스는 선택 결과가 1개일 수도 있고 더 많을 수도 있기 때문에 사용자가 선택한 값들을 getlist로 받아올 수 있습니다. getlist는 그 값들을 배열 형태로 가져옵니다.
    • 🔎 request.GET.getlist("name값")
  • intant와 superhost는 체크박스이지만 True, False값만 가지면 되요, 여러개의 체크가 존재하는게 아니니까요! 체크가 되지 않는 False일 때는 문제없지만 True일 때, 값을 "on"을 가지기 때문에 bool()로 형변환을 해줘야합니다:)
from django.shortcuts import render
from django_countries import countries
from . import models
...
...
def search(request):
    city = request.GET.get("city", "Anywhere")
    city = str.capitalize(city)
    country = request.GET.get("country", "KR")
    room_type = int(request.GET.get("room_type", 0))
    price = int(request.GET.get("price", 0))
    guests = int(request.GET.get("guests", 0))
    bedrooms = int(request.GET.get("bedrooms", 0))
    beds = int(request.GET.get("beds", 0))
    baths = int(request.GET.get("baths", 0))
    instant = bool(request.GET.get("instant", False)) # 👈 agument가 1개 일 때,
    superhost = bool(request.GET.get("superhost", False)) # 👈 agument가 1개 일 때,
    s_amenities = request.GET.getlist("amenities") # 👈 agument가 여러개면 list도 받아요:)
    s_facilities = request.GET.getlist("facilities") # 👈 agument가 여러개면 list도 받아요:)
    # print(s_amenities, s_facilities) # 👈 사용자가 check한 값을 list로 볼 수 있어요:)
    form = {
        "city": city,
        "s_country": country,
        "s_room_type": room_type,
        "price": price,
        "guests": guests,
        "bedrooms": bedrooms,
        "beds": beds,
        "baths": baths,
        "instant": instant,
        "superhost": superhost,
        "s_amenities": s_amenities,
        "s_facilities": s_facilities,
    }
    room_types = models.RoomType.objects.all()
    amenities = models.Amenity.objects.all()
    facilities = models.Facility.objects.all()
    choices = {
        "countries": countries,
        "room_types": room_types,
        "amenities": amenities,
        "facilities": facilities,
    }
    return render(request, "rooms/search.html", {**form, **choices})
  • 이제 getlist를 통해 사용자가 체크한 값들을 view에서 템플릿으로 전달할 수 있어요.
  • 이에 pk값이 list 내에 포함되있는지 확인해서,, list에 포함되어있다면 checked를 활성화시켜주면 되요:)
  • 다만, pk값은 int형이고, getlists 넘어온 list는 str이에요. int형을 str로 바꿀수 있는 템플릿 필터인 slugify를 사용하여 캐스팅을 해줄께요!
    • 🔎 변수|slugify
{% extends "base.html" %}
    {% block page_title %}
        Search
    {% endblock page_title %}
    {% block search-bar %}
    {% endblock search-bar %}
    {% block content %}
    ...
    ...
        <div>
            <label for="instatn">Instant Book Only?</label>
            <input type="checkbox" name="instant" id="instant" {% if instant %}checked{% endif %}>
        </div>
        <div>
            <label for="super_host">By Superhost Only?</label>
            <input type="checkbox" name="superhost" id="superhost" {% if superhost %}checked{% endif %}>
        </div>
        <div>    
        <div>
            <h3>Amenities</h3>
            <ul>
                {% for amenity in amenities %}
                    <li>
                        <label for="a_{{amenity.pk}}">{{amenity.name}}</label>
                        <input
                        type="checkbox"
                        value="{{amenity.pk}}"
                        name="amenities"
                        id="a_{{amenity.pk}}"
                        {% if amenity.pk|slugify in s_amenities %}
                            checked 
                        {% endif %}
                        />                        
                    </li>
                {% endfor %}
            </ul>
        </div>        
        <div>
            <h3>Facilities</h3>
            <ul>
                {% for facility in facilities %}
                    <li>
                        <label for="f_{{facility.pk}}">{{facility.name}}</label>
                        <input
                        type="checkbox"
                        value="{{facility.pk}}"
                        name="facilities"
                        id="f_{{facility.pk}}"
                        {% if facility.pk|slugify in s_facilities %}
                            checked
                        {% endif %}                          
                        />                        
                    </li>
                {% endfor %}
            </ul>
        </div>        
        <button>Search</button>
    </form>
    {% endblock content %}

6) conditional filtering

  • 이제 검색에 필요한 값을 입력 또는 선택할 수도 있고, 이를 기억할 수도 있어요,, 그렇다면, 선택된 값들에 해당되는 정보를 출력해주는 기능이 필요하겠죠. DB에서 어떤 기준에 해당하는 자료를 추출해오는 것은 filter()를 통해 할 수 있어요.
  • filter()에 전달한 파라미터는 Dict 형태로 만들어 한번에 전달하면 편리합니다.
    filter_args = {}
    rooms = models.Room.objects.filter(**filter_args) 
  • Field lookups를 참고하면 filter를 더 효과적으로 다룰 수 있는 검색 기능을 사용할 수 있어요!
    • __starswith, __lte, __gte, __pk, contain 등..
  • s_facilities, s_facilities는 getlist로 값을 받아오기 때문에 list형태 입니다. 또한 그 안에 값은 str이죠. 이에 for문으로 1개씩 꺼내서 pk값을 key값으로 filter_args에 추가해줄께요.
from django.shortcuts import render
from django_countries import countries
from . import models
...
...
def search(request):
    city = request.GET.get("city", "Anywhere")
    city = str.capitalize(city)
    country = request.GET.get("country", "KR")
    room_type = int(request.GET.get("room_type", 0))
    price = int(request.GET.get("price", 0))
    guests = int(request.GET.get("guests", 0))
    bedrooms = int(request.GET.get("bedrooms", 0))
    beds = int(request.GET.get("beds", 0))
    baths = int(request.GET.get("baths", 0))
    instant = bool(request.GET.get("instant", False))
    superhost = bool(request.GET.get("superhost", False))
    s_amenities = request.GET.getlist("amenities")
    s_facilities = request.GET.getlist("facilities")
    form = {
        "city": city,
        "s_country": country,
        "s_room_type": room_type,
        "price": price,
        "guests": guests,
        "bedrooms": bedrooms,
        "beds": beds,
        "baths": baths,
        "instant": instant,
        "superhost": superhost,
        "s_amenities": s_amenities,
        "s_facilities": s_facilities,
    }
    room_types = models.RoomType.objects.all()
    amenities = models.Amenity.objects.all()
    facilities = models.Facility.objects.all()
    choices = {
        "countries": countries,
        "room_types": room_types,
        "amenities": amenities,
        "facilities": facilities,
    }
    # filter()에 들어갈 파라미터를 filter_args에 모두 담아 unpack할께요:)
    filter_args = {}
    if city != "Anywhere":
        filter_args["city__startswith"] = city # 👈 __starswith
    filter_args["country"] = country
    if room_type != 0:
        filter_args["room_type__pk"] = room_type # 👈 foreignkey 사용
    if price != 0:
        filter_args["price__lte"] = price # 👈 ~이하
    if guests != 0:
        filter_args["guests__gte"] = guests  # 👈 ~이상
    if bedrooms != 0:
        filter_args["bedrooms__gte"] = bedrooms
    if beds != 0:
        filter_args["beds__gte"] = beds
    if baths != 0:
        filter_args["baths__gte"] = baths
    if instant is True:    # 👈 True와 False 값만 가져요
        filter_args["instant_book"] = True
    if superhost is True:  # 👈 Room 모델에 superhost가 없기 때문에 foreignkey로 접근
        filter_args["host__superhost"] = True
    if len(s_amenities) > 0:
        for s_amenity in s_amenities:
            filter_args["amenities__pk"] = int(s_amenity)
    if len(s_facilities) > 0:
        for s_facility in s_facilities:
            filter_args["facilities__pk"] = int(s_facility)
    rooms = models.Room.objects.filter(**filter_args) # 👈 Unpack
    return render(request, "rooms/search.html", {**form, **choices, "rooms": rooms})
  • 템플릿에 rooms로 전달했기 때문에 for문을 사용해서 해당되는 방을 출력할 수 있어요. form문 아래에 간단히 작성해볼께요.
...
...
    <h3>Results</h3>
    {% for room in rooms %}
        <h3>{{room.name}}</h3>
    {% endfor %}

2. Django From으로 Search Bar 구현하기

1) Django Form이란?

  • Model을 만드는것처럼 forms.py에 Class를 만들어 Django Form을 상속받아 사용하면, 더 간편하게 form을 만들 수 있어요.
  • Django가 제공하는 forms를 가져온 뒤, Form을 상속받아 사용합니다.
# rooms/forms.py 
from django import forms # 👈 django forms import
class SearchForm(forms.Form):
    """Search Form Definition"""
    city = forms.CharField() # 👈 글자를 입력할 수 있는 input을 만들어줘요.
    price = forms.IntegerField() # 👈 숫자를 입력할 수 있는 input을 만들어줘요.
  • forms.py에서 만든 Django Form인 SearchForm을 변수에 담아 템플릿에 render해볼께요:)
from django.shortcuts import render
from django_countries import countries
from . import models, forms  # 👈 forms.py import
...
...
def search(request):
    form = forms.SearchForm()
    context = {"form": form}
    return render(request, "rooms/search.html", context)
  • form 태그안에 View에서 전달받은 {{form}}을 넣어준 것뿐이에요. method와 action을 달라진게 없어요!
  • city에는 seoul, price에는 100000을 입력하고 url을 살펴볼께요. name을 지정하지 않았는데도 "?city=seoul&price=100000"으로 나타나네요,, Django에서 알아서 설정해주는군요!
{% extends "base.html" %}
    {% block page_title %}
        Search
    {% endblock page_title %}
    {% block search-bar %}
    {% endblock search-bar %}
    {% block content %}
    <h2>Search!</h2>
    <form method="get" action="{% url "rooms:search" %}">
        {{form}}
        <button>Search</button>
    </form>
    {% endblock content %}

2) Outputting forms as HTML

  • 입력창이 옆으로 나란히 나타나는걸 볼 수 있어요. 페이지 소스보기를 통해 확인해보면 <tr>, <th>, <td> 로 자동 설정되었네요. .as_p, .as_ul, .as_table로 형식을 바꿀 수 있답니다.
    • p태그처럼 사용하기 : 🔎 {{form.as_p}}

3) Django Form Field API

  • 참고 : https://docs.djangoproject.com/en/2.2/ref/forms/fields/
  • 텍스를 입력 받을 때는 CharFied, 숫자는 IntegerField, 단일선택목록은 ModelChoiceField, True&False 체크박스는 BooleanField로 form을 만들어 줄 수 있어요!
  • amenities와 facilities와 같은 다중 선택 목록은 ModelMultipleChoiceField를 사용해요. widget을 통해 더 편리하게 이용할 수 있도록 form의 형태를 바꿔줍니다. checkbox형태로 다중 선택할 수 있도록 CheckboxSelectMultiple를 적용했어요.
  • Django_Counry는 외부 라이브러리이기 때문에 Django_Country 문서를 참고하였습니다.
    • 🔎 from django_countries.fields import CountryField
    • 🔎 counrty = CountryField(default="KR").formfield() 👈 기본값으로 KR을 지정
  • 또한 field안에 여러 속성을 통해 Django Form을 제어할 수 있답니다:)
    • 🔎 initial="" : CharField에서 빈 form 대신 초기값을 넣어둘 수 있어요.
    • 🔎 empty_label="" : 선택목록을 제공하는 ModelChoiceField의 선택안함 필드의 이름을 정해줘요. empty_label이 없으면 "----"의 선택 값이 기본으로 주어집니다.
    • 🔎 queryset="" : 모델에서 데이터를 가져와 선택목록을 제공합니다.
    • 🔎 required="" : field 값을 비어두어도 전송이 가능합니다.
from django import forms
from django_countries.fields import CountryField # 👈 Django_Country form에서 가져오기
from . import models
class SearchForm(forms.Form):
    """Search Form Definition"""
    city = forms.CharField(initial="Anywhere")
    country = CountryField(default="KR").formfield()
    room_type = forms.ModelChoiceField(
        empty_label="Any Kind", queryset=models.RoomType.objects.all(), required=False
    )
    price = forms.IntegerField(required=False)
    guests = forms.IntegerField(required=False)
    bedrooms = forms.IntegerField(required=False)
    beds = forms.IntegerField(required=False)
    baths = forms.IntegerField(required=False)
    instant_book = forms.BooleanField(required=False)
    superhost = forms.BooleanField(required=False)
    amenities = forms.ModelMultipleChoiceField(
        queryset=models.Amenity.objects.all(),
        widget=forms.CheckboxSelectMultiple,
        required=False,
    )
    facilities = forms.ModelMultipleChoiceField(
        queryset=models.Facility.objects.all(),
        widget=forms.CheckboxSelectMultiple,
        required=False,
    )
  • Django Form을 사용하면, name, id, for를 지정하지 않아도, Django에서 알아서 입력해줍니다. 템플릿 파일에 html 태그를 하나하나 작성하는 수고로움을 덜어줍니다. 또한 입력 및 선택한 값들이 모두 url에 argument로 나타나는 것을 볼 수 있어요. html 태그와 그 속성이 어떻게 세팅되었는지 궁금하다면 페이지 소스보기를 참고하세요.

4) request.GET

  • 이제 입력 및 체크한 값들을 기억할 수 있게 해줄께요. Django form을 사용하지 않았을 때는 url에 argument를 GET.get 또는 GET.getlist 통해 각 argument를 받아온 뒤, 다시 템플릿으로 render했었어요. Django Form을 사용한다면 request.GET만 Form에 전달해주면 됩니다!
  • 전송버튼을 눌러도 계속 값을 가지고 있어요!
def search(request):
    form = forms.SearchForm(request.GET) # 👈 request.GET를 통해 모든 값을 기억해요! 끝.
    context = {"form": form}
    return render(request, "rooms/search.html", context)

5) is_valid()

  • 처음 접근할 때는 기억하는 값이 없을수 밖에 없겟죠. 이럴 때 required 오류메시지가 해당 필드아래 나타냅니다. 값이 있을 때는 기억하고, 없을 때는 빈 form을 보여주겠끔 처리할 수 있어요.
from django.shortcuts import render
from django_countries import countries
from . import models, forms
...
...
def search(request):
    country = request.GET.get("country")
    if country:
        form = forms.SearchForm(request.GET)
        if form.is_valid():
            print(form.cleaned_data)
    else:
        form = forms.SearchForm()
    context = {"form": form}
    return render(request, "rooms/search.html", context)
  • is_value()로 처리해주면, form에 입력된 값의 유효성 검사를 해줘요. 예를들어 IntegerField에 text를 넣었다면 문제가 있음을 알려주죠. 유효성 검사를 마친 데이터들을 cleaned_data로 출력해보면 아래처럼 나타나는 것을 볼 수 있습니다.
  • 입력한 데이터 뿐 아니라, 선택된 데이터를 DB에서 찾아와 갖고 있어요! 또한 url에서 on으로 나타나는 값을 True로 자동으로 변환해둡니다. bool, int 등의 형변환을 대신 해주죠!
{'city': 'seoul', 'country': 'KR', 'room_type': <RoomType: Private Room>,
'price': None, 'guests': None, 'bedrooms': None, 'beds': None, 'baths':
None, 'instant_book': False, 'superhost': True, 'amenities': <QuerySet
[<Amenity: Shower>, <Amenity: Wifi>, <Amenity: Washer>]>, 'facilities':
<QuerySet [<Facility: Park>, <Facility: Elevator>, <Facility: Gym>]>}

6) conditional filtering

  • cleaned_data로 argument의 값들이 정리되어 올 뿐 아니라, DB의 QuerySet형태로 있기 때문에 search argument를 만드는 것도 비교적 수월해요! 형변환이나, __pk등이 필요 없거든요!
from django.shortcuts import render
from django_countries import countries
from . import models, forms
...
...
def search(request):
    country = request.GET.get("country")
    if country:
        form = forms.SearchForm(request.GET)
        if form.is_valid():
            city = form.cleaned_data.get("city")
            country = form.cleaned_data.get("country")
            room_type = form.cleaned_data.get("room_type")
            price = form.cleaned_data.get("price")
            guests = form.cleaned_data.get("guests")
            bedrooms = form.cleaned_data.get("bedrooms")
            beds = form.cleaned_data.get("beds")
            baths = form.cleaned_data.get("baths")
            instant_book = form.cleaned_data.get("instant_book")
            superhost = form.cleaned_data.get("superhost")
            amenities = form.cleaned_data.get("amenities")
            facilities = form.cleaned_data.get("facilities")
            # search argument
            filter_args = {}
            if city != "Anywhere":
                filter_args["city__startswith"] = city
                filter_args["country"] = country
            if room_type is not None:
                filter_args["room_type"] = room_type
            if price is not None:
                filter_args["price__lte"] = price
            if guests is not None:
                filter_args["guests__gte"] = guests
            if bedrooms is not None:
                filter_args["bedrooms__gte"] = bedrooms
            if beds is not None:
                filter_args["beds__gte"] = beds
            if baths is not None:
                filter_args["baths__gte"] = baths
            if instant_book is True:
                filter_args["instant_book"] = True
            if superhost is True:
                filter_args["host__superhost"] = True
            for amenity in amenities:
                filter_args["amenities"] = amenity
            for facility in facilities:
                filter_args["facilities"] = facility
            rooms = models.Room.objects.filter(**filter_args)
    else:
        form = forms.SearchForm()
    context = {"form": form, "rooms": rooms}
    return render(request, "rooms/search.html", context)

3. 검색 결과에 Pagination 기능 추가

  • search 함수는 FBV죠,, CBV로 사용할 수 있답니다. View를 import한 후, class안에 search함수를 넣어주기만 하면 되요. 대신 request뿐아니라 self도 받아야겠죠. 더불어 rooms/urls.py에서 매핑할 view를 CBV이름으로 연결하고 as_view()를 설정해주세요:)
rooms/urls.py
from django.urls import path
from . import views
app_name = "rooms"
urlpatterns = [
    path("<int:pk>", views.RoomDetail.as_view(), name="detail"),
    path("search/", views.SearchView.as_view(), name="search"),
]
  • paginator를 적용시켰는데, consoel에 "UnorderedObjectListWarning"이 발견된다면, QuerySet에 정렬이 필요하단 경고에요. order_by로 정렬을 해주면 됩니다.
    • 🔎 qs = models.Room.objects.filter(**filter_args).order_by("-created")
from django.views.generic import ListView, DetailView, View
from django.shortcuts import render
from django.core.paginator import Paginator
from . import models, forms
...
...
class SearchView(View):
    def get(self, request):
        country = request.GET.get("country")
        if country:
            form = forms.SearchForm(request.GET)
            if form.is_valid():
                ...
                ...
                ...
               # order_by를 통해 정렬이 필요해요:)
                qs = models.Room.objects.filter(**filter_args).order_by("-created")
                paginator = Paginator(qs, 10, orphans=5)
                page = request.GET.get("page", 1)
                rooms = paginator.get_page(page)
                context = {"form": form, "rooms": rooms}
                return render(request, "rooms/search.html", context)
        else:
            form = forms.SearchForm()
            context = {"form": form}
        return render(request, "rooms/search.html", context)
profile
Keep Going, Keep Coding!

0개의 댓글