1. Django Tutorial(Airbnb) - Update Room Page

ID์งฑ์žฌยท2021๋…„ 8์›” 20์ผ
0

Django

๋ชฉ๋ก ๋ณด๊ธฐ
26/43
post-thumbnail

๐ŸŒˆ Update Room Page

๐Ÿ”ฅ Update Room Page

๐Ÿ”ฅ Delete Photo

๐Ÿ”ฅ Update Caption of Photo

๐Ÿ”ฅ Create Photo



1. Update Room Page

1) Views & Urls

  • ์ˆ˜์ • ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ๋Š” form์ด ์ œ๊ณต๋˜๋Š” ํŽ˜์ด์ง€๊ฐ€ ํ•„์š”ํ•˜๊ฒ ์ฃ . ์ด์— ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ์‹ค ์ •๋ณด ์ˆ˜์ •์„ ์š”์ฒญํ•  ๋•Œ ์‹คํ–‰๋  ๋กœ์ง์„ "EditRoomView"์—์„œ ์ฒ˜๋ฆฌํ•ด๋ณผ๊ป˜์š”. "EditRoomView"๋Š” Django์—์„œ ์ œ๊ณตํ•˜๋Š” "UpdateView"๋ฅผ ์ƒ์†๋ฐ›์•„ CBV๋กœ ๊ตฌํ˜„ํ•˜์˜€์–ด์š”. Django์—์„œ ๋งˆ๋ จํ•ด์ค€ "UpdateView"๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด,, url๋กœ ์ „๋‹ฌ๋œ argument ๊ฐ’์„ ์ž๋™์œผ๋กœ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค.
from django.views.generic import ListView, DetailView, View, UpdateView # ๐Ÿ‘ˆ import
from django.shortcuts import render
from django.core.paginator import Paginator
from . import models, forms
...
...
class EditRoomView(UpdateView): # ๐Ÿ‘ˆ UpdateView ์ƒ์†
    model = models.Room # ๐Ÿ‘ˆ Model ์ง€์ •
    template_name = "rooms/room_edit.html" # ๐Ÿ‘ˆ renderํ•  ํ…œํ”Œ๋ฆฟ ์ง€์ •
    fields = ( # ๐Ÿ‘ˆ ์‚ฌ์šฉํ•  field ์ง€์ •
        "name",
        "description",
        "country",
        "city",
        "price",
        "address",
        "guests",
        "beds",
        "bedrooms",
        "baths",
        "check_in",
        "check_out",
        "instant_book",
        "room_type",
        "amenities",
        "facilities",
        "house_rule",
    )
  • "EditRoomView"๋ฅผ URL ๊ฒฝ๋กœ์™€ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ๊ฐ์‹ค host๋ผ๋ฉด, ์ƒ์„ธ ํŽ˜์ด์ง€์—์„œ "edit" ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ์ˆ˜์ • ํŽ˜์ด์ง€๋กœ ์ง„์ž…ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ ๊ฒฝ๋กœ๋ฅผ Room์˜ pk๊ฐ’์— "/edit"์„ ์ถ”๊ฐ€ํ•˜์—ฌ "EditRoomView"์™€ ๋งคํ•‘ํ–ˆ์Šต๋‹ˆ๋‹ค.
from django.urls import path
from . import views
app_name = "rooms"
urlpatterns = [
    path("<int:pk>/", views.RoomDetail.as_view(), name="detail"),
    path("<int:pk>/edit/", views.EditRoomView.as_view(), name="edit"), # ๐Ÿ‘ˆ Edit ๊ฒฝ๋กœ
    path("search/", views.SearchView.as_view(), name="search"),
]
  • "room_deail.html"์˜ 'edit" ๋ฒ„ํŠผ์„ ์•„๋ž˜์™€ ๊ฐ™์ด ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค. 'edit' ๋ฒ„ํŠผ์€ ํ•ด๋‹น ๊ฐ์‹ค์˜ host์™€ ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ user๊ฐ€ ์ผ์น˜ํ•  ๊ฒฝ์šฐ์—๋งŒ ๋‚˜ํƒ€๋‚  ์ˆ˜ ์žˆ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.
...
...
<div class="w-1/3">
    {% if room.host == user %} # ๐Ÿ‘ˆ ๊ฐ์‹ค์˜ host์™€ user๊ฐ€ ์ผ์น˜ํ•  ๊ฒฝ์šฐ์—๋งŒ ๋ฒ„ํŠผ ์ƒ์„ฑ
        <a href="{% url 'rooms:edit' room.pk %}" class="btn-link block">Edit Room</a>
    {% endif %}
</div>

2) Templates

  • "edit" ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด "EditRoomView"๊ฐ€ ํ˜ธ์ถœ๋˜์–ด "rooms/room_edit.html"์„ renderํ•ฉ๋‹ˆ๋‹ค. "rooms/room_edit.html"๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.
