【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
- SingleObjectMixin
- 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
- MultipleObjectMixin
- 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
- SingleObjectMixin
- 使用Class Based View编写view更简洁,代码更少,但同时由于复杂的继承关系为理解带来不便,所以两者各有优势,需要自己决定使用哪种方式。想学好CBV,需要多研究几遍源码。