12. DetailView

조재훈·2022년 7월 25일
0

Clone_Airbnb

목록 보기
26/31
post-thumbnail

이제 목록에 떠있는 방을 클릭해서 그 방 페이지로 가는걸 구현할 것이다.

1) view 생성

templates - rooms 폴더에 detail.html을 생성한다.

{% extends "base.html" %}


{% block page_name %}
    Home
{% endblock page_name %}


{% block content %}

{% endblock content %}

rooms - views.py

from django.shortcuts import render

...

def room_detail(request):
    
    render(request, "rooms/detail.html")

이제 url을 등록해야하는데 core가 아니라 rooms에 urls.py를 만들도록 한다. 그리고 기본적인 뼈대는 config - urls.py에서 베껴오고 rooms에 맞게 수정해준다.
rooms - urls.py

from django.urls import path
from . import views

app_name = "rooms"

urlpatterns = [path("1", views.room_detail, name="detail")]

이제 이거를 config에 등록해준다.
config - urls.py

from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static


urlpatterns = [
    path("", include("core.urls", namespace="core")),
    path("rooms/", include("rooms.urls", namespace="rooms")),
    path("admin/", admin.site.urls),
]


if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)



URL에 rooms 뒤로 숫자 붙이면 페이지가 뜬다. 이 숫자들은 우리가 가고자 하는 room의 id가 될 것이다. 공식 문서에서 URL dispatcher를 보면 url에 숫자를 집어넣을 수 있다.
https://docs.djangoproject.com/en/4.0/topics/http/urls/

이런 것들을 path로 넣을 수 있다고 한다.

views의 room_detail에서 pk를 출력해보자
rooms - urls.py

urlpatterns = [path("<int:pk>", views.room_detail, name="detail")]

rooms - views.py

def room_detail(request, pk):
    print(pk)
    return render(request, "rooms/detail.html")



path() 함수는 route, view를 인자로 받는다. route는 URL 패턴을 포함한 문자열이어야 하며, view에 argument를 전달하기 위해 꺽쇠(<>, brackets)를 사용할수도 있다.
view는 Class Based View에서 view함수나 as_view()의 결과를 의미한다.
따라서 pk 키워드가 포함된 문자열을 url로 넘겨주게 되고 이를 view에 명시된 함수로 넘겨주게 된다. 여기서는 view.py에 있는 room_detail 함수로 넘어가게 된다.

path안에 포함되는 것 중 include()함수가 있다. module을 인자로 받고 namespace를 선택으로 넣을 수 있다. module에는 모듈 이름을 기재하고 namespace는 이 module을 상징하는 이름을 뜻한다. 전체 주소를 일일이 넣어주는 대신 그 주소를 상징하는 단어로 퉁치는건데 자세한건 아래에.

일단 각 room마다 링크를 생성해보자. 정석대로라면 아래와 같이 주소를 적어주어야 할 것이다.
templates - rooms - room_list.html

    {% for room in rooms %}
        <h1>
            <a href="/rooms/{{room.pk}}">
                {{room.name}} / ${{room.price}}
            </a>
        </h1>
    {% endfor %}


첫번째를 클릭하면 아래와 같이 페이지가 들어가진다.

그런데 문제는 저 주소를 매번 치기가 좀 짜증난다는 것이다. 그래서 이걸 대체할 문자를 지정하는데 이게 바로 url tag와 namespace이다. 동일한 기능을 아래와 같이 구현할 수 있다.

        {% for room in rooms %}
        <h1>
            <a href="{% url 'rooms:detail' %}">
                {{room.name}} / ${{room.price}}
            </a>
        </h1>
    {% endfor %}

/rooms/를 namespace가 rooms인 url tag로 대체하였다. 그리고 이어서 (:) rooms.urls에서 <int:pk>로 들어가는 세부 url을 name이 detail인 url tag로 대체하였다.
보니까 detail이 argument가 없다는 에러가 뜬다.

url tag 문서에는 url이름과 argument를 넣어야 한다. argument는 따옴표로 강조된 문자열일수도 있고 아니면 context 변수일수도 있다. 추가적인 변수는 선택이며 스페이스로 구분한다.
https://docs.djangoproject.com/en/4.0/ref/templates/builtins/#url

아래와 같이 수정해주자.

    ...
    
    {% for room in rooms %}
        <h1>
            <a href="{% url 'rooms:detail' room.pk %}">
                {{room.name}} / ${{room.price}}
            </a>
        </h1>
    {% endfor %}room.pk %}">
    
    ...



따라서 템플릿도 유사하게 수정할 수 있다.
templates - partials - header.html
기존)

<header>
    <a href="/">Hairbnb</a>
    <ul>
        <li><a href="#">Login</a></li>
    </ul>
</header>

변경)

<header>
    <a href="{% url 'core:home' %}">Hairbnb</a>
    <ul>
        <li><a href="#">Login</a></li>
    </ul>
</header>

2) get_absolute_url

admin에서 model을 수정하다가 이게 실제 웹페이지에서 어떻게 보이는지 알고싶다면 해당 모델의 pk를 복사해서 주소에 붙여넣고를 반복해야한다. 그걸 그 자리에서 바로 확인하는 방법이 있다.
현재

해당 모델에 get_absolute_url을 추가해보았다.
rooms - models.py - Room

    def get_absolute_url(self):
        return "/potato"

우측에 VIEW ON SITE가 생겼다.

주소가 potato이기 때문에 제대로 뜨진 않는다.