# rooms/room_edit.html
{% extends "base.html" %}
{% block page_title %}
    Update Room
{% endblock page_title %}
{% block search-bar %}
{% endblock search-bar %}
{% block content %}
    <div class="container lg:w-5/12 md:w-1/2 xl:w-1/4 mx-auto my-10 flex flex-col items-center border p-6 border-gray-400">
        {% include 'mixins/room/room_form.html' with form=form cta="Update room" %} # ๐Ÿ‘ˆ form์„ 'mixins/room/room_form.html'๋กœ ์ „๋‹ฌ
    </div>
{% endblock content %}
  • ํ…œํ”Œ๋ฆฟ์˜ ๋ณต์žก๋„๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด, form ์˜์—ญ์„ 'mixins/room/room_form.html'๋กœ include๋กœ ๋ถ„๋ฆฌํ•˜๊ณ , ๋‹ค์‹œ "room_form.html"์—์„œ for๋ฌธ์„ ํ†ตํ•ด ๊ฐ field๋ฅผ "room_input.html"๋กœ ๋ณด๋‚ด ์ฒ˜๋ฆฌํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ include๋ฅผ ํ™œ์šฉํ•˜์—ฌ html ์ฝ”๋“œ์˜ ๋ณต์žก๋„๋ฅผ ์ค„์ด๊ณ  ์—ฌ๋Ÿฌ๊ณณ์—์„œ form์„ ์žฌํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
# mixins/room/room_form.html
<form method="POST" class="w-full" enctype="multipart/form-data">
    {% csrf_token %}
    {% if form.non_field_errors %}
        {% for error in form.non_field_errors %}
            <span class="text-red-700 font-medium text-sm">{{error}}</span> 
        {% endfor %}
    {% endif %}
    {% for field in form %} # ๐Ÿ‘ˆ form์˜ ๊ฐ field๋ฅผ 'mixins/room/room_input.html'๋กœ ์ „๋‹ฌ
        {% include 'mixins/room/room_input.html' with field=field %}
    {% endfor %}
    <button class="btn bg-red-500 text-white">{{cta}}</button>
</form>
# room_input.html
<div class="input w-full {% if field.errors %}has_error{% endif %}">
    {{field.label}} # ๐Ÿ‘ˆ ๊ฐน field์˜ label
    {{field}} # ๐Ÿ‘ˆ ํ•„๋“œ input form
    {% if field.errors %}
        {% for error in field.errors %}
            <span class="text-red-700 font-medium text-sm">{{error}}</span> 
        {% endfor %}
    {% endif %}
</div>

3) Using mixin

  • ์ˆ˜์ •์„ ์œ„ํ•œ ํŽ˜์ด์ง€๋กœ ์ด๋™ ๋ฐ ์ˆ˜์ •์ด ์ž˜ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ์ด ๋ชจ๋“ ๊ฑด "UpdateView"๋ฅผ ์ƒ์†๋ฐ›์•„ CBV๋ฅผ ์ž‘์„ฑํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋‹ค๋งŒ, url์„ ์ง์ ‘ ์ž…๋ ฅํ•ด ๋‹ค๋ฅธ host์˜ ๊ฐ์‹ค ์ˆ˜์ • ํŽ˜์ด์ง€์— ์ ‘๊ทผํ•˜๋ฉด ์ ‘๊ทผ์ด๋˜๋Š” ํ˜„์ƒ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. "LoggedInOnlyView"๋ฅผ ์ด์šฉํ•˜์—ฌ ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด host์ธ ๊ฐ์‹ค ์ •๋ณด์—๋Š” ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
# users/mixin.py
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.contrib import messages
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
class EmailLoginOnlyView(UserPassesTestMixin):
    def test_func(self):
        return self.request.user.login_method == "email"
    def handle_no_permission(self):
        messages.error(self.request, "Can't go there")
        return redirect("core:home")
class LoggedOutOnlyView(UserPassesTestMixin): # ๐Ÿ‘ˆ Loginํ•œ ์‚ฌ์šฉ์ž๋Š” index ํŽ˜์ด์ง€๋กœ ์ด๋™(Logoutํ•œ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ)
    def test_func(self):
        return not self.request.user.is_authenticated 
    def handle_no_permission(self): 
        messages.error(self.request, "Can't go there")
        return redirect("core:home")
class LoggedInOnlyView(LoginRequiredMixin): # ๐Ÿ‘ˆ Loginํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด, login ํŽ˜์ด์ง€๋กœ ์ด๋™
    login_url = reverse_lazy("users:login")
  • "EditRoomView"์— "LoggedInOnlyView"๋ฅผ ์ƒ์†์‹œ์ผœ ์ค๋‹ˆ๋‹ค. ๋‹จ, ์ด ๊ณผ์ •์—์„œ Loginํ•œ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์ธ์ฆ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด View๊ฐ€ ํ‘œ์‹œํ•˜๊ณ  ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” "get_object()"๋ฅผ ํ†ตํ•ด ์˜ค๋ฒ„๋ผ์ด๋“œ ํ•ฉ๋‹ˆ๋‹ค.
