๐Ÿ”ซ ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ ๋ทฐ์˜ ๋ชจ๋ฒ”์ ์ธ ์ด์šฉ

Django์—๋Š” CBV, FBV ๋‘ ์ข…๋ฅ˜์˜ ๋ทฐ๊ฐ€ ์กด์žฌํ•œ๋‹ค. ํŠนํžˆ ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ ๋ทฐ๋Š” ์žฅ๊ณ ์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ์ œ๋„ค๋ฆญ ๋ทฐ๋ฅผ ์ƒ์†ํ•ด์„œ ์‚ฌ์šฉํ•˜๋ฉด ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๊ธฐ๋Šฅ์„ ๋งค์šฐ ํŽธ๋ฆฌํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‚˜๋Š” ๋Œ€๋ถ€๋ถ„ ํ•จ์ˆ˜ ๊ธฐ๋ฐ˜ ๋ทฐ๋กœ๋งŒ ์ž‘์„ฑ์„ ํ•ด์™”๋Š”๋ฐ CBV๋ฅผ ์ž˜ ํ™œ์šฉํ•œ ์ฝ”๋“œ๊ฐ€ ํ›จ์”ฌ ๊น”๋”ํ•˜๊ณ  ์ง๊ด€์ ์ด๋ผ ๋Š๊ปด์ ธ ์ตœ๋Œ€ํ•œ ์žฅ๊ณ ์—์„œ ์ง€์›ํ•ด์ฃผ๋Š” ๋ทฐ๋ฅผ ์ž˜ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค. ์ด๋ฒˆ ํฌ์ŠคํŒ…์€ Two Scoops of Django 10์žฅ์„ ์ฝ๊ณ  ์ค‘์š”ํ•œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•œ ๊ธ€์ด๋‹ค. ๋” ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์ฑ…์„ ์ฐธ๊ณ ํ•˜์‹œ๋ฉด ์ข‹๋‹ค.

๊ฐœ์š”

์žฅ๊ณ ๋Š” ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ ๋ทฐ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ํ‘œ์ค€ํ™”๋œ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•œ๋‹ค. ํ•จ์ˆ˜ ๊ธฐ๋ฐ˜ ๋ทฐ์—์„œ๋Š” ๋ทฐ ํ•จ์ˆ˜ ์ž์ฒด๊ฐ€ ๋‚ด์žฅ ํ•จ์ˆ˜์ด๊ณ , ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ ๋ทฐ์—์„œ๋Š” ๋ทฐ ํด๋ž˜์Šค๊ฐ€ ๋‚ด์žฅ ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” as_view() ํด๋ž˜์Šค ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค. django.views.generic.View์—์„œ ํ•ด๋‹น ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ๊ตฌํ˜„๋˜๋ฉฐ ๋ชจ๋“  ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ ๋ทฐ๋Š” ์ด ํด๋ž˜์Šค๋ฅผ ์ง๊ฐ„์ ‘์ ์œผ๋กœ ์ƒ์†๋ฐ›์•„ ์ด์šฉํ•œ๋‹ค. ๋˜ํ•œ ์žฅ๊ณ ๋Š” ์š”์ฆ˜ ๋Œ€๋ถ€๋ถ„์˜ ์›น ํ”„๋กœ์ ํŠธ์—์„œ ์ด์šฉ๋˜๋Š” ์ œ๋„ค๋ฆญ ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ ๋ทฐ(GGBV)๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ๊ทธ ์žฅ์ ์„ ์ตœ๋Œ€ํ•œ ์‚ด๋ฆฌ๊ณ  ์žˆ๋‹ค.

