【Django】基於類的視圖Class Based View

  • 2020 年 2 月 14 日
  • 筆記

1、簡單樣例

# urls.py  from django.conf.urls import url  from django.views.generic import TemplateView    urlpatterns = [  	url(r'^about/', TemplateView.as_view(template_name="about.html")),  ]    Equal to  # views.py  class AboutView(TemplateView):  	template_name = "about.html"    # urls.py  from django.conf.urls import url  from some_app.views import AboutView    urlpatterns = [  	url(r'^about/', AboutView.as_view()),  ]

2、基於類的視圖 View

from django.http import HttpResponse    def my_view(request):  	if request.method == 'GET':  		# <view logic>  		return HttpResponse('get it')  	elif request.method == 'POST':  		# <view logic>  		return HttpResponse('post it')  	elif request.method == 'HEAD':  		# <view logic>  		return HttpResponse('head it')

Equal to

# views.py  from django.http import HttpResponse  from django.views.generic import View    class MyView(View):  	def get(self, request):  		# <view logic>  		return HttpResponse('get it')    	def post(self, request):  		# <view logic>  		return HttpResponse('post it')    	def head(self, request):  		# <view logic>  		return HttpResponse('head it')    # urls.py  from django.conf.urls import url  from myapp.views import MyView    urlpatterns = [  	url(r'^about/', MyView.as_view()),  ]

3、類的繼承和覆蓋

from django.http import HttpResponse  from django.views.generic import View    # 類的繼承  class GreetingView(View):  	greeting = "Good Day"    	def get(self, request):  		return HttpResponse(self.greeting)    class MorningGreetingView(GreetingView):  	greeting = "Morning to ya"    # 類的覆蓋  # urls.py  urlpatterns = [  	url(r'^about/', GreetingView.as_view(greeting="G'day")),  ]

4、View源碼分析

class View(object):  	# Intentionally simple parent class for all views.  	# Only implements dispatch-by-method and simple sanity checking.    	http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']    	def __init__(self, **kwargs):  		# Constructor. Called in the URLconf; can contain helpful extra keyword arguments, and other things.  		# Go through keyword arguments, and either save their values to our instance, or raise an error.  		for key, value in six.iteritems(kwargs):  			setattr(self, key, value)    	@classonlymethod  	def as_view(cls, **initkwargs):  		# Main entry point for a request-response process.  		for key in initkwargs:  			if key in cls.http_method_names:  				raise TypeError("You tried to pass in the %s method name as a keyword argument to %s(). Don't do that." % (key, cls.__name__))  			if not hasattr(cls, key):  				raise TypeError("%s() received an invalid keyword %r, as_view only accepts arguments that are already attributes of the class." % (cls.__name__, key))    	def view(request, *args, **kwargs):  		self = cls(**initkwargs)  		if hasattr(self, 'get') and not hasattr(self, 'head'):  			self.head = self.get  		self.request = request  		self.args = args  		self.kwargs = kwargs  		return self.dispatch(request, *args, **kwargs)  	view.view_class = cls  	view.view_initkwargs = initkwargs    	# take name and docstring from class  	update_wrapper(view, cls, update=())  	# and possible attributes set by decorators like csrf_exempt from dispatch  	update_wrapper(view, cls.dispatch, assigned=())  	return view    	def dispatch(self, request, *args, **kwargs):  		# Try to dispatch to the right method; if a method doesn't exist, defer to the error handler.  		# Also defer to the error handler if the request method isn't on the approved list.  		if request.method.lower() in self.http_method_names:  			handler = getattr(self, request.method.lower(), self.http_method_not_allowed)  		else:  			handler = self.http_method_not_allowed  		return handler(request, *args, **kwargs)    	def http_method_not_allowed(self, request, *args, **kwargs):  		logger.warning('Method Not Allowed (%s): %s', request.method, request.path,  			extra={  				'status_code': 405,  				'request': request  			}  		)  		return http.HttpResponseNotAllowed(self._allowed_methods())    	def options(self, request, *args, **kwargs):  		# Handles responding to requests for the OPTIONS HTTP verb.  		response = http.HttpResponse()  		response['Allow'] = ', '.join(self._allowed_methods())  		response['Content-Length'] = '0'  		return response    	def _allowed_methods(self):  		return [m.upper() for m in self.http_method_names if hasattr(self, m)]
  • as_view返回一個view function
  • as_view接受的參數會覆蓋類定義的變量
  • init 檢查as_view傳入的參數在類中是否定義
  • dispatch view function運行會調用dispatch,根據用戶的request method來路由到get,post方法

5、TemplateView

