• drf 视图使用及源码分析


    前言

       drf视图的源码非常的绕,但是实现的功能却非常的神奇。

       它能够帮你快速的解决ORM增删改查的重复代码,非常的方便好用。

       下面是它源码中的一句话:

    class ViewSetMixin:
        """
        This is the magic.
        """
    

       好了,屁话不多说,直接看看drf视图中的功能吧。

    准备工作

       此次的Django采用3版本,所以相对于1版本来说有一些差异。

    模型表

       下面是模型表:

    from django.db import models
    
    # Create your models here.
    class User(models.Model):
        user_id = models.AutoField(primary_key=True)
        user_name = models.CharField(max_length=50)
        user_gender = models.BooleanField(
            [(0,"male"),(1,"female")],
            default = 0,
        )
        user_age = models.IntegerField()
    
        def __str__(self):
            return self.user_name
    
        class Meta:
            db_table = ''
            managed = True
            verbose_name = 'User'
            verbose_name_plural = 'Users'
    

       数据如下:

    INSERT INTO app01_user(user_name,user_age,user_gender) VALUES
        ("用户1",18,0),
        ("用户2",19,1),
        ("用户3",19,1);
    

    序列类

       序列类采用ModelSerializer

    from rest_framework import serializers
    from . import models
    
    class UserModelSerializers(serializers.ModelSerializer):
        
        class Meta:
            model = models.User
            fields = "__all__"
    

    url路由

       下面是url路由的设定:

    re_path('^api/users/(?P<uid>d+)?',views.UserAPI.as_view())
    

    封装Rsponse

       由于不需要返回原生的Response,所以我们封装了一个类,用于更加方便的返回Response

    class ResponseMeta(type):
        # 对Response类做封装
        def __call__(cls, *args, **kwargs):
            obj = cls.__new__(cls, *args, **kwargs)
            cls.__init__(obj, *args, **kwargs)
            return Response(data=obj.__dict__)
    
    
    class CommonResponse(object, metaclass=ResponseMeta):
        # 返回的信息
        def __init__(self, status, data=None, errors=None):
            self.status = status
            self.data = data
            self.errors = errors
    

    APIView

    继承关系

       APIView的导入如下:

    from rest_framework.views import APIView
    

       APIView继承了原生的DjangoView,在其之上做了一些封装,使操作更加简单。

       image-20201028161439038

    封装特性

       在APIView中对原生的request对象进行封装,最常用的两个属性如下,它弥补了Django原生ViewJSON请求格式的数据没有处理的缺陷。

       同时,APIView认为对于GET请求的资源参数,不应该使用GET获取,而是应该使用query_params进行获取。

    属性描述
    request.data 当请求数据为Json格式时,将以dict形式保存,主要针对request.POST请求
    request.query_params 当请求方式为GET时,可获取url中的请求数据

    接口书写

       以下是使用APIViewUser表进行增删改查的接口书写。

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.request import Request
    from . import models
    from . import ser
    
    
    class ResponseMeta(type):
        # 对Response类做封装
        def __call__(cls, *args, **kwargs):
            obj = cls.__new__(cls, *args, **kwargs)
            cls.__init__(obj, *args, **kwargs)
            return Response(data=obj.__dict__)
    
    
    class CommonResponse(object, metaclass=ResponseMeta):
        # 返回的信息
        def __init__(self, status, data=None, errors=None):
            self.status = status
            self.data = data
            self.errors = errors
    
    
    class UserAPI(APIView):
        def get(self, request, uid=None):
            if not uid:
                # 获取所有
                user_queryset = models.User.objects.all()
                if user_queryset.exists():
                    serialization = ser.UserModelSerializers(instance=user_queryset, many=True)
                    return CommonResponse(status=100, data=serialization.data, errors=None)
                return CommonResponse(status=200, errors="暂时没有任何学生")
            else:
                user_obj = models.User.objects.filter(pk=uid).first()
                if user_obj:
                    serialization = ser.UserModelSerializers(instance=user_obj)
                    return CommonResponse(status=100, data=serialization.data, errors=None)
                return CommonResponse(status=200, errors="没有该学生")
    
        def post(self, request):
    
            serialization = ser.UserModelSerializers(data=request.data)
            if serialization.is_valid():
                serialization.save()
                return CommonResponse(status=100, data=serialization.data, errors=None)
            return CommonResponse(status=200, errors=serialization.errors)
    
        def patch(self, request, uid):
            user_obj = models.User.objects.filter(pk=uid).first()
            if user_obj:
                serialization = ser.UserModelSerializers(instance=user_obj, data=request.data)
                if serialization.is_valid():
                    serialization.save()
                    return CommonResponse(status=100, data=serialization.data, errors=None)
                else:
                    return CommonResponse(status=200, errors="修改失败,请检查字段是否一直")
            else:
                return CommonResponse(status=200, errors="修改失败,请检查该用户是否存在")
    
        def delete(self,request,uid):
            models.User.objects.get(pk=uid).delete()
            return CommonResponse(status=100,data="删除成功",errors=None)
    

    问题发现

       在上述代码中,问题有以下几点:

    1. 重复代码多,在每个接口中都需要书写ORM查询
    2. 每个接口都需要针对同一个序列类做出不同的实例化

    GenericAPIView

    继承关系

       GenericAPIView的导入如下:

    from rest_framework.generics import GenericAPIView
    

       以下是它的继承关系:

       image-20201028161722179

       可以发现它是对APIView的继承,所以理论上来说应该又多了一些东西。

    源码阅读

       下面来看一下GenericAPIView的源码,首先你可以发现大概有4个类属性:

    class GenericAPIView(views.APIView):
    
        queryset = None  # 要查询的数据表
        serializer_class = None  # 执行序列化的序列化类
    
        lookup_field = 'pk'  # 查询时的查询条件,默认按主键查询
        lookup_url_kwarg = None  # 如果在视图中,url捕获的查询数据表过滤参数不是pk,你应该进行声明
    

       接着往下看,其实它的方法很少,对外暴露的方法就更少了。

       image-20201028142929380

       我们这里就先看最常用的,即对外暴露的方法,首先是get_queryset()

        def get_queryset(self):
    
            assert self.queryset is not None, (
                "'%s' should either include a `queryset` attribute, "
                "or override the `get_queryset()` method."
                % self.__class__.__name__
            )  # 做验证,即实例属性queryset不能是空,代表这个类属性你必须要声明,你可以选择将它做成类属性也可以做成实例属性
    
            queryset = self.queryset  # 进行赋值,将self.queryset赋值为类属性。先在UserAPI的实例中找,找不到再到UserAPI的类中找
            if isinstance(queryset, QuerySet):
                queryset = queryset.all()  # 如果它是一个QuerySET对象,就获取全部,得到一个QuerySetDict对象
            return queryset  # 进行返回
    

       看到这里发现了一个点,即queryset这个属性必须要进行赋值,由于属性查找顺序是先查找实例,而后查找类本身,所以我们直接在UserAPI中声明queryset为类属性即可。

       接下来继续继续看,get_object(),见明知意,它可以从数据表中获取单个对象:

        def get_object(self):
            queryset = self.filter_queryset(self.get_queryset())  # 首先会运行get_queryset(),获取一个所有对象的列表,然后进行filter过滤
            lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field  # 这里是对url中获取到的变量进行映射
    
            assert lookup_url_kwarg in self.kwargs, (  # 比如,url中获取到的名为uid,如果uid没有在kwargs中,即是{uid:4}中,则抛出异常
                'Expected view %s to be called with a URL keyword argument '
                'named "%s". Fix your URL conf, or set the `.lookup_field` '
                'attribute on the view correctly.' %
                (self.__class__.__name__, lookup_url_kwarg)
            )
    
            filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}  # 进行查询替换,默认的self.lookup_field是pk,将uid替换为pk。那么这里就是{pk:4}
            obj = get_object_or_404(queryset, **filter_kwargs) # 获取对象
            self.check_object_permissions(self.request, obj)  # 进行验证权限
    
            return obj  # 返回单个对象
    

       看到这里就发现了,默认查询条件是用pk,也就是说你的url中必须要用pk这个形参名进行分组捕获。否则就需要声明lookup_url_kwarg,即lokup_url_kwarg="uid",然后进行替换组建filter_kwargs。当然如果你的查询条件不是用的pk,就需要修改lookup_field为字段名,如我不是按照pk进行查询,而是按照name,就修改lookup_fieldname

    re_path('^api/users/(?P<uid>d+)?',views.UserAPI.as_view())
    

       接下来再看另一个方法get_serializer()

       def get_serializer(self, *args, **kwargs):
            serializer_class = self.get_serializer_class()  # 内部调get_serializer_class
            kwargs.setdefault('context', self.get_serializer_context())  # 获取context属性
            return serializer_class(*args, **kwargs)  # 调用serializer_class并返回
    

       接下来是get_serializer_class()方法:

        def get_serializer_class(self):
        
            assert self.serializer_class is not None, (  # 传入的必须不能是None
                "'%s' should either include a `serializer_class` attribute, "
                "or override the `get_serializer_class()` method."
                % self.__class__.__name__
            )
    
            return self.serializer_class  # 返回设置的属性,serializer_class,即序列化类
    

       OK,其实源代码读到这里就行了。

    封装特性

       通过上面的源代码分析,总结出如下方法的使用:

    方法/属性描述
    queryset 将要查询的数据表,类型应该是QuerySet
    serializer_class 将要执行的序列化类,类型不能为None
    lookup_field 查询时的查询条件,默认为pk
    lookup_url_kwarg 视图中url捕获的查询条件变量名如果不是pk,则应该进行指定
    get_queryset() 查询获取所有记录
    get_object() 查询获取单条记录
    get_serializer() 执行序列化对象

       关于最常用的调用方法就三个,常用属性四个。

       其他的方法基本上都是内部调用,所以暂时不深究。

    接口书写

       接下来使用GenericAPIView进行接口书写。

    from rest_framework.response import Response
    from rest_framework.generics import GenericAPIView
    from . import models
    from . import ser
    
    
    class ResponseMeta(type):
        # 对Response类做封装
        def __call__(cls, *args, **kwargs):
            obj = cls.__new__(cls, *args, **kwargs)
            cls.__init__(obj, *args, **kwargs)
            return Response(data=obj.__dict__)
    
    
    class CommonResponse(object, metaclass=ResponseMeta):
        # 返回的信息
        def __init__(self, status, data=None, errors=None):
            self.status = status
            self.data = data
            self.errors = errors
    
    
    class UserAPI(GenericAPIView):
        queryset = models.User.objects  # 传入对象即可,但是更推荐加上all(),它会避免一些问题
        serializer_class = ser.UserModelSerializers  # 序列化类
        lookup_field = "pk"
        lookup_url_kwarg = "uid"  # 由于捕获的是uid,需要声明
    
        def get(self, request, uid=None):
            if not uid:
                # 获取所有
                user_queryset = self.get_queryset()  # 获取所有
                if user_queryset.exists():
                    serialization = self.get_serializer(instance=user_queryset,many=True)  # 获取序列化类,序列化多条
                    return CommonResponse(status=100, data=serialization.data, errors=None)
                return CommonResponse(status=200, errors="暂时没有任何学生")
    
            else:
                user_obj = self.get_object()
                if user_obj:
                    serialization = self.get_serializer(instance=user_obj)
                    return CommonResponse(status=100, data=serialization.data, errors=None)
                return CommonResponse(status=200, errors="没有该学生")
    
        def post(self, request):
    
            serialization = self.get_serializer(data=request.data)
            if serialization.is_valid():
                serialization.save()
                return CommonResponse(status=100, data=serialization.data, errors=None)
            return CommonResponse(status=200, errors=serialization.errors)
    
        def patch(self, request, uid):
            user_obj = self.get_object()
            if user_obj:
                serialization = self.get_serializer(instance=user_obj,data=request.data)
                if serialization.is_valid():
                    serialization.save()
                    return CommonResponse(status=100, data=serialization.data, errors=None)
                else:
                    return CommonResponse(status=200, errors="修改失败,请检查字段是否一直")
            else:
                return CommonResponse(status=200, errors="修改失败,请检查该用户是否存在")
    
        def delete(self,request,uid):
            self.get_object().delete()
            return CommonResponse(status=100,data="删除成功",errors=None)
    

    问题发现

       相对于使用APIView来说,它不必再手动去写ORM语句。

       但是对于返回信息、对于验证操作还是要自己写。

    mixins中扩展类

    五个扩展类

       下面是rest_framework.mixins中的五个扩展类,它们做了更高级别的封装,配合GenericAPIView使用有奇效。

    from rest_framework.mixins import ListModelMixin,RetrieveModelMixin,CreateModelMixin,UpdateModelMixin,DestroyModelMixin
    
    描述
    ListModelMixin 该类主要负责查询所有记录
    RetrieveModelMixin 该类主要负责查询单条记录
    CreateModelMixin 该类主要负责创建记录
    UpdateModelMixin 该类主要负责对记录做更新操作
    DestroyModelMixin 该类主要负责删除记录

    继承关系

       这五个类都继承于object,是独立的子类。

       image-20201028171401608

    源码阅读

       下面是ListModelMixin的源码,不难发现,它就是配合GenericAPIView使用的,因为它会使用get_queryset()方法,并且,它会自动的返回Response对象,并把验证结果添加进去:

    class ListModelMixin:
    
        def list(self, request, *args, **kwargs):
            queryset = self.filter_queryset(self.get_queryset())
    
            page = self.paginate_queryset(queryset)
            if page is not None:
                serializer = self.get_serializer(page, many=True)
                return self.get_paginated_response(serializer.data)
    
            serializer = self.get_serializer(queryset, many=True)
            return Response(serializer.data)
    

       至于其他几个类,其实都差不多,这里摘出两个比较特别的类来看一下,分别是CreateModelMixinDestroyModelMixin这两个类。

       下面是CreateModelMixin类的源码:

    class CreateModelMixin:
    
        def create(self, request, *args, **kwargs):
            serializer = self.get_serializer(data=request.data)
            serializer.is_valid(raise_exception=True)
            self.perform_create(serializer)  # 内部进行序列化类保存
            headers = self.get_success_headers(serializer.data)  # 返回一个location请求头
            return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)  # 注意返回结果,状态码是201
    
        def perform_create(self, serializer):
            serializer.save()
    
        def get_success_headers(self, data):
            try:
                return {'Location': str(data[api_settings.URL_FIELD_NAME])}
            except (TypeError, KeyError):
                return {}
    

       下面是DestroyModelMixin类的源码:

    class DestroyModelMixin:
        def destroy(self, request, *args, **kwargs):
            instance = self.get_object()
            self.perform_destroy(instance)  # 内部执行删除
            return Response(status=status.HTTP_204_NO_CONTENT)  # 返回状态码204
    
        def perform_destroy(self, instance):
            instance.delete()
    

       那么读这两个类的源码,就是想要让你知道,创建成功后的返回状态码是201,而删除成功的返回状态码是204。这在REST规范中写的很清楚,可以看见这里也是这么做的。

    类与方法

       下面是不同的五个扩展类中不同的五个方法,功能与类一样。

    方法描述
    ListModelMixin list() 查询所有,并返回Response对象
    RetrieveModelMixin retrieve() 查询单条,并返回Response对象
    CreateModelMixin create() 创建记录,并返回Response对象
    UpdateModelMixin update() 更新记录,并返回Response对象
    DestroyModelMixin destroy() 删除记录,并返回Response对象

       由于它会自动进行return Response(),所以我们就不用再对返回对象进行包装了。

    接口书写

       下面是利用GenericAPIViewmixins中的五个扩展类进行接口书写。

    from . import models
    from . import ser
    from rest_framework.generics import GenericAPIView
    from rest_framework.mixins import ListModelMixin,RetrieveModelMixin,CreateModelMixin,UpdateModelMixin,DestroyModelMixin
    
    class UserAPI(GenericAPIView,ListModelMixin,RetrieveModelMixin,CreateModelMixin,UpdateModelMixin,DestroyModelMixin):
    
        queryset = models.User.objects  # 传入对象即可,但是更推荐加上all(),这会避免一些问题
        serializer_class = ser.UserModelSerializers  # 序列化类
        lookup_field = "pk"
        lookup_url_kwarg = "uid"  # 由于捕获的是uid,需要声明
    
        def get(self, request, uid=None):
            if not uid:
                # 获取所有
                return self.list(request)
    
            else:
                return self.retrieve(request,uid)
    
        def post(self, request):
            return self.create(request)
    
        def patch(self, request, uid):
            return self.update(request,uid)
    
        def delete(self,request,uid):
            return self.destroy(request,uid)
    

    问题发现

       可以看见,代码相比于前两个少了非常非常多。但是还是存在一些问题。

       第一个问题就是这个视图UserAPI继承的类太多了,太长了,其次就是每次都需要在视图中return,它能不能帮我们自己return呢?那这个就非常舒服了。

    modelViewSet

    基本使用

       modelViewSet是针对GenericAPIViewmixins中扩展类的结合使用做了一些优化,它可以根据不同的请求自动的做出回应。

       同时也不再需要你在视图中进行return。以下是基本使用方法,但是使用它时我们需要对路由做一些改进,具体的情况等下面的源码分析后你就明白了:

    urlpatterns = [
        path('admin/', admin.site.urls),
        path('api/users/', views.UserAPI.as_view(actions={"get":"list","post":"create"})),
        re_path('^api/users/(?P<uid>d+)?',views.UserAPI.as_view(actions={"get":"retrieve","patch":"update","delete":"destroy"}))
    ]
    

       那么在views.py中,书写的话很简单:

    from . import models
    from . import ser
    from rest_framework.viewsets import ModelViewSet
    class UserAPI(ModelViewSet):
        queryset = models.User.objects  # 传入对象即可,但是更推荐加上all(),这会避免一些问题
        serializer_class = ser.UserModelSerializers  # 序列化类
        lookup_field = "pk"
        lookup_url_kwarg = "uid"  # 由于捕获的是uid,需要声明
    
    

    继承关系

       ModelViewSet的导入如下:

    from rest_framework.viewsets import ModelViewSet
    

       你可看它的源码,它其实也没什么特别之处,就是针对上面第一个问题做了改进。但是你会发现,它会继承一个新的类,即GenericViewSet这个类。

    class ModelViewSet(mixins.CreateModelMixin,
                       mixins.RetrieveModelMixin,
                       mixins.UpdateModelMixin,
                       mixins.DestroyModelMixin,
                       mixins.ListModelMixin,
                       GenericViewSet):
        """
        A viewset that provides default `create()`, `retrieve()`, `update()`,
        `partial_update()`, `destroy()` and `list()` actions.
        """
        pass
    

       下面是它的继承图:

       image-20201028174903534

       那么GenericViewSet中又会有什么新的发现呢?我们先看一看它。

    GenericViewSet

       打开GenericViewSet中发现什么都没有。但是它继承了ViewSetMixin

    class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
        """
        The GenericViewSet class does not provide any actions by default,
        but does include the base set of generic view behavior, such as
        the `get_object` and `get_queryset` methods.
        """
        pass
    

    ViewSetMixin

       我们可以在上面基本使用时对urlas_view()传参发现了一点不一样的地方,我们传递进了一个关键字参数actions,这个参数非常的蹊跷,因为在APIView中的as_view()方法中并没有为该参数预留位置。

    def as_view(cls, **initkwargs):
    

       我们再接着看看GenericAPIView中的as_view()方法有没有为该参数预留位置,非常遗憾的是在GenericAPIView中根本就没有as_view()方法,说明它用了父类也就是APIViewas_view()方法

       那么只有一个可能,就是ViewSetMixin覆写了as_view()方法,那么到底是不是这么回事?我们看一下就知道了:

    class ViewSetMixin:
    
        @classonlymethod
        def as_view(cls, actions=None, **initkwargs):
    

       是的,那么它内部是怎么做的呢?实际上它的核心代码就是那个for循环,它会根据不同的请求方式来执行不同的mixins中五个扩展类的方法,因此我们需要两条url来放入不同的actions。由于modelsViewSet继承了mixins五个扩展类,所以才能够调用扩展类下的方法。

        @classonlymethod 
        def as_view(cls, actions=None, **initkwargs):  # cls即为UserAPI这个类
            cls.name = None
            cls.description = None
            cls.suffix = None
            cls.detail = None
            cls.basename = None
            if not actions:  # 必须传入actions,否则抛出异常
                raise TypeError("The `actions` argument must be provided when "
                                "calling `.as_view()` on a ViewSet. For example "
                                "`.as_view({'get': 'list'})`")
            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" % (
                        cls.__name__, key))
    
            if 'name' in initkwargs and 'suffix' in initkwargs:  # 不用管,这个也是构造字典
                raise TypeError("%s() received both `name` and `suffix`, which are "
                                "mutually exclusive arguments." % (cls.__name__))
    
            def view(request, *args, **kwargs):  # 闭包函数view
                self = cls(**initkwargs)
    
                if 'get' in actions and 'head' not in actions:
                    actions['head'] = actions['get']
    
                self.action_map = actions
    
                for method, action in actions.items():  # 其实这里是核心代码,  actions={"get":"retrieve","patch":"update","delete":"destroy"},或者等于{"get":"list","post":"create"}
                    handler = getattr(self, action)  # 根据请求方式,来执行list、create、retrieve、update、destroy这几个方法
                    setattr(self, method, handler)
    
                self.request = request
                self.args = args
                self.kwargs = kwargs
    
                return self.dispatch(request, *args, **kwargs)
     
            update_wrapper(view, cls, updated=())  # 传入执行update_wrapper(),不用管
    
            update_wrapper(view, cls.dispatch, assigned=())   # 不用管
    
    
            view.cls = cls
            view.initkwargs = initkwargs
            view.actions = actions
            return csrf_exempt(view)
    

       根据不同的请求方式来执行不同的函数方法,可以说这个设计非常的巧妙,所以你可以像下面这样做:

    # views.py
    from rest_framework.viewsets import ViewSetMixin
    class Book6View(ViewSetMixin,APIView): # 一定要放在APIVIew前,因为as_view()的查找顺序一定要先是ViewSetMixin
        def get_all_book(self,request):
            print("xxxx")
            book_list = Book.objects.all()
            book_ser = BookSerializer(book_list, many=True)
            return Response(book_ser.data)
        
    # urls.py
        #继承ViewSetMixin的视图类,路由可以改写成这样
        path('books6/', views.Book6View.as_view(actions={'get': 'get_all_book'})),
    

    generics扩展类

       在generices这个模块中,不只有GenericAPIView,还有一些其他的扩展类。

       image-20201103144932782

       一共有9个,功能如下:

    功能
    CreateAPIView 创建记录
    DestroyAPIView 删除记录
    UpdateAPIView 更新单条记录
    ListAPIView 查询所有记录
    RetrieveAPIView 查询单条记录
    ListCreateAPIView 创建以及查询所有记录
    RetrieveUpdateAPIView 更新以及查询单条记录
    RetrieveDestroyAPIView 删除以及查询单条记录
    RetrieveUpdateDestroyAPIView 删除、更新、查询单条记录

       他们的实现也很简单,如RetrieveUpdateDestroyAPIView类举例:

    class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
                                       mixins.UpdateModelMixin,
                                       mixins.DestroyModelMixin,
                                       GenericAPIView):
        """
        Concrete view for retrieving, updating or deleting a model instance.
        """
        def get(self, request, *args, **kwargs):
            return self.retrieve(request, *args, **kwargs)
    
        def put(self, request, *args, **kwargs):
            return self.update(request, *args, **kwargs)
    
        def patch(self, request, *args, **kwargs):
            return self.partial_update(request, *args, **kwargs)
    
        def delete(self, request, *args, **kwargs):
            return self.destroy(request, *args, **kwargs)
    

       所以我们使用的时候只需要指定queryset以及serializer_class,这个接口就可以拥有 删除、更新、查询单条记录的功能了。

    from . import models
    from . import ser
    from rest_framework.generics import RetrieveUpdateDestroyAPIView
    
    class UserAPI(RetrieveUpdateDestroyAPIView):  
        queryset = models.User.objects  # 传入对象即可,但是更推荐加上all(),这会避免一些问题
        serializer_class = ser.UserModelSerializers  # 序列化类
        lookup_field = "pk"
        lookup_url_kwarg = "uid"  # 由于捕获的是uid,需要声明
    
    
  • 相关阅读:
    JavaScript 操作注意事项(此日志持续更新)
    JavaScript省市级联
    Outlook 2013 中添加 live.cn 帐户
    readonly, const, static, static readonly 关键字实例说明
    Windows 8 应用开发常见问题及解决方案(持续更新)
    【转】用C#动态创建Access数据库
    使用 Layer 弹出 iframe 层,并让 iframe 自适应内容宽高
    Visual Studio 常见问题及解决方案(持续更新)
    Python核心编程学习日记之模块
    Python核心编程学习日记之函数式编程
  • 原文地址:https://www.cnblogs.com/Yunya-Cnblogs/p/13893085.html
Copyright © 2020-2023  润新知