1. Django Tutorial(Airbnb) - Django Session & save_m2m

🌈 Django Session

🔥 Swich Btn for Session

🔥 Create Room in Host Mode


📌 이 포스팅에서는 Django의 Session으로 사용자의 모드(Guest/Host) 전환을 구현해보겠습니다.

모든 정보를 DB에 저장할 필요는 없습니다. Guest Mode나 Host Mode의 경우 Session을 사용하는게 더 간편합니다. Session은 Django의 Back단에서 정보를 잠시 유지하다가 Logout할 경우 모두 사라집니다. 따라서, 화폐정보(currency)처럼 일시적으로 필요한 정보들은 사실 Model에 field를 만들어 저장하는 것 보다 Session을 이용하는게 더 적절합니다.!


1. Swich Btn for Session

"users/views.py"에서 session을 활용해보도록 하겠습니다. 우선 session은 login된 상태여야하기 때문에 session에 올리거나 내릴 때 모두 login이 필수입니다. 따라서 "login_required" 데코레이션을 사용합니다.

  • 🔎 from django.contrib.auth.decorators import login_required

Host 모드가 요청되면 "start_hosting" 함수가 호출되고, 다시 Guest 모드를 요청하면 "stop_hosting" 함수를 작동시킵니다.

# users/views.py
from django.contrib.auth.decorators import login_required # 👈 "login_required" import
...
...
@login_required
def start_hosting(request): 
    request.session["is_hosting"] = True  # 👈 session에 "is_hosting" 추가
    return redirect(reverse("core:home"))
@login_required
def stop_hosting(request):
    try:
        del request.session["is_hosting"]  # 👈 session에서 "is_hosting" 제거
    except KeyError:
        pass    
    return redirect(reverse("core:home"))

위에 session 전환 기능을 하나의 함수로 기능하게 통합시킬 수 있습니다. 아래처럼 두 개의 경우밖에 없을 때에는 try 구문으로 전환시켜줄 수 있습니다. 즉, session에서 "is_hosting"를 제거하려고 시도하고, KeyError가 발생하면 session에 "is_hosting"이 없기 때문에 추가하는 것이죠. 이 방법은 사용하면, url 경로를 1개만 지정하면 된다는 장점이 있습니다!

from django.contrib.auth.decorators import login_required # 👈 "login_required" import
...
...
@login_required
def switch_hosting(request):
    try:
        del request.session["is_hosting"] # 👈 우선 제거를 시도하고, 
    except KeyError:
        request.session["is_hosting"] = True # 👈 오류가 발생하면 추가
    return redirect(reverse("core:home"))
  • Url과 View는 아래처럼 매핑해주면됩니다. 마치 View의 함수를 toggle 기능처럼 작동시키는 것이죠!
from django.urls import path
from . import views
app_name = "users"
urlpatterns = [
    ...
    ...
    ...    
    path("switch-hosting/", views.switch_hosting, name="switch-hosting"), # 👈 swaich-hosting
]

이제 View와 Url을 매핑하였으니, Template에서 사용자가 클릭한 버튼을 "partials/nav.html"에 생성하겠습니다. 버튼은 swich되기 때문에 session이 추가된 상태일 때는 Host 모드 상태이기 때문에 "Stop hosting" 버튼이 나타나야하고, 추가되지 않은 상태에서는 "Start hosting" 버튼이 나타나야 합니다.
이와 함께 Host 모드일 때는 객실을 생성할 수 있는 "Create Room" 버튼이 나타나도록 하겠습니다.

<ul class="flex items-center text-sm font-medium h-full">
    {% if user.is_authenticated %}
        <li class="nav_link"> # 👈 session 전환 Btn
            <a href="{% url 'users:switch-hosting' %}">
                {% if request.session.is_hosting %} # 👈 session에 "is_hosting"이 존재한다면,,
                    Stop hosting
                {% else %} # 👈 session에 "is_hosting"이 존재하지 않는다면,,
                    Start hosting
                {% endif %}
            </a>
        </li>
        {% if request.session.is_hosting %} # 👈 host mode일 때, "Create Room" bnt 노출 
            <li class="nav_link"><a href="#">Create Room</a></li>
        {% endif %}
        <li class="nav_link"><a href="{{user.get_absolute_url}}">Profile</a></li>
        <li class="nav_link"><a href="{% url "users:logout" %}">Log out</a></li>
    {% else %}
        <li class="nav_link"><a href="{% url "users:login" %}">Log in</a></li>
        <li class="nav_link"><a href="{% url "users:signup" %}">Sign up</a></li>
    {% endif %}    
</ul>



2. Create Room in Host Mode

1) Create Room

Host 모드일 때만 객실을 생성할 수 있는 버튼을 노출할 수 있게 했으니 이를 기능할 수 있게 처리해볼께요. CBV로 만들 때, save() 매서드를 가로채야할 때는 CreateView 보다 FormView 만드는 것이 좋습니다.