class ContextMixin(object):  	# A default context mixin that passes  the keyword arguments received by get_context_data as the template context.  	def get_context_data(self, **kwargs):  		if 'view' not in kwargs:  			kwargs['view'] = self  		return kwargs    class TemplateResponseMixin(object):  	# A mixin that can be used to render a template.  	template_name = None  	template_engine = None  	response_class = TemplateResponse  	content_type = None    	def render_to_response(self, context, **response_kwargs):  		# Returns a response, using the 'response_class' for this view, with a template rendered with the given context.  		# If any keyword arguments are provided, they will be passed to the constructor of the response class.  		response_kwargs.setdefault('content_type', self.content_type)  		return self.response_class(  			request=self.request,  			template=self.get_template_names(),  			context=context,  			using=self.template_engine,  			**response_kwargs  		)    	def get_template_names(self):  		# Returns a list of template names to be used for the request. Must return a list.  		# May not be called if render_to_response is overridden.  		if self.template_name is None:  			raise ImproperlyConfigured(  				"TemplateResponseMixin requires either a definition of 'template_name' or an implementation of 'get_template_names()'")  		else:  			return [self.template_name]    class TemplateView(TemplateResponseMixin, ContextMixin, View):  	# A view that renders a template. This view will also pass into the context any keyword arguments passed by the URLconf.  	def get(self, request, *args, **kwargs):  		context = self.get_context_data(**kwargs)  		return self.render_to_response(context)
  • Mixin提供一些方法,如TemplateResponseMixin主要提供了一個render_to_response方法,渲染模板
  • View提供get,post用戶訪問入口

6、封裝Mixin

from django.contrib.auth.decorators import login_required    class LoginRequiredMixin(object):  	@classmethod  	def as_view(cls, **initkwargs):  		view = super(LoginRequiredMixin, cls).as_view(**initkwargs)  		return login_required(view)    class MyView(LoginRequiredMixin, ...):  	# this is a generic view  	...

7、裝飾類

from django.contrib.auth.decorators import login_required  from django.utils.decorators import method_decorator  from django.views.generic import TemplateView    class ProtectedView(TemplateView):  	template_name = 'secret.html'    	@method_decorator(login_required)  	def dispatch(self, *args, **kwargs):  		return super(ProtectedView, self).dispatch(*args, **kwargs)

8、通用視圖——List View

# models.py  from django.db import models  class Publisher(models.Model):  	name = models.CharField(max_length=30)  	address = models.CharField(max_length=50)  	city = models.CharField(max_length=60)  	state_province = models.CharField(max_length=30)  	country = models.CharField(max_length=50)  	website = models.URLField()    	class Meta:  		ordering = ['-name']    	def __str__(self):						# __unicode__ on Python 2  		return self.name    class Author(models.Model):  	salutation = models.CharField(max_length=10)  	name = models.CharField(max_length=200)  	email = models.EmailField()  	headshot = models.ImageField(upload_to='author_headshots')    	def __str__(self):						# __unicode__ on Python 2  		return self.name    class Book(models.Model):  	title = models.CharField(max_length=100)  	authors = models.ManyToManyField('Author')  	publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)  	publication_date = models.DateField()
# books/views.py  # General function version  def Publisher_list(request):  	publishers = Publisher.objects.all()  	return render(request, 'publisher_list.html', {'publishers': publishers})
# books/views.py  # Django class base view generic class view  from django.views.generic import ListView  from books.models import Publisher  class PublisherList(ListView):  	model = Publisher  	# queryset = Publisher.objects.all()  	# queryset = Publisher.objects.filter(ip='127.0.0.1')  	# context = {'object_list': queryset}  	# context_object_name = 'publishers'  	# template_name = <app_label>/<model_name>_list.html  	# template_name = 'books/publisher_list.html'  	# def get_queryset(self):  	#		return Publisher.objects.all()
# books/templates/books/publisher_list.html  {% extends "base.html" %}  {% block content %}  	<h2>Publishers</h2>  	<ul>  		{% for publisher in object_list %}  			<li>{{ publisher.name }}</li>  		{% endfor %}  	</ul>  {% endblock %}
  • Multi object list view:多個對象的List View
  • model:模型
  • queryset:查詢集
  • get_queryset三者關係
  • content_object_name:返回list
  • template_name:<app_label>/<model_name>_list.html

9、通用視圖——Detail View

顯示一個object的詳情頁面。

from django.views.generic import DetailView  from books.models import Publisher, Book    class PublisherDetail(DetailView):  	model = Publisher  	content_object_name = 'publisher'    	# Call the base implementation first to get a context  	context = super(PublisherDetail, self).get_context_data(**kwargs)  	# Add in a QuerySet of all the books  	context['book_list'] = Book.objects.all()  	return context    	# def get_object(self):  	#		object = super(PublisherDetail, self).get_object()  	#		object.last_accessed = timezone.now()  	#		object.save()  	#		return object
  • Single object detail view
  • get_context_data
  • content_object_name

