Django Class Based views (4)
CBV를 좀 더 자세하게 알아보자
제네릭 뷰 중 Edit View에 대해 공부 !
Generic Edit View
- 폼을 통해 객체를 생성, 수정, 삭제하는 기능을 제공하는 뷰
- FormView, CreateView, UpdateView, DeleteView로 구성
FormView
- 폼이 주어지면 해당 폼을 출력
1
2
class FormView(TemplateResponseMixin, BaseFormView):
"""A view for displaying a form and rendering a template response."""
-
TemplateResponseMixin과 BaseFormView를 상속받는다
-
BaseFormView
1 2
class BaseFormView(FormMixin, ProcessFormView): """A base view for displaying a form."""
- BaseFormView는 다시 FormMixin, ProcessFormView를 상속받는다
- FormMixin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
class FormMixin(ContextMixin): """Provide a way to show and handle a form in a request.""" initial = {} form_class = None success_url = None prefix = None def get_initial(self): """Return the initial data to use for forms on this view.""" return self.initial.copy() def get_prefix(self): """Return the prefix to use for forms.""" return self.prefix def get_form_class(self): """Return the form class to use.""" return self.form_class def get_form(self, form_class=None): """Return an instance of the form to be used in this view.""" if form_class is None: form_class = self.get_form_class() return form_class(**self.get_form_kwargs()) def get_form_kwargs(self): """Return the keyword arguments for instantiating the form.""" kwargs = { 'initial': self.get_initial(), 'prefix': self.get_prefix(), } if self.request.method in ('POST', 'PUT'): kwargs.update({ 'data': self.request.POST, 'files': self.request.FILES, }) return kwargs def get_success_url(self): """Return the URL to redirect to after processing a valid form.""" if not self.success_url: raise ImproperlyConfigured("No URL to redirect to. Provide a success_url.") return str(self.success_url) # success_url may be lazy def form_valid(self, form): """If the form is valid, redirect to the supplied URL.""" return HttpResponseRedirect(self.get_success_url()) def form_invalid(self, form): """If the form is invalid, render the invalid form.""" return self.render_to_response(self.get_context_data(form=form)) def get_context_data(self, **kwargs): """Insert the form into the context dict.""" if 'form' not in kwargs: kwargs['form'] = self.get_form() return super().get_context_data(**kwargs)
- 클래스 변수들
- initial
- 딕셔너리형식으로 form에 대한 초기 데이터를 포함
- form_class
- 인스턴스화 하기위한 form_class를 지정
- success_url
- form이 성공적으로 처리될 때 리디렉션할 URL
- initial
- get_initial
- form에 대한 초기 데이터를 검색, default로 초기 복사본을 return한다
- get_form_class
- 사용할 form class를 return
- get_form
- 클래스 변수 form_class가 있으면 인스턴스화 하고, 없으면 get_form_class가 실행되어 인스턴스화한 후 return
- form_valid / form_invalid
- form이 유효하다면 success_url로 리디렉션, 유효하지 않다면 렌더링
- ProcessFormView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
class ProcessFormView(View): """Render a form on GET and processes it on POST.""" def get(self, request, *args, **kwargs): """Handle GET requests: instantiate a blank version of the form.""" return self.render_to_response(self.get_context_data()) def post(self, request, *args, **kwargs): """ Handle POST requests: instantiate a form instance with the passed POST variables and then check if it's valid. """ form = self.get_form() if form.is_valid(): return self.form_valid(form) else: return self.form_invalid(form) # PUT is a valid HTTP verb for creating (with a known URL) or editing an # object, note that browsers only support POST for now. def put(self, *args, **kwargs): return self.post(*args, **kwargs)
- ProcessFormView는 GET, POST 워크플로우를 제공한다
- get
- get요청을 처리하며 빈 form을 인스턴스화
- post
- post요청을 처리하며 전달받은 form을 인스턴스화
- 전달받은 form이 유효한지, 유효하지 않은지를 검사
- TemplateResponseMixin은 template의 기본적인 설정, template의 이름, rendering 하는 과정들을 담당한다
CreateView
- 객체를 생성하는 폼 출력
1
2
3
4
5
class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView):
"""
View for creating a new object, with a response rendered by a template.
"""
template_name_suffix = '_form'
-
GET요청에 나타나는 CreateView페이지는 default로
_form
의 template_name_suffix를 사용한다 -
SingleObjectTemplateResponseMixin, BaseCreateView를 상속받는다
- BaseCreateView
1 2 3 4 5 6 7 8 9 10 11 12
class BaseCreateView(ModelFormMixin, ProcessFormView): """ Base view for creating a new object instance. Using this base class requires subclassing to provide a response mixin. """ def get(self, request, *args, **kwargs): self.object = None return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.object = None return super().post(request, *args, **kwargs)
- SingleObjectTemplateResponseMixin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
class SingleObjectTemplateResponseMixin(TemplateResponseMixin): template_name_field = None template_name_suffix = '_detail' def get_template_names(self): """ Return a list of template names to be used for the request. May not be called if render_to_response() is overridden. Return the following list: * the value of ``template_name`` on the view (if provided) * the contents of the ``template_name_field`` field on the object instance that the view is operating upon (if available) * ``<app_label>/<model_name><template_name_suffix>.html`` """ try: names = super().get_template_names() except ImproperlyConfigured: # If template_name isn't specified, it's not a problem -- # we just start with an empty list. names = [] # If self.template_name_field is set, grab the value of the field # of that name from the object; this is the most specific template # name, if given. if self.object and self.template_name_field: name = getattr(self.object, self.template_name_field, None) if name: names.insert(0, name) # The least-specific option is the default <app>/<model>_detail.html; # only use this if the object in question is a model. if isinstance(self.object, models.Model): object_meta = self.object._meta names.append("%s/%s%s.html" % ( object_meta.app_label, object_meta.model_name, self.template_name_suffix )) elif getattr(self, 'model', None) is not None and issubclass(self.model, models.Model): names.append("%s/%s%s.html" % ( self.model._meta.app_label, self.model._meta.model_name, self.template_name_suffix )) # If we still haven't managed to find any template names, we should # re-raise the ImproperlyConfigured to alert the user. if not names: raise return names
django.views.generic.detail.SingleObjectTemplateResponseMixin
- 클래스 변수
- template_name_field
- 후보 템플릿의 이름을 결정하는데 사용할 수 있는 현재 개체 인스턴스 필드
- template_name_suffix
- 자동으로 생성된 후보 템플릿 이름에 추가할 접미사로, 기본 접미사는
_detail
이다
- 자동으로 생성된 후보 템플릿 이름에 추가할 접미사로, 기본 접미사는
- template_name_field
- get_template_names
- template_name값이 제공된 경우 return
- 작동중인 개체 인스턴스의 template_name_field 내용이 사용가능한 경우 return
<app_label>/<model_name><template_name_suffix>.html
형식으로 return- render_to_response()이 오버라이딩된 경우 호출되지 않을 수 있다
-
사용 예시
1
2
3
4
5
6
7
8
9
10
# forms.py
from django import forms
from django.db import models
from .models import Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['userName', 'contents', ...]
1
2
3
4
5
6
7
8
# views.py
from django.views.generic import CreateView
from .forms import CommentForm
class CommentCreateView(CreateView):
template_name = 'commentBoard/comment_new.html'
success_url = '/'
form_class = CommentForm
1
2
3
4
# urls.py
urlpatterns = [
path('comment/new/', views.CommentCreateView.as_view(), name='comment_new'),
]
UpdateView
- 기존 객체를 수정하는 폼을 출력
1
2
3
class UpdateView(SingleObjectTemplateResponseMixin, BaseUpdateView):
"""View for updating an object, with a response rendered by a template."""
template_name_suffix = '_form'
-
SingleObjectTemplateResponseMixin, BaseUpdateView상속
- BaseUpdateView
1 2 3 4 5 6 7 8 9 10 11 12
class BaseUpdateView(ModelFormMixin, ProcessFormView): """ Base view for updating an existing object. Using this base class requires subclassing to provide a response mixin. """ def get(self, request, *args, **kwargs): self.object = self.get_object() return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.object = self.get_object() return super().post(request, *args, **kwargs)
- 기존의 object를 업데이트하기 위한 view
- response mixin을 제공하기 위해 하위클래스가 필요하다
- ModelFormMixin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
class ModelFormMixin(FormMixin, SingleObjectMixin): """Provide a way to show and handle a ModelForm in a request.""" fields = None def get_form_class(self): """Return the form class to use in this view.""" if self.fields is not None and self.form_class: raise ImproperlyConfigured( "Specifying both 'fields' and 'form_class' is not permitted." ) if self.form_class: return self.form_class else: if self.model is not None: # If a model has been explicitly provided, use it model = self.model elif getattr(self, 'object', None) is not None: # If this view is operating on a single object, use # the class of that object model = self.object.__class__ else: # Try to get a queryset and extract the model class # from that model = self.get_queryset().model if self.fields is None: raise ImproperlyConfigured( "Using ModelFormMixin (base class of %s) without " "the 'fields' attribute is prohibited." % self.__class__.__name__ ) return model_forms.modelform_factory(model, fields=self.fields) def get_form_kwargs(self): """Return the keyword arguments for instantiating the form.""" kwargs = super().get_form_kwargs() if hasattr(self, 'object'): kwargs.update({'instance': self.object}) return kwargs def get_success_url(self): """Return the URL to redirect to after processing a valid form.""" if self.success_url: url = self.success_url.format(**self.object.__dict__) else: try: url = self.object.get_absolute_url() except AttributeError: raise ImproperlyConfigured( "No URL to redirect to. Either provide a url or define" " a get_absolute_url method on the Model.") return url def form_valid(self, form): """If the form is valid, save the associated model.""" self.object = form.save() return super().form_valid(form)
- ModelForms에서 작동하는 mixin
- SingleObjectMixin의 하위 클래스이기 때문에 ModelForm이 조작하고 있는 객체의 유형을 설명하는 모델과 Queryset 속성에 접근할 수 있다
- fields
- 모델을 사용하는, 즉 form class를 자동으로 생성하는 경우 필수 속성이다
- 이 속성을 생략하게되면
ImproperlyConfigured exception
발생
-
사용 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
# views.py
class CommentUpdateView(UpdateView):
model = Comment
context_object_name = 'Comment'
form_class = CommentForm
template_name = 'commentBoard/Comment_update.html'
success_url = '/'
# get object
def get_object(self):
comment = get_object_or_404(Comment, pk=self.kwargs['pk'])
return comment
- context_object_name을 정해서 해당 이름으로 template에서 사용 할 수 있게 해줌
- template_name을
commentBoard/Comment_update.html
로 템플릿 파일로 지정 - success_url 을 통해 리디렉션할 url 설정
comment
는 해당 데이터 객체의 pk를 받아서 해당 pk와 일치하는 Comment 데이터 객체를 찾게 된다.
1
2
3
4
5
6
7
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('comment/<int:pk>/edit/', views.CommentUpdateView.as_view(), name='comment_edit'),
]
- 여기서 pk를
CommentUpdateView
에 전달하게 됨
DeleteView
- 기존 객체를 삭제하는 폼을 출력
1
2
3
4
5
6
class DeleteView(SingleObjectTemplateResponseMixin, BaseDeleteView):
"""
View for deleting an object retrieved with self.get_object(), with a
response rendered by a template.
"""
template_name_suffix = '_confirm_delete'
-
SingleObjectTemplateResponseMixin, BaseDeleteView 상속
- BaseDeleteView
1 2 3 4 5
class BaseDeleteView(DeletionMixin, BaseDetailView): """ Base view for deleting an object. Using this base class requires subclassing to provide a response mixin. """
- UpdateView와 마찬가지로 mixin을 위해 하위클래스
- DeletionMixin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
class DeletionMixin: """Provide the ability to delete objects.""" success_url = None def delete(self, request, *args, **kwargs): """ Call the delete() method on the fetched object and then redirect to the success URL. """ self.object = self.get_object() success_url = self.get_success_url() self.object.delete() return HttpResponseRedirect(success_url) # Add support for browsers which only accept GET and POST for now. def post(self, request, *args, **kwargs): return self.delete(request, *args, **kwargs) def get_success_url(self): if self.success_url: return self.success_url.format(**self.object.__dict__) else: raise ImproperlyConfigured( "No URL to redirect to. Provide a success_url.")
- success_url
- 지정된 개체가 삭제되었을 때 리디렉션할 url
/parent/{parent_id}/
처럼 사전 문자열 형식을 포함할 수 있다
참고사이트