【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,需要多研究几遍源码。