【Django】基于类的视图Class Based View

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

发布了227 篇原创文章 · 获赞 605 · 访问量 130万+

猜你喜欢

转载自blog.csdn.net/gongxifacai_believe/article/details/104077920