from django.http import Http404 # ๐Ÿ‘ˆ import "Http404"
from django.views.generic import ListView, DetailView, View, UpdateView
from django.shortcuts import render
from django.core.paginator import Paginator
from users import mixin as user_mixins # ๐Ÿ‘ˆ import "mixin"
from . import models, forms 
...
...
class EditRoomView(user_mixins.LoggedInOnlyView, UpdateView): # ๐Ÿ‘ˆ "LoggedInOnlyView"
    model = models.Room
    template_name = "rooms/room_edit.html"
    fields = (
        "name",
        "description",
        "country",
        "city",
        "price",
        "address",
        "guests",
        "beds",
        "bedrooms",
        "baths",
        "check_in",
        "check_out",
        "instant_book",
        "room_type",
        "amenities",
        "facilities",
        "house_rule",
    )
    def get_object(self, queryset=None):
        room = super().get_object(queryset=queryset)
        # print(room.host.pk, self.request.user.pk)
        if room.host.pk != self.request.user.pk: # ๐Ÿ‘ˆ ์‚ฌ์šฉ์ž์™€ host๊ฐ€ ์ผ์น˜ํ•˜์ง€์•Š์œผ๋ฉด,,
            raise Http404() 
        return room  # ๐Ÿ‘ˆ ์ผ์น˜ํ•˜๋ฉด ํ•ด๋‹น room Object๋ฅผ ๋ฐ˜ํ™˜   



2. Delete Photo

1) View & Urls

  • ๊ฐ์‹ค ์ •๋ณด์— ์ €์žฅ๋œ ์‚ฌ์ง„์„ ์‚ญ์ œํ•˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์‚ฌ์ง„๋“ค์ด ๋ชฉ๋ก์ฒ˜๋Ÿผ ๋‚˜์—ด๋œ ์ƒํƒœ์—์„œ ์›ํ•˜๋Š” ์‚ฌ์ง„์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์‚ญ์ œํ•˜๊ณ  ์ƒ์„ฑํ•œ๋‹ค๋Š” ๊ฒƒ์ด ๊ฒฐ๊ตญ ์ˆ˜์ •๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์šฐ์„  ์‚ฌ์ง„ ์ •๋ณด๋ฅผ ๋ชฉ๋ก์ฒ˜๋Ÿผ ๋‚˜์—ดํ•˜๊ธฐ ์œ„ํ•ด์„œ "DetailView"๋ฅผ ์ƒ์†๋ฐ›๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด์™€ ํ•จ๊ป˜ 'get_object()'๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ loginํ•œ ์‚ฌ์šฉ์ž์™€ ํ•ด๋‹น ๊ฐ์‹ค์˜ host๊ฐ€ ์ผ์น˜ํ•œ ๊ฒฝ์šฐ์—๋งŒ, Room Object๋ฅผ ํ…œํ”Œ๋ฆฟ ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
from django.http import Http404
from django.views.generic import ListView, DetailView, View, UpdateView
from django.shortcuts import render
from django.core.paginator import Paginator
from users import mixin as user_mixins
from . import models, forms
...
...
class RoomPhotosView(user_mixins.LoggedInOnlyView, DetailView):
    model = models.Room
    template_name = "rooms/room_photos.html"
    def get_object(self, queryset=None):  # ๐Ÿ‘ˆ ํ™”๋ฉด์— ํ‘œ์‹œ๋˜๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ด์š”!
        room = super().get_object(queryset=queryset)
        if room.host.pk != self.request.user.pk:
            raise Http404()
        return room  # ๐Ÿ‘ˆ ๊ฐ์‹ค์˜ host์™€ ์ผ์น˜ํ•œ ๊ฒฝ์šฐ์—๋งŒ Room ๊ฐ์ฒด๋ฅผ ํ…Œํ”Œ๋ฆฌ ๋ณ€์ˆ˜๋กœ ๋ฐ˜ํ™˜ํ•ด์ค๋‹ˆ๋‹ค.