10、通用視圖——Form

basic form view
# forms.py  from django import forms    class ContactForm(forms.Form):  	name = forms.CharField()  	message = forms.CharField(widget=forms.Textarea)    	def send_email(self):  		# send email using the self.cleaned_data dictionary  		pass    # views.py  from myapp.forms import ContactForm  from django.views.generic.edit import FormView    class ContactView(FormView):  	template_name = 'contact.html'  	form_class = ContactForm  	success_url = '/thanks/'    	def form_valid(self, form):  		# This method is called when valid form data has been POSTed.  		# It should return an HttpResponse.  		form.send_email()  		return super(ContactView, self).form_valid(form)
CreateView, UpdateView, DeleteView
# models.py  from django.urls import reverse_lazy  from django.db import models    class Author(models.Model):  	name = models.CharField(max_length=200)    	def get_absolute_url(self):  		return reverse_lazy0('author-detail', kwargs={'pk': self.pk})    # views.py  from django.views.generic.edit import CreateView, UpdateView, DeleteView  from django.urls import reverse_lazy  from myapp.models import Author    class AuthorCreate(CreateView):  	model = Author  	fields = ['name']    class AuthorUpdate(UpdateView):  	model = Author  	fields = ['name']    class AuthorDelete(DeleteView):  	model = Author  	success_url = reverse_lazy('author-list')    # urls.py  from django.conf.urls import url  from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete    urlpatterns = [  	url(r'author/add/$', AuthorCreate.as_view(), name='author-add'),  	url(r'author/(?P<pk>[0-9]+)/$', AuthorUpdate.as_view(), name='author-update'),  	url(r'author/(?P<pk>[0-9]+)/delete/$', AuthorDelete.as_view(), name='author-delete'),  ]

11、使用ListView和SingleObjectMixin

如果我們需要在Publisher的詳情頁面里展現該出版社的圖書,顯然有SingleObject,也有MultiObject。

# views.py  from django.views.generic import ListView  from django.views.generic.detail import SingleObjectMixin  from books.models import Publisher    class PublisherDetail(SingleObjectMixin, ListView):  	paginate_by = 2  	template_name = "books/publisher_detail.html"    	def get(self, request, *args, **kwargs):  		self.object = self.get_object(queryset=Publisher.objects.all())  		return super(PublisherDetail, self).get(request, *args, **kwargs)    	def get_context_data(self, **kwargs):  		context = super(PublisherDetail, self).get_context_data(**kwargs)  		context['publisher'] = self.object  		return context    	def get_queryset(self):  		return self.object.book_set.all()    # publisher_detail.html  {% extends "base.html" %}  {% block content %}  	<h2>Publisher {{ publisher.name }}</h2>  	<ol>  		{% for book in page_obj %}  			<li>{{ book.title }}</li>  		{% endfor %}  	</ol>  	<div class="pagination">  		<span class="step-links">  			{% if page_obj.has_previous %}  				<a href="?page={{ page_obj.previous_page_number }}">previous</a>  			{% endif %}  			<span class="current">  				Page {{ page_obj.number }} of {{ paginator.num_pages }}.  			</span>  			{% if page_obj.has_next %}  				<a href="?page={{ page_obj.next_page_number }}">next</a>  			{% endif %}  		</span>  	</div>  {% endblock %}

12、總結

  • DetailView
    • SingleObjectMixin
      • get_object
      • get_context_data
    • SingleObjectTemplateResponseMixin
      • get_template_names -> <app_lable>/<model_name>_detail.html
    • BaseDetailView
  • ListView
    • MultipleObjectMixin
      • get_queryset -> return from queryset or model
      • paginate_queryset
      • get_context_data -> {『object_list』: queryset}
    • MultipleObjectTemplateResponseMixin
      • get_template_names -> <app_lable>/<model_name>_list.html
    • BaseListView
  • UpdateView CreateView
    • SingleObjectMixin
      • get_object
    • ModelFormMixin
      • get_form_class
      • form_valid
      • form_invalid
      • get_context_data
    • SingleObjectTemplateResponseMixin
      • get_template_names -> <app_label>/<model_name>_update.html
  • 使用Class Based View編寫view更簡潔,代碼更少,但同時由於複雜的繼承關係為理解帶來不便,所以兩者各有優勢,需要自己決定使用哪種方式。想學好CBV,需要多研究幾遍源碼。