zoukankan      html  css  js  c++  java
  • django(六):view和cbv

      FBV即以函数的形式实现视图函数,CBV即以类的形式实现视图函数;相比而言,CBV根据请求方式书写各自的代码逻辑,结构清晰明了,但是由于多了一层反射机制,性能要差一些;FBV执行效率要高一些,但是代码逻辑看起来要混乱一些。

    一、CBV源码实现

      django支持以类的形式写视图函数,它需要继承自django.views.generic.base.View。可以通过from django.views.gener

    class View:
        """
        Intentionally simple parent class for all views. Only implements
        dispatch-by-method and simple sanity checking.
        """
      # 1.允许可被重写的方法列表
        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 kwargs.items():
                setattr(self, key, value)
      # 2.在path路径中将views.funciton改写为views.Class.as_view(),其实是执行了这个函数;initkwaegs可以写入传递的请求方式;
      """
        在views.Class.as_view()时,首先把cls,也就是自己传递给了这个函数;
        定义了view函数后,给view函数设置view_class和view_initkwargs属性;
        将view函数作为装饰器,装饰给这个cls,以及cls中的dispatch方法;其本质上就是将initkwarga传递进去;
        真正返回的是view函数,它内部实例化了当前的cls类,可以使用cls中的方法;
        整个过程相当于调用了View.view方法,只不过对这个方法做了一下包裹.
        感觉像是一个人模型(Class)在空间戒指(as_view)隐藏了自身的属性(view_class和view_initkwargs)和功能(view)。
      """
    @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))
         # 3.path调用views.Class.as_view(),也就是执行view函数;view函数实例化了一个当前Class的对象,并把request以及参数封装传递给了Class.dispatch,然后调用Class.dispatch
    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, updated=()) # and possible attributes set by decorators # like csrf_exempt from dispatch update_wrapper(view, cls.dispatch, assigned=()) return view   # 4.dispatch负责在FBV类中反射相应的方法
      """
      它从request.method中获取请求类型(假设是GET),并进行反射,并交给我们在Class中写好的对应方法(GET)去执行
      那么dispatch就相当于一个请求分发器,它在请求处理前执行
      """
      
    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: # 如果请求方法在self.http_method_not_allowed,就反射相应的方法 handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: # 否则就调用self.http_method_not_allowed 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 HttpResponseNotAllowed(self._allowed_methods()) def options(self, request, *args, **kwargs): """Handle responding to requests for the OPTIONS HTTP verb.""" response = 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)]
    from functools import update_wrapper
    class Person:
        def __init__(self, **kwargs):
            pass
    
        @classmethod
        def as_View(cls, **initkwargs):
            def view(request, *args, **kwargs):
                self = cls(**initkwargs)
                self.request = request
                self.args = args
                self.kwargs = kwargs
                return self.dispatch(request, *args, **kwargs)
    
            view.view_class = cls
            view.view_initkwargs = initkwargs
            update_wrapper(view, cls, updated=())
            update_wrapper(view, cls.dispatch, assigned=())
    
            return view
    
        def dispatch(self, request, *args, **kwargs):
            print(request)
            print(*args)
            print(kwargs)
            return "HttpHandler"
    
    
    
    if __name__ == '__main__':
        view = Person.as_View(**{"name": "Jan", "age":100})
        print(view)
        print(view.view_class)
        print(view.view_initkwargs)
        view("request", 1234, gender="female")
        print()
    
    """
    <function Person at 0x10c75b840>
    <class '__main__.Person'>
    {'name': 'Jan', 'age': 100}
    request
    1234
    {'gender': 'female'}
    """
    as_view的逻辑

    二、使用CBV

      对比FBV:

    # views.py
    from
    django.shortcuts import render, redirect from django.views.generic.base import View # CBV写法 class Login(View): def get(self, request, *args, **kwargs): return render(request, 'app04/login.html', {"msg": ''}) def post(self, request, *args, **kwargs): user= request.POST.get("user", False) pwd = request.POST.get("pwd", False) if user == "root" and pwd == "root": request.session["username"] = user return redirect("index") else: msg = "用户名或密码错误" return render(request, 'app04/login.html', {"msg": ''}) # FBV写法 def login(request): msg = "" # print(request.environ["Set-Cookie"]) if request.method == "POST": user= request.POST.get("user", False) pwd = request.POST.get("pwd", False) if user == "root" and pwd == "root": request.session["username"] = user return redirect("index") else: msg = "用户名或密码错误" return render(request, 'app04/login.html', {"msg": msg}) def index(request): username = request.session.get("username") if username: return render(request, 'app04/index.html', {'username': username}) return redirect('login')
    # urls.py
    from django.urls import path
    from app04 import views
    
    urlpatterns = [
        # path('login', views.login, name="login"),
        path('index', views.index, name="index"),
        path('login', views.Login.as_view(), name="login")
    ]

    三、CBV使用

      django.utils.decorators.py是django内置的装饰器包,提供了对类、方法以及中间件的装饰功能。

    "Functions that help with dynamically creating decorators for views."
    
    # For backwards compatibility in Django 2.0.
    from contextlib import ContextDecorator  # noqa
    from functools import WRAPPER_ASSIGNMENTS, update_wrapper, wraps
    
    
    class classonlymethod(classmethod):
        def __get__(self, instance, cls=None):
            if instance is not None:
                raise AttributeError("This method is available only on the class, not on instances.")
            return super().__get__(instance, cls)
    
    
    def method_decorator(decorator, name=''):
        """
        Convert a function decorator into a method decorator
        """
        # 'obj' can be a class or a function. If 'obj' is a function at the time it
        # is passed to _dec,  it will eventually be a method of the class it is
        # defined on. If 'obj' is a class, the 'name' is required to be the name
        # of the method that will be decorated.
        def _dec(obj):
            is_class = isinstance(obj, type)
            if is_class:
                if name and hasattr(obj, name):
                    func = getattr(obj, name)
                    if not callable(func):
                        raise TypeError(
                            "Cannot decorate '{0}' as it isn't a callable "
                            "attribute of {1} ({2})".format(name, obj, func)
                        )
                else:
                    raise ValueError(
                        "The keyword argument `name` must be the name of a method "
                        "of the decorated class: {0}. Got '{1}' instead".format(
                            obj, name,
                        )
                    )
            else:
                func = obj
    
            def decorate(function):
                """
                Apply a list/tuple of decorators if decorator is one. Decorator
                functions are applied so that the call order is the same as the
                order in which they appear in the iterable.
                """
                if hasattr(decorator, '__iter__'):
                    for dec in decorator[::-1]:
                        function = dec(function)
                    return function
                return decorator(function)
    
            def _wrapper(self, *args, **kwargs):
                @decorate
                def bound_func(*args2, **kwargs2):
                    return func.__get__(self, type(self))(*args2, **kwargs2)
                # bound_func has the signature that 'decorator' expects i.e.  no
                # 'self' argument, but it is a closure over self so it can call
                # 'func' correctly.
                return bound_func(*args, **kwargs)
            # In case 'decorator' adds attributes to the function it decorates, we
            # want to copy those. We don't have access to bound_func in this scope,
            # but we can cheat by using it on a dummy function.
    
            @decorate
            def dummy(*args, **kwargs):
                pass
            update_wrapper(_wrapper, dummy)
            # Need to preserve any existing attributes of 'func', including the name.
            update_wrapper(_wrapper, func)
    
            if is_class:
                setattr(obj, name, _wrapper)
                return obj
    
            return _wrapper
        # Don't worry about making _dec look similar to a list/tuple as it's rather
        # meaningless.
        if not hasattr(decorator, '__iter__'):
            update_wrapper(_dec, decorator)
        # Change the name to aid debugging.
        if hasattr(decorator, '__name__'):
            _dec.__name__ = 'method_decorator(%s)' % decorator.__name__
        else:
            _dec.__name__ = 'method_decorator(%s)' % decorator.__class__.__name__
        return _dec
    
    
    def decorator_from_middleware_with_args(middleware_class):
        """
        Like decorator_from_middleware, but return a function
        that accepts the arguments to be passed to the middleware_class.
        Use like::
    
             cache_page = decorator_from_middleware_with_args(CacheMiddleware)
             # ...
    
             @cache_page(3600)
             def my_view(request):
                 # ...
        """
        return make_middleware_decorator(middleware_class)
    
    
    def decorator_from_middleware(middleware_class):
        """
        Given a middleware class (not an instance), return a view decorator. This
        lets you use middleware functionality on a per-view basis. The middleware
        is created with no params passed.
        """
        return make_middleware_decorator(middleware_class)()
    
    
    # Unused, for backwards compatibility in Django 2.0.
    def available_attrs(fn):
        """
        Return the list of functools-wrappable attributes on a callable.
        This was required as a workaround for http://bugs.python.org/issue3445
        under Python 2.
        """
        return WRAPPER_ASSIGNMENTS
    
    
    def make_middleware_decorator(middleware_class):
        def _make_decorator(*m_args, **m_kwargs):
            middleware = middleware_class(*m_args, **m_kwargs)
    
            def _decorator(view_func):
                @wraps(view_func)
                def _wrapped_view(request, *args, **kwargs):
                    if hasattr(middleware, 'process_request'):
                        result = middleware.process_request(request)
                        if result is not None:
                            return result
                    if hasattr(middleware, 'process_view'):
                        result = middleware.process_view(request, view_func, args, kwargs)
                        if result is not None:
                            return result
                    try:
                        response = view_func(request, *args, **kwargs)
                    except Exception as e:
                        if hasattr(middleware, 'process_exception'):
                            result = middleware.process_exception(request, e)
                            if result is not None:
                                return result
                        raise
                    if hasattr(response, 'render') and callable(response.render):
                        if hasattr(middleware, 'process_template_response'):
                            response = middleware.process_template_response(request, response)
                        # Defer running of process_response until after the template
                        # has been rendered:
                        if hasattr(middleware, 'process_response'):
                            def callback(response):
                                return middleware.process_response(request, response)
                            response.add_post_render_callback(callback)
                    else:
                        if hasattr(middleware, 'process_response'):
                            return middleware.process_response(request, response)
                    return response
                return _wrapped_view
            return _decorator
        return _make_decorator
    
    
    class classproperty:
        def __init__(self, method=None):
            self.fget = method
    
        def __get__(self, instance, cls=None):
            return self.fget(cls)
    
        def getter(self, method):
            self.fget = method
            return self
    装饰器

      请求处理装饰:

    from django.shortcuts import render, redirect
    from django.views.generic.base import View
    from django.utils.decorators import method_decorator
    
    def host(func):
        def fn(request, *args, **kwargs):
            print(request.get_host())
            return func(request, *args, **kwargs)
        return fn
    def get(func):
        def fn(request, *args, **kwargs):
            print(request.method)
            return func(request, *args, **kwargs)
        return fn
    
    class Login(View):
        @method_decorator(host)  # 给dispatch添加装饰器,所有请求方式都执行
        def dispatch(self, request, *args, **kwargs):
            return super().dispatch(request, *args, **kwargs)   # dispatch反射,一定要return
    
        @method_decorator(get)   # 给单个请求添加装饰器
        def get(self, request, *args, **kwargs):
            return render(request, 'app04/login.html', {"msg": ''})
    
        def post(self, request, *args, **kwargs):
            user= request.POST.get("user", False)
            pwd = request.POST.get("pwd", False)
            if user == "root" and pwd == "root":
                request.session["username"] = user
                return redirect("index")
            else:
                msg = "用户名或密码错误"
                return render(request, 'app04/login.html', {"msg": ''})

      上面可以重写,把装饰器写在类上,并指定给要装饰的函数

    from django.shortcuts import render, redirect
    from django.views.generic.base import View
    from django.utils.decorators import method_decorator
    
    
    def host(func):
        def fn(request, *args, **kwargs):
            print(request.get_host())
            return func(request, *args, **kwargs)
        return fn
    def get(func):
        def fn(request, *args, **kwargs):
            print(request.method)
            return func(request, *args, **kwargs)
        return fn
    
    @method_decorator(get, name="get")   # 给单个请求添加装饰器
    @method_decorator(host, name="dispatch")  # 给dispatch添加装饰器,所有请求方式都执行;一定要通过name指定要装饰的函数
    class Login(View):
    
        def dispatch(self, request, *args, **kwargs):
            return super().dispatch(request, *args, **kwargs)   # dispatch反射,一定要return
        def get(self, request, *args, **kwargs):
            return render(request, 'app04/login.html', {"msg": ''})
        def post(self, request, *args, **kwargs):
            user= request.POST.get("user", False)
            pwd = request.POST.get("pwd", False)
            if user == "root" and pwd == "root":
                request.session["username"] = user
                return redirect("index")
            else:
                msg = "用户名或密码错误"
                return render(request, 'app04/login.html', {"msg": ''})

     四、view类及其子类

      view相关类在django.views.generic文件下。它通过__all__指明了所有可以调用的视图函数及其子类。其中的"View"位于.base.py文件里,是其它类的基类,它的相关内容已经在上文做了标注。其它子类用于辅助快速构建视图函数。

    # django.views.generic.__init__.py
    from django.views.generic.base import RedirectView, TemplateView, View
    from django.views.generic.dates import (
        ArchiveIndexView, DateDetailView, DayArchiveView, MonthArchiveView,
        TodayArchiveView, WeekArchiveView, YearArchiveView,
    )
    from django.views.generic.detail import DetailView
    from django.views.generic.edit import (
        CreateView, DeleteView, FormView, UpdateView,
    )
    from django.views.generic.list import ListView
    
    __all__ = [
        'View', 'TemplateView', 'RedirectView', 'ArchiveIndexView',
        'YearArchiveView', 'MonthArchiveView', 'WeekArchiveView', 'DayArchiveView',
        'TodayArchiveView', 'DateDetailView', 'DetailView', 'FormView',
        'CreateView', 'UpdateView', 'DeleteView', 'ListView', 'GenericViewError',
    ]
    
    class GenericViewError(Exception):
        """A problem in a generic view."""
        pass

      1.TemplateView

      用于在get请求时直接返回一个静态模板。源码如下:

    class TemplateView(TemplateResponseMixin, ContextMixin, View):   # 只支持get
        """
        Render a template. Pass keyword arguments from the URLconf to the context.
        """
        def get(self, request, *args, **kwargs):
            context = self.get_context_data(**kwargs)
            return self.render_to_response(context)

      用例:

    # app01/urls.py
    from django.urls import path
    from django.views.generic import TemplateView
    urlpatterns = [
        path("example1", TemplateView.as_view(template_name="app01/example1.html")),   # 不用在app01/views.py中写视图函数
    ]
    # templates/app01/example1.html
    ...
    <h1>this is a example1</h1>
    ...

      2.ListView

      给静态文件传递queryset列表。源码如下:

    class ListView(MultipleObjectTemplateResponseMixin, BaseListView):   # 继承了这两个父类
        """
        Render some list of objects, set by `self.model` or `self.queryset`.
        `self.queryset` can actually be any iterable of items, not just a queryset.
        """

      用例:

    # app01/urls.py
    from django.urls import path
    from .models import Book
    from django.views.generic import *
    bookinfo = {
        'queryset': Book.objects.all(),    # Book.objects.all去掉括号,会动态地进行查询
        'template_name': "app01/example2.html"
    }
    
    urlpatterns = [
        path("example1", TemplateView.as_view(template_name="app01/example1.html")),
        path("example2", ListView.as_view(**bookinfo)),
    ]
    
    # templates/app01/example2.py
    ...
        <p>example2</p>
        {% for obj in object_list %}    # 注意这里的object_list,它是默认的变量名,即对应bookinfo里的queryset.
            <p>{{ obj.id }}, {{ obj.name }}</p>
        {% endfor %}
    ...

      它其实等价于:

    # app01/urls.py
    
    from django.urls import path
    from .views import *
    urlpatterns = [
        path("example3", BookListView.as_view(),)
    ]
    
    # app01/views.py
    from django.views.generic import ListView
    from .models import Book
    class BookListView(ListView):
        model = Book
        queryset = Book.objects.all()
        template_name = "app01/example2.html"
    
        def get_context_data(self, *, object_list=None, **kwargs):
            return super().get_context_data(**kwargs)
  • 相关阅读:
    typescript接口初识
    TypeScript如何创建一个工程
    typescript开发入门
    node.js下面创建一个express应用的几条命令【乱序版】
    一天入门typescript
    Node.js快速创建一个Express应用的几个步骤
    数据结构--栈
    数据结构--单链表
    数据结构--二叉树
    数据结构--树
  • 原文地址:https://www.cnblogs.com/kuaizifeng/p/9532520.html
Copyright © 2011-2022 走看看