from django.urls import path
from . import views
app_name = "rooms"
urlpatterns = [
    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"), # ๐Ÿ‘ˆ ํ•ด๋‹น ๊ฐ์‹ค์— ๋Œ€ํ•œ ์‚ฌ์ง„ ์ •๋ณด๋ฅผ DetailView๋กœ ๋ณด์—ฌ์ฃผ๋Š” URL์ด์—์š”.
    path("search/", views.SearchView.as_view(), name="search"),
]
  • ์‚ฌ์ง„ ํŒŒ์ผ์„ ์ˆ˜์ •ํ•  ํŽ˜์ด์ง€์˜ ๊ฒฝ๋กœ๋ฅผ ์ƒ์„ฑํ–ˆ์œผ๋‹ˆ, ์ด๊ณณ์œผ๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋ฒ„ํŠผ๋„ ์žˆ์–ด์•ผ๊ฒ ์ฃ . ๊ฐ์‹ค์„ ์ˆ˜์ •ํ•˜๋Š” ํŽ˜์ด์ง€ ๋งจ ์•„๋ž˜ ๋ถ€๋ถ„์— "Edit Photos" ๋ฒ„ํŠผ์„ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.
{% extends "base.html" %}
{% block page_title %}
    Update Room
{% endblock page_title %}
{% block search-bar %}
{% endblock search-bar %}
{% block content %}
    <div class="container lg:w-5/12 md:w-1/2 xl:w-1/4 mx-auto my-10 flex flex-col items-center border p-6 border-gray-400">
        {% include 'mixins/room/room_form.html' with form=form cta="Update room" %}
         <div class="mt-5"> # ๐Ÿ‘‡ ์ด Btn์„ ํ†ตํ•ด photo๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋Š” ํŽ˜์ด์ง€๋กœ ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค:)
             <a href="{% url 'rooms:photos' room.pk %}" class="text-teal-500 font-medium">Edit Photos</a>
         </div>
    </div>
{% endblock content %}
  • Model์—์„œ Photo ํ…Œ์ด๋ธ”์€ ForeignKey๋กœ Room ํ…Œ์ด๋ธ”์„ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— related_name์„ ํ†ตํ•ด ํ•ด๋‹น Room Object๋ฅผ ๊ฐ€๋ฅดํ‚ค๋Š” Photo Object๋“ค์„ ์ „๋ถ€ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์–ด์š”.
{% extends "base.html" %}
{% block page_title %}
    {{room.name}}'s Photos
{% endblock page_title %}
{% block search-bar %}
{% endblock search-bar %}
{% block content %}
    <div class="container mx-auto my-10 flex flex-col p-6">
        {% for photo in room.photos.all %}  # ๐Ÿ‘ˆ related_name ์œผ๋กœ ์ ‘๊ทผ
            <div class="mb-5 border p-6 border-gray-400 flex justify-between">
                <div class="flex items-start">
                    <img src="{{photo.file.url}}" class="w-32 h-32"> # ๐Ÿ‘ˆ image
                    <span class="ml-5 text-xl">{{photo.caption}}</span>  # ๐Ÿ‘ˆ caption
                </div>
                <div class="flex flex-col w-1/5">
                    <a class="btn-link mb-5 bg-teal-500" href="#">Edit</a>  # ๐Ÿ‘ˆ Edit ๋ฒ„ํŠผ
                    <a class="btn-link bg-red-600" href="#">Delete</a> # ๐Ÿ‘ˆ Delete ๋ฒ„ํŠผ
                </div> 
            </div>    
        {% endfor %}
    </div>
{% endblock content %}

2) Delete Photo

  • "Delete" ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ, ๊ฒฝ๋กœ๋Š” Room์˜ pk๋ฟ ์•„๋‹ˆ๋ผ Photo์˜ pk๋„ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • ๐Ÿ”Ž path("<int:pk>/photos/", views.delete_photo, name="delete-photo")
from django.urls import path
from . import views
app_name = "rooms"
urlpatterns = [
    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:room_pk>/photos/<int:photo_pk>/delete/",
        views.delete_photo,
        name="delete-photo",
    ), # ๐Ÿ‘ˆ delete-btn ๊ฒฝ๋กœ
    path("search/", views.SearchView.as_view(), name="search"),
]
  • 'Delete' ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ, Room์˜ PK๊ฐ’๊ณผ Photo์˜ PK๊ฐ’์€ ์–ด๋–ป๊ฒŒ ๊ฐ€์ ธ์˜ฌ๊นŒ์š”? ๋งํฌ์˜ ๊ฒฝ๋กœ์™€ ํ•จ๊ป˜ ์ ์–ด์ฃผ๋˜ ์‚ฌ์ด์— ๊ณต๋ฐฑ์„ ๋„ฃ์–ด์ฃผ๋ฉด 2๊ฐœ์˜ ์ธ์ž๋ฅผ ํ•จ๊ป˜ ์ „๋‹ฌํ•ด์ค๋‹ˆ๋‹ค.
# room_photo.html
<div class="flex flex-col w-1/5">
    <a class="btn-link mb-5 bg-teal-500" href="#">Edit</a>
    <a class="btn-link bg-red-600" href="{% url 'rooms:delete-photo' room.pk photo.pk %}">Delete</a> # ๐Ÿ‘ˆ "room.pk" & "photo.pk" ์ „๋‹ฌ
</div>
  • "@login_required"๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ๋˜์–ด์žˆ์œผ๋ฉด ์ •์ƒ์ ์œผ๋กœ ์•„๋ž˜ ํ•จ์ˆ˜์˜ ๊ธฐ๋Šฅ์„ ํ˜ธ์ถœํ•˜๊ณ , ๋กœ๊ทธ์ธ๋˜์–ด์žˆ์ง€ ์•Š์œผ๋ฉด settings.LOGIN_URL์— ์ง€์ •๋œ ๊ฒฝ๋กœ๋กœ redirect๋ฅผ ์‹œ์ผœ์ค๋‹ˆ๋‹ค. ์ด์— settgins.py์— ์•„๋ž˜ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ• ๊ป˜์š”:)

  • ์‚ญ์ œ ๋˜ํ•œ ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์™€ ํ•ด๋‹น ๊ฐ์‹ค์˜ host์™€ ์ผ์น˜ํ•ด์•ผ์ง€๋งŒ ์ฒ˜๋ฆฌํ•ด์ค„ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