그럼 이를 이용해서 실제 해당 페이지로 이동할수도 있다. 이전에 했던 url tag를 이용할건데 우선 reverse 함수를 import해주자.

from django.urls import reverse

그리고 get_absolute_url 함수는 아래와 같이 한다. namespace와 이후 붙을 name을 차례대로 입력한다.

    def get_absolute_url(self):
        return reverse("rooms:detail")

하지만 충분치 않다. 앞서 경우와 마찬가지로 argument가 필요하다.

여기서는 그 argument를 kwargs를 이용해서 넣어줄 것이다.

    def get_absolute_url(self):
        return reverse("rooms:detail", kwargs={'pk':self.pk})

예압

3) Room 정보 연동

세부 페이지는 마련해줬으니 이제 거기에다가 해당 방의 정보를 띄워줘야한다.
rooms - views.py

def room_detail(request, pk):
    room = models.Room.objects.get(pk=pk)
    print(room)
    return render(request, "rooms/detail.html")

콘솔에 아래와 같이 뜬다.

이제 이걸 context 변수로 추가하자.

def room_detail(request, pk):
    room = models.Room.objects.get(pk=pk)
    print(room)
    return render(request, "rooms/detail.html", context={"room": room})

views에서 context로 변수를 넘겨줬으니 이걸 이제 html에서 쓸 수 있게 된다.
templates - rooms - detail.html

{% extends "base.html" %}


{% block page_name %}
    Home
{% endblock page_name %}


{% block content %}
    {{room.name}}
{% endblock content %}


다른 정보도 계속 입력해보자.

{% extends "base.html" %}


{% block page_name %}
    Home
{% endblock page_name %}


{% block content %}
    <div>
        <h1>{{room.name}}</h1>
        <h3>{{room.description}}</h3>
    </div>
    <div>
        <h2>By : {{room.host.username}}
            {% if room.host.superhost %}
                (Superhost)
            {% endif %}
        </h2>
        <h3>Amenities</h3>
        <ul>
            {% for a in room.amenities.all %}
                <li>{{a}}</li>
            {% endfor %}
        </ul>
        <h3>Facilities</h3>
        <ul>
            {% for f in room.facilities.all %}
                <li>{{f}}</li>
            {% endfor %}
        </ul>
        <h3>House Rule</h3>
        <ul>
            {% for r in room.house_rules.all %}
                <li>{{r}}</li>
            {% endfor %}
        </ul>
    </div>
{% endblock content %}



그리고 url에 존재하지 않는 pk값을 넣어서 접속을 시도하는 경우를 방지하려 한다.
rooms - views.py

from django.views.generic import ListView
from django.urls import reverse
from django.shortcuts import render, redirect
from . import models


class HomeView(ListView):

    """HomeView Definition"""

    model = models.Room
    paginate_by = 10
    paginate_orphans = 5
    ordering = "created"
    context_object_name = "rooms"


def room_detail(request, pk):
    try:
        room = models.Room.objects.get(pk=pk)
        return render(request, "rooms/detail.html", context={"room": room})
    except models.Room.DoesNotExist:
        return redirect(reverse("core:home"))

이제 url에 pk값을 아무거나 넣으면

메인 화면으로 튕긴다.

4) 404 page

이상한 url을 메인으로 튕기지 말고 페이지 못찾았다는 에러를 띄워주도록 하자.
rooms - views.py

from django.views.generic import ListView
from django.http import Http404
from django.shortcuts import render
from . import models

    ...
    
    def room_detail(request, pk):
    try:
        room = models.Room.objects.get(pk=pk)
        return render(request, "rooms/detail.html", context={"room": room})
    except models.Room.DoesNotExist:
        raise Http404()

참고로 에러는 return이 아니라 raise를 해야한다.

현재 settings에서 debug가 true로 되어있어서 저런데 false로 바꿔보자.
config - settings.py

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

ALLOWED_HOSTS = ["*"]


근데 이게 마음에 안들면 별도로 html을 만들어줘도 된다....고 하는데 별도로 만들어줘도 안뜬다. 이유가 뭐지...
templates에 파일을 만들어두면 알아서 찾는다는데....
templates - rooms - 404.html

{% extends "base.html" %}

{% block content %}
<h1>Not Found Baby</h1>
{% endblock content %}

5) DetailView

room_detail을 바꿔보자.
rooms - views.py

...

class RoomDetail(DetailView):
    
    pass

rooms - urls.py

...

urlpatterns = [path("<int:pk>", views.RoomDetail.as_view(), name="detail")]

일단 된다. 근데 QuerySet이 없다고 한다.

model을 설정해주자
rooms - views.py

...

class RoomDetail(DetailView):
    """RoomDetail Definition"""

    model = models.Room

CBV로 하다보니 이제 이게 알아서 이름을 지어놓은 템플릿을 찾는다. 우리가 detail.html로 지어놓은 파일 이름을 room_detail.html로 바꿔주자.


오 ㅋ

장고의 DeailView는 기본 argument로 pk를 찾는다. urlpatterns의 path에 설정해놓은 <int:pk>를 <int:potato>로 바꿔보자.

RoomDetail은 반드시 object pk나 URLconf의 slug중 하나로 호출되어야 한다고 써있다.
CCBV에서 DetailView를 검색해보면 기본 검색 키워드가 pk로 아예 지정이 되어있다.

그리고 말같지도 않은 주소를 치면 내가 별도로 설정하지 않았는데도 404 페이지로 보내버린다.

profile
맨땅에 헤딩. 인생은 실전.

0개의 댓글