Django์๋ CBV, FBV ๋ ์ข ๋ฅ์ ๋ทฐ๊ฐ ์กด์ฌํ๋ค. ํนํ ํด๋์ค ๊ธฐ๋ฐ ๋ทฐ๋ ์ฅ๊ณ ์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ๋ ์ ๋ค๋ฆญ ๋ทฐ๋ฅผ ์์ํด์ ์ฌ์ฉํ๋ฉด ์ฌ๋ฌ๊ฐ์ง ๊ธฐ๋ฅ์ ๋งค์ฐ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
๋๋ ๋๋ถ๋ถ ํจ์ ๊ธฐ๋ฐ ๋ทฐ๋ก๋ง ์์ฑ์ ํด์๋๋ฐ CBV๋ฅผ ์ ํ์ฉํ ์ฝ๋๊ฐ ํจ์ฌ ๊น๋ํ๊ณ ์ง๊ด์ ์ด๋ผ ๋๊ปด์ ธ ์ต๋ํ ์ฅ๊ณ ์์ ์ง์ํด์ฃผ๋ ๋ทฐ๋ฅผ ์ ์ฌ์ฉํด๋ณด๊ธฐ๋ก ํ๋ค.
์ด๋ฒ ํฌ์คํ
์ Two Scoops of Django
10์ฅ์ ์ฝ๊ณ ์ค์ํ ๋ด์ฉ์ ์ ๋ฆฌํ ๊ธ์ด๋ค. ๋ ์์ธํ ๋ด์ฉ์ ์ฑ
์ ์ฐธ๊ณ ํ์๋ฉด ์ข๋ค.
์ฅ๊ณ ๋ ํด๋์ค ๊ธฐ๋ฐ ๋ทฐ๋ฅผ ์์ฑํ๋ ํ์คํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ค.
ํจ์ ๊ธฐ๋ฐ ๋ทฐ์์๋ ๋ทฐ ํจ์ ์์ฒด๊ฐ ๋ด์ฅ ํจ์์ด๊ณ , ํด๋์ค ๊ธฐ๋ฐ ๋ทฐ์์๋ ๋ทฐ ํด๋์ค๊ฐ ๋ด์ฅ ํจ์๋ฅผ ๋ฐํํ๋ as_view()
ํด๋์ค ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ค.
django.views.generic.View
์์ ํด๋น ๋ฉ์ปค๋์ฆ์ด ๊ตฌํ๋๋ฉฐ ๋ชจ๋ ํด๋์ค ๊ธฐ๋ฐ ๋ทฐ๋ ์ด ํด๋์ค๋ฅผ ์ง๊ฐ์ ์ ์ผ๋ก ์์๋ฐ์ ์ด์ฉํ๋ค.
๋ํ ์ฅ๊ณ ๋ ์์ฆ ๋๋ถ๋ถ์ ์น ํ๋ก์ ํธ์์ ์ด์ฉ๋๋ ์ ๋ค๋ฆญ ํด๋์ค ๊ธฐ๋ฐ ๋ทฐ(GGBV)๋ฅผ ์ ๊ณตํ๋ฉฐ, ๊ทธ ์ฅ์ ์ ์ต๋ํ ์ด๋ฆฌ๊ณ ์๋ค.
์ฅ๊ณ ์ ๊ธฐ๋ณธํ์ ๋ณด๋ฉด ์ ๋ค๋ฆญ ํด๋์ค ๊ธฐ๋ฐ ๋ทฐ๋ฅผ ์ํ ์ค์ํ ๋ฏน์ค์ธ๋ค์ด ๋น ์ ธ ์๋ค. ํ์ง๋ง
django-braces
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํจ์ผ๋ก์จ ์ด๋ฐ ๋ถ๋ถ๋ค์ ํด๊ฒฐํ ์ ์๋ค.django-braces
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ฅ๊ณ ์ ์ ๋ค๋ฆญ ํด๋์ค ๊ธฐ๋ฐ ๋ทฐ๋ฅผ ๋งค์ฐ ์ฝ๊ณ ๋น ๋ฅด๊ฒ ๊ฐ๋ฐํ๊ธฐ ์ํ ๋ช ํํ ๋ฏน์ค์ธ๋ค์ ์ ๊ณตํ๊ณ ์๋ค.
ํ๋ก๊ทธ๋๋ฐ์์๋ ๋ฏน์ค์ธ์ด๋ ์ค์ฒดํ๋ ํด๋์ค๊ฐ ์๋๋ผ ์์ํด ์ค ๊ธฐ๋ฅ๋ค์ ์ ๊ณตํ๋ ํด๋์ค๋ฅผ ์๋ฏธํ๋ค. ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์์ ๋ค์ค ์์์ ํด์ผ ํ ๋ ๋ฏน์ค์ธ์ ์ฐ๋ฉด ํด๋์ค์ ๋ ๋์ ๊ธฐ๋ฅ๊ณผ ์ญํ ์ ์ ๊ณตํ ์ ์๋ค.
๋ฏน์ค์ธ์ ์ด์ฉํด์ ๋ทฐ ํด๋์ค๋ฅผ ์ ์ํ ๋ ์ผ๋ค์ค ๋ฌ๋ธ๊ฐ ์ ์ํ ์์์ ๊ดํ ๊ท์น๋ค์ ๋ฐ๋ฅด๊ธฐ๋ก ํ์.
from django.views.generic import TemplateView
class FreshFruitMixin(object):
def get_context_data(self, **kwargs):
context = super(FreshFruitMixin, self).get_context_data(**kwargs)
context['has_fresh_fruit'] = True
return context
class FruityFlavorView(FreshFruitMixin, TemplateView):
template_name = "fruity_flavor.html"
์ด ๋จ์ํ ์์ ์์ FruityFlavorView
ํด๋์ค๋ FreshFruitMixin
๊ณผ TemplateView
๋ฅผ ๋ ๋ค ์์ํ๊ณ ์๋ค.
TemplateView
๊ฐ ์ฅ๊ณ ์์ ์ ๊ณตํ๋ ๊ธฐ๋ณธ ํด๋์ค์ด๊ธฐ ๋๋ฌธ์ ๊ฐ์ฅ ์ค๋ฅธ์ชฝ์ ์์นํ๋ฉฐ(๊ท์น 1), ๊ทธ ์ผ์ชฝ์ FreshFruitMixin
(๊ท์น 2)์ ๊ฐ์ ธ๋ค ๋์๋ค.
๋ง์ง๋ง์ผ๋ก FreshFruitMixin
์ object
๋ฅผ ์์ํ๊ณ ์๋ค(๊ท์น 3).
์ด๋ฆ | ๋ชฉ์ |
---|---|
View | ์ด๋์์๋ ์ด์ฉ ๊ฐ๋ฅํ ๊ธฐ๋ณธ ๋ทฐ |
RedirectView | ์ฌ์ฉ์๋ฅผ ๋ค๋ฅธ URL๋ก ๋ฆฌ๋ค์ด๋ ํธ |
TemplateView | ์ฅ๊ณ HTML ํ ํ๋ฆฟ์ ๋ณด์ฌ์ค ๋ |
ListView | ๊ฐ์ฒด ๋ชฉ๋ก |
DetailView | ๊ฐ์ฒด๋ฅผ ๋ณด์ฌ์ค ๋ |
FormView | ํผ ์ ์ก |
CreateView | ๊ฐ์ฒด๋ฅผ ๋ง๋ค ๋ |
UpdateView | ๊ฐ์ฒด๋ฅผ ์ ๋ฐ์ดํธํ ๋ |
DeleteView | ๊ฐ์ฒด๋ฅผ ์ญ์ |
generic dateview | ์๊ฐ ์์๋ก ๊ฐ์ฒด๋ฅผ ๋์ดํด ๋ณด์ฌ์ค ๋ |
from django.views.generic import DetailView
from braces.views import LoginRequiredMixin
from .models import Flavor
class FlavorDetailView(LoginRequiredMixin, DetailView):
model = Flavor
๋ทฐ์์ ํผ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ ๋ ์ปค์คํ
์ก์
์ ๊ตฌํํ๊ณ ์ ํ๋ค๋ฉด, form_valid()
๋ ์ ๋ค๋ฆญ ํด๋์ค ๊ธฐ๋ฐ ๋ทฐ๊ฐ ์์ฒญ์ ๋ณด๋ด๋ ๊ณณ์ ์๋ฆฌ์ก๊ฒ ๋๋ค.
from django.views.generic import CreateView
from braces.views import LoginRequiredMixin
from .models import Flavor
class FlavorCreateView(LoginRequiredMixin, CreateView):
model = Flavor
fields = ('title', 'slug', 'scoops_remaining')
def form_valid(self, form):
# ์ปค์คํ
๋ก์ง์ด ์ด๊ณณ์ ์์น
return super(FlavorCreateView, self).form_valid(form)
์ด๋ฏธ ์ฒดํฌ๋ ํผ์ ๋ํด ์ปค์คํ
๋ก์ง์ ์ ์ฉํ๊ณ ์ถ์ ๊ฒฝ์ฐ, form_valid()
์ ๋ก์ง์ ์ถ๊ฐํ๋ฉด ๋๋ค.
form_valid()
์ ๋ฐํํ์ django.http.HttpResponseRedirect
๊ฐ ๋๋ค.
๋ทฐ์์ ํผ์ ๋ถ์ ํฉ์ฑ ๊ฒ์ฌ๋ฅผ ํ ๋ ์ปค์คํ
์ก์
์ ๊ตฌํํ๊ณ ์ ํ๋ค๋ฉด, form_invalid()
๋ ์ ๋ค๋ฆญ ํด๋์ค ๊ธฐ๋ฐ ๋ทฐ๊ฐ ์์ฒญ์ ๋ณด๋ด๋ ๊ณณ์ ์๋ฆฌ์ก๊ฒ ๋๋ค.
์ด ๋ฉ์๋๋ django.http.HttpResponse
๋ฅผ ๋ฐํํ๋ค.
from django.views.generic import CreateView
from braces.views import LoginRequiredMixin
from .models import Flavor
class FlavorCreateView(LoginRequiredMixin, CreateView):
model = Flavor
def form_invalid(self, form):
# ์ปค์คํ
๋ก์ง์ด ์ด๊ณณ์ ์์น
return super(FlavorCreateView, self).form_invalid(form)
form_valid()
์์ ๋ก์ง์ ์ถ๊ฐํ๋ ๊ฒ๊ณผ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก form_invalid()
์์๋ ๋ก์ง์ ์ถ๊ฐํ ์ ์๋ค.
์ฝํ ์ธ ๋ฅผ ๋ ๋๋งํ๋ ๋ฐ ํด๋์ค ๊ธฐ๋ฐ ๋ทฐ๋ฅผ ์ด์ฉํ๋ค๋ฉด ์์ฒด์ ์ธ ๋ฉ์๋์ ์์ฑ์ ์ ๊ณตํ๋ ๋ทฐ ๊ฐ์ฒด๋ฅผ ์ด์ฉํ์ฌ ๋ค๋ฅธ ๋ฉ์๋๋ ์์ฑ์์ ํธ์ถ์ด ๊ฐ๋ฅํ๊ฒ ํ๋ ๋ฐฉ๋ฒ์ ๊ณ ๋ คํด ๋ณผ ์ ์๋ค. ์ด๋ฐ ๋ทฐ ๊ฐ์ฒด๋ค์ ํ ํ๋ฆฟ์์๋ ํธ์ถํ ์ ์๋ค.
from django.utils.functional import cached_property
from django.views.generic import UpdateView, TemplateView
from braces.views import LoginRequiredMixin
from .models import Flavor
from .tasks import update_users_who_favorited
class FavoriteMixin(object):
@cached_property
def likes_and_favorites(self):
likes = self.object.likes()
favorites = self.object.favorites()
return {
"likes": likes,
"favorites": favorites,
"favorites_count": favorites.count(),
}
class FlavorUpdateView(LoginRequiredMixin, FavoriteMixin, UpdateView):
model = Flavor
fields = ('title', 'slug', 'scoops_remaining')
def form_valid(self, form):
update_users_who_favorited(
instance=self.object,
favorites=self.likes_and_favorites['favorites']
)
return super(FlavorCreateView, self).form_valid(form)
class FlavorDetailView(LoginRequiredMixin, FavoriteMixin, TemplateView):
model = Flavor
{% extends "base.html" %}
{% block likes_and_favorites %}
<ul>
<li>Likes: {{ view.likes_and_favorites.likes }}</li>
<li>Favorites: {{view.likes_and_favorites.favorites_count }}</li>
</ul>
{% endblock likes_and_favorites %}
from django.core.urlresolvers import reverse
from django.db import models
STATUS = (
(0, "zero"),
(1, "one"),
)
class Flavor(models.Model):
title = models.CharField(max_lenght=255)
slug = models.SlugField(unique=True)
scoops_remaining = models.IntegerField(default=0, choices=STATUS)
def get_absolute_url(self):
return reverse("flavors:detail", kwrags={"slug": self.slug})
๊ฐ์ฅ ๋จ์ํ๊ณ ์ผ๋ฐ์ ์ธ ์ฅ๊ณ ํผ ์๋๋ฆฌ์ค๋ค. ๋ชจ๋ธ์ ์์ฑํ ํ ๋ชจ๋ธ์ ์๋ก์ด ๋ ์ฝ๋๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ๊ธฐ์กด ๋ ์ฝ๋๋ฅผ ์์ ํ๋ ๊ธฐ๋ฅ๋ค์ด๋ค.
์ฌ๊ธฐ ๋ค์ ๋ทฐ๋ค์ด ์๋ค.
FlavorCreateView
: ์๋ก์ด ์ข
๋ฅ์ ์์ด์คํฌ๋ฆผ์ ์ถ๊ฐํ๋ ํผFlavorUpdateView
: ๊ธฐ์กด ์์ด์คํฌ๋ฆผ์ ์์ ํ๋ ํผFlavorDetailView
: ์์ด์คํฌ๋ฆผ ์ถ๊ฐ์ ๋ณ๊ฒฝ์ ํ์ ํ๋ ํผfrom django.views.generic import CreateView, UpdateView, DetailView
from braces.views import LoginRequiredMixin
from .models import Flavor
class FlavorCreateView(LoginRequiredMixin, CreateView):
model = Flavor
fields = ('title', 'slug', 'scoops_remaining')
class FlavorUpdateView(LoginRequiredMixin, UpdateView):
model = Flavor
fields = ('title', 'slug', 'scoops_remaining')
class FlavorDetailView(DetailView):
model = Flavor
์ฌ๊ธฐ์ ์ฃผ์ํด์ผ ํ ์ ์ด ์๋ค. ์ด ๋ทฐ๋ค์ [urls.py](http://urls.py)
๋ชจ๋์ ์ฐ๋ํ๊ณ ํ์ํ ํ
ํ๋ฆฟ์ ์์ฑํ ํ ๋ค์ ๋ฌธ์ ์ ๋ด์ฐฉํ๊ฒ ๋ ๊ฒ์ด๋ค.
FlavorDetailView๊ฐ ํ์ธ ํ์ด์ง๊ฐ ์๋๋ค.
๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ์ฒซ ๋ฒ์งธ ์ ์ฐจ๋ django.contrib.messages
๋ฅผ ์ด์ฉํ์ฌ ์ฌ์ฉ์๊ฐ ๋ฐฉ๋ฌธํด ์์ด์คํฌ๋ฆผ์ ์ถ๊ฐํ๊ฑฐ๋ ์์ด์คํฌ๋ฆผ์ ๋ณ๊ฒฝํ๋ค๋ ๊ฒ์ FlavorDetailView
์ ์๋ฆฌ๋ ๊ฒ์ด๋ค.
FlavorCreateView.form_valid()
์ FlavorUpdateView.form_valid()
๋ฉ์๋๋ค์ ์ค๋ฒ๋ผ์ด๋ฉํ ํ์๊ฐ ์๋ค. ์ด๋ FlavorActionMixin
์์ ํ๋ฒ์ ํธ๋ฆฌํ๊ฒ ํด๊ฒฐํ ์ ์๋ค.
์ด์ ์ฅ๊ณ ์ ๋ฉ์์ง ํ๋ ์์ํฌ๋ฅผ ์ด์ฉํ์ฌ ์ฌ์ฉ์๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์์ดํ
์ ์ถ๊ฐํ๊ฑฐ๋ ์์ ํ์ ๋ ํ์ธ ๋ฉ์์ง๋ฅผ ๋ณด์ฌ์ฃผ๊ฒ ํด๋ณด์.
๋ทฐ์ ํ์ธ ๋ฉ์์ง๋ฅผ ๋ณด์ฌ์ฃผ๋ ํ๋ฅผ ์์ฑํ๋ FlavorActionMixin
์ ์ ์ํ์.
๋ฏน์ค์ธ์ object๋ฅผ ์์ํด์ผ ํ๋ค. FlavorActionMixin์ ์ด๋ฏธ ์กด์ฌํ๋ ๋ฏน์ค์ธ์ด๋ ๋ทฐ๋ฅผ ์์ํ์ง ์๊ณ ํ์ด์ฌ์ object ํ์ ์ ์์ํ๋ค๋ ์ ์ ์์๋์. ๋ฏน์ค์ธ์ ๊ฐ๋ฅํ ํ ์์ฃผ ๋จ์ํ ์์์ ์ฐ๊ฒฐ์ด ๋์ด์ผ ํ๋ค๋ ๊ฒ์ ์์ง ๋ง์.
from django.contrib import messages
from django.views.generic import CreateView, UpdateView, DetailView
from braces.views import LoginRequiredMixin
from .models import Flavor
class FlavorActionMixin(object):
fields = ('title', 'slug', 'scoops_remaining')
@property
def success_msg(self):
return NotImplemented
def form_valid(self, form):
message.info(self.request, self.success_msg)
return super(FlavorActionMixin, self).form_valid(form)
class FlavorCreateView(LoginRequiredMixin, FlavorActionMixin, CreateView):
model = Flavor
success_msg = "Flavor created!"
class FlavorUpdateView(LoginRequiredMixin, FlavorActionMixin, CreateView):
model = Flavor
success_msg = "Flavor updated!"
class FlavorDetailView(DetailView):
model = Flavor
์ข
๋ฅ๊ฐ ์์ฑ๋๊ฑฐ๋ ์
๋ฐ์ดํธ๋ ํ ๋ฉ์์ง ๋ฆฌ์คํธ๊ฐ FlavorDetailView
์ context
๋ก ์ ์ก๋๋ค. ๋ค์ ์ฝ๋๋ฅผ ๋ทฐ์ ํ
ํ๋ฆฟ์ ์ถ๊ฐํ๊ณ ์์ด์คํฌ๋ฆผ ์ข
๋ฅ๋ฅผ ์๋ก ์์ฑํ๊ฑฐ๋ ์
๋ฐ์ดํธํ๋ฉด ์ด์ ๋ฉ์์ง๋ค์ ๋ณผ ์ ์์ ๊ฒ์ด๋ค.
{% if messages %}
<ul class="messages">
{% for message in message %}
<li id="message_{{ forloop.counter }}"
{% if message.tags %} class="{{ message.tags }}" {% endif %}>
{{ messsage }}
</li>
{% endfor %}
</ul>
{% endif %}
๋๋๋ก ModelForm
์ด ์๋๋ผ ์ฅ๊ณ Form
์ ์ด์ฉํ๊ณ ์ถ์ ๋๋ ์์ ๊ฒ์ด๋ค. ๊ฒ์ ํผ๊ณผ ๊ฐ์ ๊ฒฝ์ฐ ๋ง์ด๋ค.
์ด๋ฒ ์์ ์์๋ ๊ฐ๋จํ ์์ด์คํฌ๋ฆผ ์ข
๋ฅ ๊ฒ์ ํผ์ ๋ง๋ค์ด ๋ณด์. HTML ํผ์ ๋ง๋ ํ ์ด ํผ์ ์ก์
์ด ORM์ ์ฟผ๋ฆฌํ์ฌ ์ฟผ๋ฆฌ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌ์คํธ๋ก ๊ฒ์ ๊ฒฐ๊ณผ ํ์ด์ง์ ๋ณด์ฌ์ฃผ๋๋ก ํ๊ฒ ๋ค.
๊ฒ์ ์ฟผ๋ฆฌ์ ๋ง๋ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด ListView
์์ ์ง์ํ๋ ๊ธฐ๋ณธ ์ฟผ๋ฆฌ์ธํธ๋ฅผ ์์ ํด์ผ ํ๋ค. ์ด๋ฅผ ์ํด ListView
์ get_queryset()
๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ํ๋ค.
from django.views.generic import ListView
from .models import Flavor
class FlavorListView(ListView):
model = Flavor
def get_queryset(self):
queryset = super(FlavorListView, self).get_queryset()
q = self.request.GET.get("q")
if q:
return queryset.filter(title__icontains=q)
return queryset
{% comment %}
Usage: {% include "flavors/_flavor_search.html" %}
{% endcomment %}
<form action="{% url 'flavor_list' %}" method="GET">
<input type="text" name="q">
<button type"submit">search</button>
</form>
์ผ๋จ ListView
์ get_queryset()
๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ฉํ๋ค๋ฉด ๋๋จธ์ง ๋ถ๋ถ์ ์ผ๋ฐ์ ์ธ HTML ํผ๊ณผ ๋ค๋ฅผ ๊ฒ ์์ด์ง๋ค.
๋ชจ๋ ๋ทฐ์์ django.views.generic.View
๋ง ์ด์ฉํ์ฌ ์ฅ๊ณ ํ๋ก์ ํธ ์ ๋ถ๋ฅผ ๊ตฌ์ฑํ ์๋ ์๋ค.
from django.shortcuts import get_object_or_404
from django.shortcuts import render, redirect
from django.views.generic import View
from braces.views import LoginRequiredMixin
from .forms import FlavorForm
from .models import Flavor
class FlavorView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
flavor = get_object_or_404(Flavor, slug=kwargs['slug'])
return render(request, "flavors/flavor_detail.html", {"flavor": flavor})
def post(self, request, *args, **kwargs):
flavor = get_object_or_404(Flavor, slug=kwargs['slug'])
form = FlavorForm(request.POST)
if form.is_valid():
form.save()
return redirect("flavors:detail", flavor.slug)
๋ฌผ๋ก ์ด๋ฅผ ํจ์ ๊ธฐ๋ฐ ๋ทฐ๋ก๋ ๋ง๋ค์ด ์ด์ฉํ ์๋ ์๋ค.
ํ์ง๋ง FlavorView
์์์ GET/POST ๋ฉ์๋ ๋ฐ์ฝ๋ ์ด์
์ ์ด์ฉํ๋ ๊ฒ์ด ๊ธฐ์กด์ if request.method ==
์์ ์กฐ๊ฑด๋ฌธ์ ํตํ๋ ๊ฒ๋ณด๋ค ๋ ๋ซ๋ค๋ ๊ฒ์ ์ข ๊ณ ๋ฏผํด๋ด์ผ ํ ๊ฒ์ด๋ค.
๊ฒ๋ค๊ฐ ๋ฏน์ค์ธ์ ์ด์ฉํ๋ ๊ฒ์ด ํจ์ฌ ์ง๊ด์ ์ด๊ธฐ๋ ํ๋ค.
ํต์ฌ์ ๊ฐ์ฒด ์งํฅ์ ์ฅ์ ์ ์ด๋ฆฐ ํด๋์ค ๊ธฐ๋ฐ ๋ทฐ์ ํจ์ ๊ธฐ๋ฐ ๋ทฐ๋ฅผ ์๋ก ์กฐํฉํด์ ์ด์ฉํจ์ผ๋ก์จ ๊ทธ ์ฅ์ ์ ์ต๋ํ ์ด๋ฆด ์ ์๋ค๋ ๊ฒ์ด๋ค!
์ด๋ฒ ์ฅ์ ์ฝ์ผ๋ฉฐ ์ด๋ค ์์ผ๋ก CBV๋ฅผ ํ์ฉํ๋ฉด ๋ ์ข์์ง ๊ฐ์ ์ก์ ๊ฒ ๊ฐ๋ค :)