from django.http import Http404
from django.views.generic import ListView, DetailView, View, UpdateView
from django.shortcuts import render, redirect, reverse
from django.core.paginator import Paginator
from django.contrib.auth.decorators import login_required # ๐Ÿ‘ˆ "login_required" import
from django.contrib import messages # ๐Ÿ‘ˆ "messages" import
from users import mixin as user_mixins
from . import models, forms
...
...
class RoomPhotosView(user_mixins.LoggedInOnlyView, DetailView):
    model = models.Room
    template_name = "rooms/room_photos.html"
    def get_object(self, queryset=None):
        room = super().get_object(queryset=queryset)
        if room.host.pk != self.request.user.pk:
            raise Http404()
        return room
@login_required
def delete_photo(request, room_pk, photo_pk):
    # print("Should delete {photo_pk} from {room_pk}") # ๐Ÿ‘ˆ 2๊ฐœ์˜ pk๊ฐ’์ด ์ž˜ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค:)
    user = request.user # ๐Ÿ‘ˆ ํ˜„์žฌ loginํ•œ ์‚ฌ์šฉ์ž๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
    try:
        room = models.Room.objects.get(pk=room_pk)
        if room.host.pk != user.pk: # ๐Ÿ‘ˆ ์‚ฌ์šฉ์ž์˜ pk๊ฐ’๊ณผ room.host์˜ pk๊ฐ’ ์ผ์น˜ ํ™•์ธ
            messages.error(request, "Cant delete that photo")
        else:
            models.Photo.objects.filter(pk=photo_pk).delete() # ๐Ÿ‘ˆ ์‚ญ์ œ
            messages.success(request, "Photo Delete") 
        return redirect(reverse("rooms:photos", kwargs={"pk": room_pk}))
    except models.Room.DoesNotExist: # ๐Ÿ‘ˆ ํ•ด๋‹น Room์ด ์กด์žฌํ•˜์ง€ ์•Š๋‹ค๋ฉด,
        return redirect(reverse("core:home"))


3. Update Caption of Photo

1) UpdateView

  • CBV๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, "UpdateView"๋ฅผ ์ƒ์†๋ฐ›์•„ ์‚ฌ์šฉํ•˜๋ฉด, ๊ฐ„ํŽธํ•˜๊ฒŒ ์ˆ˜์ •์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Photo ๋ชจ๋ธ์˜ "caption" field๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ๋กœ์ง์„ ๊ตฌํ˜„ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์šฐ์„ , CBV๋ฅผ ๋งŒ๋“ค์–ด Photo๋กœ model์„ ์ง€์ •ํ•ด์ฃผ๊ณ , fields ๊ฐ’์œผ๋กœ "caption" ํ•„๋“œ๋ฅผ ์ง€์ •ํ•ด์ค๋‹ˆ๋‹ค.
  • "UpdateView" ๋˜ํ•œ pk๊ฐ’์„ ์ž๋™์œผ๋กœ ์ธ์‹ํ•˜๋Š”๋ฐ photo_pk๊ฐ’์œผ๋กœ argument๋ฅผ ๋ฐ›์•„์˜ค๋ ค๋ฉด "pk_url_kwarg"๋ฅผ ์ด์šฉํ•ด ์ง€์ •ํ•ด์ค˜์•ผ ํ•ด์š”.
    • ๐Ÿ”Ž pk_url_kwarg = "url argument ์ด๋ฆ„"
  • ์ˆ˜์ •์ด ๋˜๋ฉด ์ด๋™ํ•  ๊ณณ์„ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•ด "get_success_url"๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋กœ์ง์ด ํ•„์š”ํ•  ๋•Œ๋Š” ๋งค์„œ๋“œ๋ฅผ ์ด์šฉํ•˜๊ณ , ๋กœ์ง์ด ํ•„์š”์—†์„ ๋•Œ๋Š” "success_url" ์†์„ฑ์œผ๋กœ ์ง€์ •ํ•ด์ค˜๋„ ๋ฉ๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ์—๋Š” url์˜ pk๊ฐ’์„ ์ฒ˜๋ฆฌํ•˜์—ฌ ์ „๋‹ฌํ•ด์ค˜์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋งค์„œ๋“œ๋ฅผ ์ด์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