class CreateRoomView(user_mixins.LoggedInOnlyView, FormView):
    form_class = forms.CreateRoomForm # 👈 템플릿에 입력을 받기 위한 form입니다.
    template_name = "rooms/room_create.html" # 👈 객실을 생성하기 위해 render할 템플릿입니다.

"room/forms.py"의 "CreateRoomForm"은 ModelForm을 상속받아, 객실 생성을 위한 field를 지정해줍니다.

class CreateRoomForm(forms.ModelForm):
    class Meta:
        model = models.Room
        fields = (
            "name",
            "description",
            "country",
            "city",
            "price",
            "address",
            "guests",
            "beds",
            "bedrooms",
            "baths",
            "check_in",
            "check_out",
            "instant_book",
            "room_type",
            "amenities",
            "facilities",
            "house_rule",
        )

"CreateRoomView"를 작동시킬 Url 경로를 매핑하겠습니다.

from django.urls import path
from . import views
app_name = "rooms"
urlpatterns = [
    path("create/", views.CreateRoomView.as_view(), name="create"), # 👈 CreateRoomView를 연결해줍니다.
    path("<int:pk>/", views.RoomDetail.as_view(), name="detail"),
    path("<int:pk>/edit/", views.EditRoomView.as_view(), name="edit"),
    path("<int:pk>/photos/", views.RoomPhotosView.as_view(), name="photos"),
    path("<int:pk>/photos/add", views.AddPhotoView.as_view(), name="add-photos"),
    path(
        "<int:room_pk>/photos/<int:photo_pk>/delete/",
        views.delete_photo,
        name="delete-photo",
    ),
    path(
        "<int:room_pk>/photos/<int:photo_pk>/edit/",
        views.EditPhotoView.as_view(),
        name="edit-photo",
    ),
    path("search/", views.SearchView.as_view(), name="search"),
]
  • "Create Room" 버튼에 경로를 지정해줍니다.
<ul class="flex items-center text-sm font-medium h-full">
    {% if user.is_authenticated %}
        <li class="nav_link">
            <a href="{% url 'users:switch-hosting' %}">
                {% if request.session.is_hosting %}
                    Stop hosting
                {% else %}
                    Start hosting
                {% endif %}
            </a>
        </li>
        {% if request.session.is_hosting %} # 👇 url을 연결해줄께요:)
            <li class="nav_link"><a href="{% url 'rooms:create' %}">Create Room</a></li>
        {% endif %}        
        <li class="nav_link"><a href="{{user.get_absolute_url}}">Profile</a></li>
        <li class="nav_link"><a href="{% url "users:logout" %}">Log out</a></li>
    {% else %}
        <li class="nav_link"><a href="{% url "users:login" %}">Log in</a></li>
        <li class="nav_link"><a href="{% url "users:signup" %}">Sign up</a></li>
    {% endif %}    
</ul>

2) save_m2m()

이제 form에서 Room Object를 생성 후,, 저장하기 전 가로채서 생성된 Object를 View단으로 전달해보겠습니다.

class CreateRoomForm(forms.ModelForm):
    class Meta:
        model = models.Room
        fields = (
            "name",
            "description",
            "country",
            "city",
            "price",
            "address",
            "guests",
            "beds",
            "bedrooms",
            "baths",
            "check_in",
            "check_out",
            "instant_book",
            "room_type",
            "amenities",
            "facilities",
            "house_rule",
        )
    def save(self, *args, **kwargs):
        room = super().save(commit=False) # 👈 Object를 생성까지만 합니다.
        return room # 👈 그 Object를 View로 return합니다.

FBV에서는 is_valid()를 사용하지만, CBV에서는 form_vaild를 사용해 유효성 검사를 진행시킵니다. 유효하다면, save 매서드를 호출하여 "CreateRoomForm"에서 Object생성 후 저장하기 전 상태를 받아옵니다. 이후 현재 사용자를 host로 지정한 뒤, 저장시킵니다.
마지막으로 저장할 field들 중 Many-to-Mnay field가 있다면,, save_m2m 매서드를 이용해서 저장시킵니다.

class CreateRoomView(user_mixins.LoggedInOnlyView, FormView):
    form_class = forms.CreateRoomForm
    template_name = "rooms/room_create.html"
    def form_valid(self, form):
        room = form.save() # 👈 Form에서 생성된 Object 가져와요:)
        room.host = self.request.user  # 👈 현재 사용자를 host로 지정합니다.
        room.save()  # 👈 저장.
        form.save_m2m() # 👈 Many-to-Many 필드를 저장해줘요:)
        messages.success(self.request, "Room Uploaded")
        return redirect(reverse("rooms:detail", kwargs={"pk": room.pk})) # 👈 생성한 방으로 rediect

좋은 웹페이지 즐겨찾기