์žฅ๊ณ ์˜ ๊ธฐ๋ณธํ˜•์„ ๋ณด๋ฉด ์ œ๋„ค๋ฆญ ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ ๋ทฐ๋ฅผ ์œ„ํ•œ ์ค‘์š”ํ•œ ๋ฏน์Šค์ธ๋“ค์ด ๋น ์ ธ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ django-braces ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•จ์œผ๋กœ์จ ์ด๋Ÿฐ ๋ถ€๋ถ„๋“ค์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. django-braces ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์žฅ๊ณ ์˜ ์ œ๋„ค๋ฆญ ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ ๋ทฐ๋ฅผ ๋งค์šฐ ์‰ฝ๊ณ  ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ธฐ ์œ„ํ•œ ๋ช…ํ™•ํ•œ ๋ฏน์Šค์ธ๋“ค์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ๋‹ค.

ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ ๋ทฐ๋ฅผ ์ด์šฉํ•  ๋•Œ์˜ ๊ฐ€์ด๋“œ๋ผ์ธ

  • ๋ทฐ ์ฝ”๋“œ์˜ ์–‘์€ ์ ์œผ๋ฉด ์ ์„์ˆ˜๋ก ์ข‹๋‹ค.
  • ๋ทฐ ์•ˆ์—์„œ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ๋ฐ˜๋ณต์ ์œผ๋กœ ์ด์šฉํ•˜์ง€ ๋ง์ž.
  • ๋ทฐ๋Š” ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๋กœ์ง์—์„œ ๊ด€๋ฆฌํ•˜๋„๋ก ํ•˜์ž. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์€ ๋ชจ๋ธ์—์„œ ์ฒ˜๋ฆฌํ•˜์ž. ๋งค์šฐ ํŠน๋ณ„ํ•œ ๊ฒฝ์šฐ๋Š” ํผ์—์„œ ์ฒ˜๋ฆฌํ•˜์ž.
  • ๋ทฐ๋Š” ๊ฐ„๋‹จ ๋ช…๋ฃŒํ•ด์•ผ ํ•œ๋‹ค.
  • 403, 404, 500 ์—๋Ÿฌ ํ•ธ๋“ค๋ง์— ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ ๋ทฐ๋Š” ์ด์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋Œ€์‹  ํ•จ์ˆ˜ ๊ธฐ๋ฐ˜ ๋ทฐ๋ฅผ ์ด์šฉํ•˜์ž.
  • ๋ฏน์Šค์ธ์€ ๊ฐ„๋‹จ ๋ช…๋ฃŒํ•ด์•ผ ํ•œ๋‹ค.

ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ ๋ทฐ์™€ ๋ฏน์Šค์ธ ์ด์šฉํ•˜๊ธฐ

ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ๋Š” ๋ฏน์Šค์ธ์ด๋ž€ ์‹ค์ฒดํ™”๋œ ํด๋ž˜์Šค๊ฐ€ ์•„๋‹ˆ๋ผ ์ƒ์†ํ•ด ์ค„ ๊ธฐ๋Šฅ๋“ค์„ ์ œ๊ณตํ•˜๋Š” ํด๋ž˜์Šค๋ฅผ ์˜๋ฏธํ•œ๋‹ค. ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์—์„œ ๋‹ค์ค‘ ์ƒ์†์„ ํ•ด์•ผ ํ•  ๋•Œ ๋ฏน์Šค์ธ์„ ์“ฐ๋ฉด ํด๋ž˜์Šค์— ๋” ๋‚˜์€ ๊ธฐ๋Šฅ๊ณผ ์—ญํ• ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ฏน์Šค์ธ์„ ์ด์šฉํ•ด์„œ ๋ทฐ ํด๋ž˜์Šค๋ฅผ ์ œ์ž‘ํ•  ๋•Œ ์ผ€๋„ค์Šค ๋Ÿฌ๋ธŒ๊ฐ€ ์ œ์•ˆํ•œ ์ƒ์†์— ๊ด€ํ•œ ๊ทœ์น™๋“ค์„ ๋”ฐ๋ฅด๊ธฐ๋กœ ํ•˜์ž.

  1. ์žฅ๊ณ ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ ๋ทฐ๋Š” ํ•ญ์ƒ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ์ง„ํ–‰ํ•œ๋‹ค.
  2. ๋ฏน์Šค์ธ์€ ๊ธฐ๋ณธ ๋ทฐ์—์„œ๋ถ€ํ„ฐ ์™ผ์ชฝ์œผ๋กœ ์ง„ํ–‰ํ•œ๋‹ค.
  3. ๋ฏน์Šค์ธ์€ ํŒŒ์ด์ฌ์˜ ๊ธฐ๋ณธ ๊ฐ์ฒด ํƒ€์ž…์„ ์ƒ์†ํ•ด์•ผ๋งŒ ํ•œ๋‹ค.
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})

๋ทฐ + ๋ชจ๋ธํผ ์˜ˆ์ œ

๊ฐ€์žฅ ๋‹จ์ˆœํ•˜๊ณ  ์ผ๋ฐ˜์ ์ธ ์žฅ๊ณ  ํผ ์‹œ๋‚˜๋ฆฌ์˜ค๋‹ค. ๋ชจ๋ธ์„ ์ƒ์„ฑํ•œ ํ›„ ๋ชจ๋ธ์— ์ƒˆ๋กœ์šด ๋ ˆ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๊ธฐ์กด ๋ ˆ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ๊ธฐ๋Šฅ๋“ค์ด๋‹ค.

์—ฌ๊ธฐ ๋‹ค์Œ ๋ทฐ๋“ค์ด ์žˆ๋‹ค.

  1. FlavorCreateView : ์ƒˆ๋กœ์šด ์ข…๋ฅ˜์˜ ์•„์ด์Šคํฌ๋ฆผ์„ ์ถ”๊ฐ€ํ•˜๋Š” ํผ
  2. FlavorUpdateView : ๊ธฐ์กด ์•„์ด์Šคํฌ๋ฆผ์„ ์ˆ˜์ •ํ•˜๋Š” ํผ
  3. 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 ์ด์šฉํ•˜๊ธฐ

๋ชจ๋“  ๋ทฐ์—์„œ 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๋ฅผ ํ™œ์šฉํ•˜๋ฉด ๋” ์ข‹์„์ง€ ๊ฐ์„ ์žก์€ ๊ฒƒ ๊ฐ™๋‹ค :)

References

  • Two Scoops of Django

Written by@ugaemi
Record things I want to remember

๐Ÿฑ GitHub๐Ÿ“š Reading Space