from django.http import Http404
from django.views.generic import ListView, DetailView, View, UpdateView
from django.shortcuts import render, redirect, reverse
from django.core.paginator import Paginator
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from users import mixin as user_mixins
from . import models, forms
...
...
class EditPhotoView(user_mixins.LoggedInOnlyView, SuccessMessageMixin, UpdateView):
    model = models.Photo
    pk_url_kwarg = "photo_pk"  # ๐Ÿ‘ˆ pk๊ฐ’๋งŒ ์ฐพ๊ณ ์žˆ๊ธฐ ๋–„๋ฌธ์— phpto_pk๊ฐ’์„ ์ฐพ๋„๋ก ์•Œ๋ ค์ค˜์•ผํ•ด์š”:)
    template_name = "rooms/photo_edit.html"
    success_message = "Photo Updated"
    fields = ("caption",) # ๐Ÿ‘ˆ ํŠœํ”Œ ๋˜๋Š” ๋ฆฌ์ŠคํŠธ๋กœ fields๋ฅผ ์ง€์ •ํ•ด์ฃผ์–ด์•ผํ•ด์š”!
    def get_success_url(self):
        room_pk = self.kwargs.get("room_pk") # ๐Ÿ‘ˆ ํ˜„์žฌ url์—์„œ room_pk๊ฐ’์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค:)
        return reverse("rooms:photos", kwargs={"pk": room_pk}) # ๐Ÿ‘ˆ room_pk๊ฐ’์„ url์˜ argument๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
  • View๋ฅผ ๋งŒ๋“ค์—ˆ์œผ๋‹ˆ, URL๋กœ ๋งคํ•‘ํ•ด์ค„๊ป˜์š”.
from django.urls import path
from . import views
app_name = "rooms"
urlpatterns = [
    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: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",
    ), # ๐Ÿ‘ˆ ์ˆ˜์ • ๋ฒ„ํŠผ์˜ url ๊ฒฝ๋กœ์™€ "EditPhotoView"๋ฅผ ์—ฐ๊ฒฐ์‹œํ‚ต๋‹ˆ๋‹ค.
    path("search/", views.SearchView.as_view(), name="search"),
]
  • Edit ๋ฒ„ํŠผ์— ๋งํฌ ์ฃผ์†Œ๋ฅผ ์ง€์ •ํ•ด ์ค๋‹ˆ๋‹ค. "room.pk"๊ณผ "photo.pk"์„ View๋กœ ์ „๋‹ฌํ•ด์ค˜์•ผํ•ฉ๋‹ˆ๋‹ค.
{% extends "base.html" %}
{% block page_title %}
    {{room.name}}'s Photos
{% endblock page_title %}
{% block search-bar %}
{% endblock search-bar %}
{% block content %}
    <div class="container mx-auto my-10 flex flex-col p-6">
        {% for photo in room.photos.all %}
            <div class="mb-5 border p-6 border-gray-400 flex justify-between">
                <div class="flex items-start">
                    <img src="{{photo.file.url}}" class="w-32 h-32">
                    <span class="ml-5 text-xl">{{photo.caption}}</span>
                </div>
                <div class="flex flex-col w-1/5">
       edit ๋ฒ„ํŠผ ๐Ÿ‘‰  <a class="btn-link mb-5 bg-teal-500" href="{% url 'rooms:edit-photo' room.pk photo.pk %}">Edit</a> 
                    <a class="btn-link bg-red-600" href="{% url 'rooms:delete-photo' room.pk photo.pk %}">Delete</a>
                </div>
            </div>    
        {% endfor %}
        <div class="flex justify-center mt-5"> # ๐Ÿ‘ˆ ๋’ค๋กœ๊ฐ€๊ธฐ btn
            <a href="{% url 'rooms:edit' room.pk %}" class="text-teal-500 font-medium text-xl">Back to edit room</a>
        </div>  
    </div>
{% endblock content %}
  • ์ด์ œ View์—์„œ renderํ•  "rooms/photo_edit.html"๋ฅผ ์ž‘์—…ํ•ด์ค„๊ป˜์š”. "rooms/photo_edit.html"๋Š” ์‚ฌ์šฉ์ž๊ฐ€ Photo์˜ caption์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋Š” form์ด ๋‚˜ํƒ€๋‚˜๋Š” ํ…œํ”Œ๋ฆฟ์ด์—์š”. "rooms/photo_edit.html"๋Š” View์—์„œ ์ง€์ •ํ•œ "template_name"์˜ ๊ฐ’์ž…๋‹ˆ๋‹ค.
# rooms/photo_edit.html
{% extends "base.html" %}
{% block page_title %}
    Update Photo
{% endblock page_title %}
{% block search-bar %}
{% endblock search-bar %}
{% block content %}
    <div class="container lg:w-5/12 md:w-1/2 xl:w-1/4 mx-auto my-10 flex flex-col items-center border p-6 border-gray-400"> 
        {% include 'mixins/room/room_form.html' with form=form cta="Update photo" %}
    </div>
{% endblock content %}
  • "Update photo" ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด, View์—์„œ "get_success_url"๊ฐ€ ์‹คํ–‰๋˜์–ด Photo Page๋กœ redirect๋ฉ๋‹ˆ๋‹ค. ์ˆ˜์ •์ด ์™„๋ฃŒ๋ฌ์œผ๋‹ˆ ๋ฐ”๋กœ ์ด์ „ ํ™”๋ฉด์œผ๋กœ ๋ณต๊ท€ํ•˜๋Š” ๊ฒƒ์ด์—์š”:)


