1. Django Tutorial(Airbnb) - 즐겨찾기 기능 구현

ID짱재·2021년 8월 31일
1

Django

목록 보기
30/43
post-thumbnail

🌈 즐겨찾기 기능 구현

🔥 templatetags 기능로 toggle 구현

🔥 get_or_create 매서드



1. templatetags 기능로 toggle 구현


🤔 toggle Btn 만들기

✔️ 사용자가 객실의 즐겨찾기 버튼을 누르면, "List" Model에 해당 객실 정보가 저장되는 즐겨찾기 기능을 구현해보겠습니다.
✔️ "List" 모델을 "OneToOneField"와 "ManyToManyField"로 필드를 만들어 주었습니다.

  • name 필드 : 즐겨찾기에 저장될 Object의 이름입니다.
  • user 필드 : OneToOneField를 통해 1:1 관계로 사용자 테이블 참조합니다.
  • room 필드 : ManyToManyField를 통해 다대다 관계로 객실을 테이블을 참조합니다.
from django.db import models
from core import models as core_models
# Create your models here.
class List(core_models.TimeStampedModel):
    """List Model Definition"""
    name = models.CharField(max_length=80) # 👈 간단한 name 필드에요.
    user = models.OneToOneField(
        "users.User", related_name="list", on_delete=models.CASCADE
    ) # 👈 OneToOne 필드는 1:1 관계를 가집니다.
    room = models.ManyToManyField(
        "rooms.Room",
        related_name="list",
        blank=True
    )  # 👈 여러 객실을 즐겨찾기할 수 있기 때문에 ManyToManyField 필드입니다.
    def __str__(self):
        return self.name
    def count_rooms(self):
        return self.room.count()
    count_rooms.short_description = "Number of Rooms"


🤔 탬플릿 태그 만들기

✔️ 모델을 확인했으니, Template Tag를 만들어 보겠습니다. 이 Template Tag의 역할은 현재 객실이 사용자의 List 모델에 이미 저장되어있으면 True, 존재하지 않으면 False를 반환해줍니다.
✔️ List 모델에 해당 객실이 존재하는지 여부를 확인하는 이유는 이 판단에 따라 "즐겨찾기 추가", "즐겨찾기 제거" 버튼을 나타내주기 위함입니다.
✔️ 템플릿 태그를 만드는 방법은 템플릿 필터를 만드는 방법과 유사합니다. 사용할 app 내에 templatetags 디렉토리를 생성 후,__init__.py과 작업할 파일(on_fave.py) 생성해 주면 됩니다.
✔️ 탬플릿 태그를 만들 때는 simple_tag에 등록해 줍니다. 이와 함께 takes_context=True를 parameter로 전달해 요청한 사용자의 정보를 가져올 수 있습니다. 이 경우에는 만들어줄 함수의 첫번째 인자로 context를 받아와야 합니다.
✔️ user와 name 기준으로 get_or_none()을 사용해서 QuerySet에서 Object를 가져옵니다. 존재한다면, 해당 List Object에서 모든 room 중에서 현재의 room이 존재하는지 찾아 bool 값을 return해주면 됩니다.

# lists/templatetags/on_fave.py
from django import template # 👈 "template" import
from lists import models as list_models
register = template.Library()  # 👈 Library() 에 등록합니다.
@register.simple_tag(takes_context=True) 
def on_favs(context, room):  # 👈 takes_context=True이기 때문에 첫번째 인자는 context를 받아야해요:)
    # print(context.request, room)
    user = context.request.user
    the_list = list_models.List.objects.get_or_none(
        user=user, name="My Favourites Houses"
    )
    return room in the_list.room.all() # 👈 True or False 반환

✔️ 즐겨찾기와 관련한 경로를 매핑해줍니다.
✔️ "toggle_room"은 해당 객실을 List 모델에 저장하기 위해 "room_pk"값을 argument로 전달합니다.
✔️ "SeeFavsView"는 사용자가 즐겨찾기 한 객실 목록으로 이동 시켜줍니다.

from django.urls import path
from . import views
app_name = "lists"
urlpatterns = [
    path("toggle/<int:room_pk>", views.toggle_room, name="toggle-room"),  # 👈 즐겨찾기 저장
    path("favs/", views.SeeFavsView.as_view(), name="see-favs"),  # 👈 즐겨찾기 목록 제공
]

🤔 toggle button

✔️ "toggle btn"은 객실 상세보기(room_detail.html)에서 templatetag를 사용하며 만들어줍니다.
✔️ 이미 load 중인 항목이 있다면 아래처럼 연이어 입력해 load해 올 수 있습니다.

  • 🔎 {% load is_booked on_favs i18n %}