4. Create Photo

1) FromView

  • Room Object์— ์‚ฌ์ง„์„ ์ถ”๊ฐ€ํ•˜๋Š” CBV ๋กœ์ง์„ ๋งŒ๋“ค์–ด๋ณผ๊ป˜์š”. ์‚ฌ์ง„์„ ์ˆ˜์ •ํ•  ํ…œํ”Œ๋ฆฟ์€ "rooms/photo_create.html"์—์„œ ๋งŒ๋“ค์–ด์ค„๊บผ์—์š”. ๋˜ํ•œ fields๋กœ "caption" ํ•„๋“œ์™€ "file" ํ•„๋“œ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์ง„์„ ์ถ”๊ฐ€ํ•  ๋•Œ๋Š” CreateView๋ณด๋‹ค FromView๋ฅผ ์ƒ์†๋ฐ›๋Š” ๊ฒƒ์ด ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค. ์ด์— FormView๋ฅผ ์ƒ์†๋ฐ›๊ณ , ์‚ฌ์šฉํ•  form์„ ์ง€์ •ํ•ด์ค„๊ป˜์š”.
from django.db.models import fields
from django.http import Http404
from django.views.generic import ListView, DetailView, View, UpdateView, FormView
from django.shortcuts import render, redirect, reverse
from django.core.paginator import Paginator
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from users import mixin as user_mixins
from . import models, forms
...
...
class AddPhotoView(user_mixins.LoggedInOnlyView, FormView):
    model = models.Photo
    template_name = "rooms/photo_create.html" # ๐Ÿ‘ˆ renderํ•  template
    fields = ("caption", "file") # ๐Ÿ‘ˆ ์‚ฌ์šฉํ•  fields
    form_class = forms.CreatePhotoForm # ๐Ÿ‘ˆ Form ์ง€์ •
  • URL์„ ๋งคํ•‘ํ•ด๋ณผ๊ป˜์š”. ํ•ด๋‹น ๊ฐ์‹ค์— ๋Œ€ํ•œ pk๊ฐ’์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. Photo๊ฐ€ ์–ด๋–ค ๊ฐ์‹ค์— ์ƒ์„ฑ๋˜์–ด์•ผํ• ์ง€ ์•Œ์•„์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์ด์ฃ .
from django.urls import path
from . import views
app_name = "rooms"
urlpatterns = [
   ...
   ...
    path("<int:pk>/photos/add", views.AddPhotoView.as_view(), name="add-photos"),
   ...
   ...
  • "room_photos.html"์— ์‚ฌ์ง„์„ ์—…๋กœ๋“œํ•˜๋Š” ๋ฒ„ํŠผ์„ ์ƒ์„ฑํ• ๊ป˜์š”. ์ˆ˜์ •ํ•  ๊ฐ์‹ค์— pk๊ฐ’์ด ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฒฝ๋กœ์™€ ํ•จ๊ป˜ ์ถ”๊ฐ€ํ•ด์ฃผ๊ณ  ์‚ญ์ œ ๋ฒ„ํŠผ ์œ—๋ถ€๋ถ„์— ์œ„์น˜ํ•˜๋„๋ก ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์˜€์–ด์š”.
# room_photos.html
<div class="my-10 w-full"> # ๐Ÿ‘ˆ ์›ํ•˜๋Š” ์œ„์น˜์— ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”:)
    <a href="{% url 'rooms:add-photo' room.pk %}"" class="btn-link w-1/6 block">Upload Photo</a>
</div>

  • ์ˆ˜์ • ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด "rooms/photo_create.html" ํ…œํ”Œ๋ฆฟ์ด render๋ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด "rooms/photo_create.html" ๊ฐ„๋‹จํžˆ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.
# photo_create.html
{% extends "base.html" %}
{% block page_title %}
    Upload photo
{% endblock page_title %}
{% block search-bar %}
{% endblock search-bar %}
{% block content %}
    <div class="container lg:w-5/12 md:w-1/2 xl:w-1/4 mx-auto my-10 flex flex-col items-center border p-6 border-gray-400">
        {% include 'mixins/room/room_form.html' with form=form cta="Upload photo" %}
    </div>
{% endblock content %}

2) get pk in Form

  • ModelForm์„ ์ƒ์†๋ฐ›์•„ model๊ณผ field๋ฅผ ์ง€์ •ํ•ด์ฃผ๊ณ , save() ๋งค์„œ๋“œ๋ฅผ ๊ฐ€๋กœ์ฑ„๋„๋ก ํ• ๊ฒŒ์š”. "commit=False"๋Š” Object๋ฅผ ์ƒ์„ฑํ•˜์ง€๋งŒ ์ €์žฅํ•˜์ง„ ์•Š์Šต๋‹ˆ๋‹ค. save() ๋งค์„œ๋“œ๋ฅผ ๊ฐ€๋กœ์ฑ„๋Š” ์ด์œ ๋Š” Photo Model์˜ field ์ค‘ "room"์ด ForeignKey๋กœ Room Object๋ฅผ ๊ฐ€๋ฅดํ‚ค๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ํ•จ๊ป˜ ๋„ฃ์–ด DB์— ์ €์žฅํ•ด์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์ด์—์š”.