✔️ 위에서 만든 "on_favs" 템플릿 태그를 사용할 때, 함수로 전달할 변수가 있다면 아래 처럼 옆에 입력해줍니다.

  • 🔎 {% on_favs room as on_favs_boolean %}

✔️ 즉, "on_favs" 함수의 반환 값에 따라 "즐겨찾기 제거(Remove) 버튼" 또는 "즐겨찾기 저장(Save) 버튼"이 Html에 나타납니다.

# templates/rooms/room_detail.html
{% extends "base.html" %}
{% load is_booked on_favs i18n %}
{% block page_title %}
    {{room.name}}
{% endblock page_title %}
{% block content %}
...
...
{% on_favs room as on_favs_boolean %} # 👈 on_fave 함수에 room 을 전달합니다.
    {% if on_favs_boolean %}
        <a class="block mb-10 w-2/6 text-teal-600 font-bold" href="{% url 'lists:toggle-room' room.pk %}?action=remove">{% trans "Remove from Favourites" %}</a> # True일 때,, 👈 action값에 remove를 넣어줍니다.
    {% else %}
        <a class="block mb-10 w-2/6 text-teal-600 font-bold" href="{% url 'lists:toggle-room' room.pk %}?action=add">{% trans "Save to Favourites" %}</a>  # 👈 False일 때, action값에 add를 넣어줍니다.
    {% endif %}
...
...
{% endblock %}


2. get_or_create 매서드


🤔 get_or_create 사용법

✔️ a태그에서 설정해둔 action값을 통해 Url에서 어떤 toggle 버튼이 눌렸는지 View에서 그 값을 확인할 수 있습니다.

  • 이미 List 모델에 저장된 Object 일 때 : /lists/toggle123?action=remove
  • 이미 List 모델에 저장된 Object 가 아닐 때 : /lists/toggle123?action=add

✔️ "get_or_create" 매서드는 튜플 형태로 생성된 객체(Object)와 생성 여부(Create)를 반환해줍니다. List 모델의 room 필드는 ManyToMnayfield이기 때문에 add를 통해 값을 추가하거나 remove를 통해 값을 제거할 수 있습니다.
✔️ 아래에서는 the_list가 생성된 Object이고, 언더바가 Bool값을 지닌 Create의 여부를 담고 있습니다. 또한 "get_or_create"는 save()를 하지 않아도 자동으로 save() 매서드를 호출해줍니다.

✔️ 즐겨찾기 목록 페이지를 위한 CBV는 TemplateView를 상속받아 간단하게 구현해주었습니다.

from django.shortcuts import redirect, reverse
from django.views.generic import TemplateView
from rooms import models as room_models
from . import models
def toggle_room(request, room_pk):
    action = request.GET.get("action", None) # 👈 action값 추출
    room = room_models.Room.objects.get_or_none(pk=room_pk) # 👈 Room Object 가져오기
    if room is not None and action is not None:
        the_list, _ = models.List.objects.get_or_create(
            user=request.user, name="My Favourites Houses"
        ) # 👈 List Object 생성
        if action == "add": # 👈 추가
            the_list.room.add(room)
        elif action == "remove": # 👈 제거
            the_list.room.remove(room)
    return redirect(reverse("rooms:detail", kwargs={"pk": room_pk}))
class SeeFavsView(TemplateView): # 👈 즐겨찾기 목록을 TemplateView를 상속하여 보여줍니다.
    template_name = "lists/list_detail.html"

🤔 즐겨찾기 목록 페이지 구현

✔️ nav.html에 "favs" 버튼을 만들어 즐겨찾기에 저장한 목록이 표시되도록 하였어요.

  • 🔎 <li class="nav_link"><a href="{% url 'lists:see-favs' %}">{% trans "Favs" %} ({{user.list.room.count}})</a></li>

✔️ related_name 값인 list를 이용하여, 해당 사용자의 List Object로 접근하여 모든 room field값을 화면에 출력합니다.

  • 🔎 {% for room in user.list.room.all %}
{% extends "base.html" %}
{% block page_title %}
    {{user.first_name}}'s Fav
{% endblock page_title %}
{% block content %}
    <div class="min-h-75vh">
        <h3 class="mb-12 text-2xl text-center">Your Favourites</h3>
    <div class="container mx-auto pb-10 ">
        <div class="flex flex-wrap -mx-40 mb-10">
            {% for room in user.list.room.all  %} # 👈 related_name으로 접근
                {% include 'mixins/room_card.html' with room=room %}
            {% endfor %}
        </div>
    </div>
    </div>
{% endblock content %}

profile
Keep Going, Keep Coding!

0개의 댓글