# rooms/forms.py
from django import forms
from django_countries.fields import CountryField
from . import models
...
...
class CreatePhotoForm(forms.ModelForm):
    class Meta:
        model = models.Photo
        fields = ("caption", "file")
    def save(self, *args, **kwargs):
        photo = super().save(commit=False)
  • Room Object์— ๋Œ€ํ•œ pk๊ฐ’์„ room ํ•„๋“œ์— ์ €์žฅํ•ด์ค˜์•ผํ•˜๋Š”๋ฐ, "CreatePhotoForm"์—์„œ๋Š” pk๊ฐ’์„ ์•Œ์•„ ๋‚ผ ์ˆ˜ ์—†์–ด์š”. View์—์„œ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— form_valid() ๋งค์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด View์˜ pk๊ฐ’์„ "CreatePhotoForm"์˜ save()๋งค์„œ๋“œ๋กœ ์ „๋‹ฌ์‹œ์ผœ ์ค„๊ฒŒ์š”:)
...
...
class AddPhotoView(user_mixins.LoggedInOnlyView, FormView):
    model = models.Photo
    template_name = "rooms/photo_create.html"
    fields = ("caption", "file")
    form_class = forms.CreatePhotoForm
    def form_valid(self, form): # ๐Ÿ‘ˆ form_valie() ๋งค์„œ๋“œ ์‚ฌ์šฉ
        pk = self.kwargs.get("pk") # ๐Ÿ‘ˆ pk๊ฐ’์„ queryset์—์„œ ๊ฐ€์ ธ์™€,,
        form.save(pk) # ๐Ÿ‘ˆ form์˜ save() ๋งค์„œ๋“œ์— ๋„˜๊ฒจ์ค๋‹ˆ๋‹ค.
  • ์ด์ œ CreatePhotoForm ๋‚ด์—์„œ save() ๋งค์„œ๋“œ๋ฅผ ํ†ตํ•ด pk๊ฐ’์„ ์ธ์ž๋กœ ๋ฐ›์•„์™€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
# rooms/forms.py
from django import forms
from django_countries.fields import CountryField
from . import models
...
...
class CreatePhotoForm(forms.ModelForm): # ๐Ÿ‘ˆ ModelForm์œผ๋กœ ๋งŒ๋“ค๊ป˜์š”:)
    class Meta:
        model = models.Photo
        fields = ("caption", "file")
    def save(self, pk, *args, **kwargs):
        photo = super().save(commit=False)
        # print(pk) # ๐Ÿ‘ˆ View์—์„œ ์ „๋‹ฌ๋œ pk๊ฐ’์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”.
        room = models.Room.objects.get(pk=pk) # ํ•ด๋‹น pk๊ฐ’์˜ Room Object๋ฅผ ๊ฐ€์ ธ์™€์š”.
        photo.room = room # ๐Ÿ‘ˆ ์ƒ์„ฑ๋œ Object์˜ room field๊ฐ’์„ ์ง€์ •ํ•ด์ค๋‹ˆ๋‹ค.
        photo.save()  # ๐Ÿ‘ˆ ๊ทธ๋ฆฌ๊ณ  ์ €์žฅ.

  • ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ์ด์œ ๋Š” "AddPhotoView"์—์„œ form_valid() ๋งค์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, HttpResponse๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์‚ฌ์ง„์„ ์ƒ์„ฑ ํ›„ ์–ด๋””๋กœ ์ด๋™ํ• ์ง€ ์ง€์ •ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋–„๋ฌธ์ด์ฃ . ์ด์— ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ๋๋‚˜ ๋กœ์ง์ด ๋ฌธ์ œ ์—†์ด ์ฒ˜๋ฆฌ๋˜๋ฉด, redirect ์‹œ์ผœ์ค๋‹ˆ๋‹ค.
    • ๐Ÿ”Ž return redirect(reverse("rooms:photos", kwargs={"pk": pk}))
...
...
class AddPhotoView(user_mixins.LoggedInOnlyView, FormView):
    model = models.Photo
    template_name = "rooms/photo_create.html"
    fields = ("caption", "file")
    form_class = forms.CreatePhotoForm
    def form_valid(self, form):
        pk = self.kwargs.get("pk")
        form.save(pk)
        messages.success(self.request, "Photo Uploaded")
        return redirect(reverse("rooms:photos", kwargs={"pk": pk}))
profile
Keep Going, Keep Coding!

0๊ฐœ์˜ ๋Œ“๊ธ€