Django Rest Framework 源碼探究(三 其它部分)

RESTful API 設計指南

restful api規範開發指南:

  1. 協議 建議使用HTTPS
  2. 域名 專用域名
  3. 版本 建議:https://api.example.com/v1/
  4. 路徑
  5. http動詞
    對於資源的具體操做類型,由HTTP動詞表示。
    經常使用的HTTP動詞有下面五個(括號裏是對應的SQL命令)。
    GET(SELECT):從服務器取出資源(一項或多項)。
    POST(CREATE):在服務器新建一個資源。
    PUT(UPDATE):在服務器更新資源(客戶端提供改變後的完整資源)。
    PATCH(UPDATE):在服務器更新資源(客戶端提供改變的屬性)。
    DELETE(DELETE):從服務器刪除資源。
    還有兩個不經常使用的HTTP動詞。
    HEAD:獲取資源的元數據。
    OPTIONS:獲取信息,關於資源的哪些屬性是客戶端能夠改變的。
  6. 過濾信息
?limit=10:指定返回記錄的數量
?offset=10:指定返回記錄的開始位置。
?page=2&per_page=100:指定第幾頁,以及每頁的記錄數。
?sortby=name&order=asc:指定返回結果按照哪一個屬性排序,以及排序順序。
?animal_type_id=1:指定篩選條件
  1. 狀態碼 200 400 404 操做是冪等的 ,冪等 http狀態碼以外,能夠本身設計code
  2. 錯誤處理
  3. 返回結果
  4. Hypermedia API

djangorestframework

  • 看源碼時要注意的問題
  1. python3默認都是新式類,繼承遵循深度優先原則
  2. 執行時先順杆爬執行全部類的new方法,爬杆時深度優先從左至右 不重複 不重複的意思是,來的時候路過,再日後找的時候就不走這了
  3. 先執行全部的new方法,而後原路返回(倒序)執行全部的init方法

知乎連接html

Python的多重繼承正如文檔所言是深度優先從左至右不重複
在Python裏,當你新構造一個對象時,有兩個步驟:首先是自底向上,從左至右調用__new__,而後再依照遞歸棧依次調用__init__

1、認證

源碼流程分析

2、權限

3、節流

  • 認證、權限、節流三者思路同樣,明白一個就全通了。

4、版本

版本控制有什麼用

  1. 更方便的提取當前請求所使用的版本
  2. 能夠經過restful 內置的reverse反向生成url

使用

單個視圖使用: class 類中指定: versioning_class = QueryParameterVersioning 全局使用:python

settings中配置生效,全局使用

REST_FRAMEWORK = {
    "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
    "DEFAULT_VERSION":'v1',
    "ALLOWED_VERSIONS":['v1','v2'],
    "VERSION_PARAM":'version',
    "DEFAULT_PARSER_CLASSES":['rest_framework.parsers.JSONParser','rest_framework.parsers.FormParser']
}

E:\space_env\env_for_blog\Lib\site-packages\rest_framework\versioning.py 一個基類,5個版本控制類:數據庫

BaseVersioning QueryParameterVersioning(BaseVersioning) URLPathVersioning(BaseVersioning) AcceptHeaderVersioning(BaseVersioning) HostNameVersioning(BaseVersioning) NamespaceVersioning(BaseVersioning)django

源碼探究

BaseVersioning 中有三個函數 def determine_version 返回值是 version 子類覆寫的鉤子 def reverse 反向生成url,每一個子類生成的方式不同 def is_allowed_version 給後邊子類提供可支持的版本號啊json

class URLPathVersioning(BaseVersioning):
    # 版本錯誤信息
    invalid_version_message = _('Invalid version in URL path.')
    def determine_version()
        ···
        return version
    def reverse():
        return父類的reverse方法

探究後端

APIView -- dispatch -- self.initial(request, *args, **kwargs)
    # Determine the API version, if versioning is in use.
    version, scheme = self.determine_version(request, *args, **kwargs)
    request.version, request.versioning_scheme = version, scheme
        def determine_version(self, request, *args, **kwargs):
            """
            返回元組: (version, versioning_scheme)
            """
            if self.versioning_class is None:
                return (None, None)
            scheme = self.versioning_class()
            return (scheme.determine_version(request, *args, **kwargs), scheme)
self.initial傳入的是新request
在APIView - def determine_version裏邊:獲取了version版本, versioning_scheme版本類對象
而後把這倆值封裝到request參數裏邊

        # 獲取版本
        print(request.version)
        # 獲取處理版本的對象
        print(request.versioning_scheme)

reverse 利用restful - reverseapi

# 反向生成URL(rest framework)
    u1 = request.versioning_scheme.reverse(viewname='uuu',request=request)
    print(u1)

利用django自帶的reverse服務器

# 反向生成URL
     u2 = reverse(viewname='uuu',kwargs={'version':2})
     print(u2)
     這個須要本身指定kwargs

傳參方式 - 解析方式

  • 基於url傳參 如:/users?version=v1
  • 基於url的正則方式 如:/v1/users/
  • 基於 accept 請求頭方式 如:Accept: application/json; version=1.0
  • 基於主機名方法 也就是請求域名 如:v1.example.com
  • 基於django路由系統的namespace 如:example.com/v1/users/

基於url傳參

對應的類是:versioning_class = QueryParameterVersioning http://127.0.0.1:8000/api/users/?version=v2restful

基於url路徑正則

對應類:URLPathVersioning(BaseVersioning) url(r'^(?P<version>[v1|v2]+)/users/$', views.UsersView.as_view(),name='uuu'), http://127.0.0.1:8000/api/users/?version=v2網絡

  • 其它幾種使用相似,基本也用不到

【注意】 配置或測試URL時,須要保證所輸入的URL符合規則 也就是說,變量得是那個v1/v2 才能順利匹配版本/返回invalid_version_message 若是連URL開頭都沒匹配上的話,那隻能404了,壓根走不到版本控制的代碼處


5、解析器

HTTP協議須知

後端要想成功的解析出數據,有兩點要求: 1. 請求頭要寫對,好比 Content-Type: application/x-www-form-urlencoded 2. 數據格式要與請求頭標識的一致

form提交時:Content-Type: application/x-www-form-urlencoded 後端:request.POST從中取值 application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值 其他狀況,須要request.body取值

啥用

rest_framework 解析器,對請求體數據進行解析

BaseParser 基類 沒卵用 JSONParser media_type = 'application/json' FormParser media_type = 'application/x-www-form-urlencoded' MultiPartParser media_type = 'multipart/form-data' Parser for multipart form data, which may include file data. 文件傳輸,也用這個解析 FileUploadParser media_type = '/' 這個貌似是上傳數據用的,用到再看

使用

class ParserView(APIView):
    parser_classes = [JSONParser,FormParser,]
    """
    JSONParser:表示只能解析content-type:application/json頭
    JSONParser:表示只能解析content-type:application/x-www-form-urlencoded頭
    """
    def post(self,request,*args,**kwargs):
        print(request.data)
        return HttpResponse('ParserView返回')

源碼流程

  1. 獲取用戶請求
  2. 獲取用戶請求體
  3. 根據用戶請求頭 和 parser_classes = [JSONParser,FormParser,] 中支持的請求頭進行比較
  4. JSONParser對象去請求體
  5. request.data 真正調用

封裝參數 dispatch - self.initialize_request - parsers=self.get_parsers() request.data Request() 類 >>> def data()方法 >>> _load_data_and_files is_form_media_type 直接copy 或者 self._parse() media_type = self.content_type - 獲取content_type stream = self.stream - 調用Request/stream()獲取body數據 parser = self.negotiator.select_parser(self, self.parsers) - 獲取設置的序列化類

parsed = parser.parse(stream, media_type, self.parser_context) - 序列化
    
    try:
        return (parsed.data, parsed.files)
    except AttributeError:
        empty_files = MultiValueDict()
        return (parsed, empty_files)

6、序列化Serializer

序列化是什麼

連接:什麼是序列化,爲何要序列化

【概念】:序列化就是一種用來處理對象流的機制,所謂對象流也就是將對象的內容進行流化,流的概念這裏不用多說(就是I/O), 咱們能夠對流化後的對象進行讀寫操做,也可將流化後的對象傳輸於網絡之間(注:要想將對象傳輸於網絡必須進行流化)! 在對對象流進行讀寫操做時會引起一些問題,而序列化機制正是用來解決這些問題的!

序列化器容許將諸如查詢集和模型實例之類的複雜數據轉換爲原生 Python 數據類型,而後能夠將它們輕鬆地呈現爲 JSON,XML 或其餘內容類型。 序列化器還提供反序列化,在首次驗證傳入數據以後,能夠將解析的數據轉換回複雜類型(並作驗證)。

REST framework 中的序列化類與 Django 的 Form 和 ModelForm 類很是類似。 咱們提供了一個 Serializer 類,它提供了一種強大的通用方法來控制響應的輸出,以及一個 ModelSerializer 類, 它爲建立處理模型實例和查詢集的序列化提供了有效的快捷方式。

序列化在django-rest-framework中的典型應用就是:
從數據庫中取數據,而後將其序列化爲支持網絡傳輸的表述(如json)
驗證,對請求發來的數據進行驗證.對發來的json串解析爲Serializer對應的字段 ser.is_valid()驗證,而後ser.validated_data取數據進行操做,或者ser.errors提取錯誤信息

咱們能夠經過聲明序列來完成,這些序列與Django的表單(forms)工做類似。 forms是DB(後端)與HTML的橋樑,serializers是DB(後端)與傳輸的轉換器 二者在設計思路和使用方式上有不少雷同,源碼流程也很類似

初步使用

原始方式

def get(self,request,*args,**kwargs):
    roles = models.Role.objects.all().values('id','title')
    roles = list(roles)
    ret = json.dumps(roles,ensure_ascii=False)

    return HttpResponse(ret)

【注意】ensure_ascii=False 表示不轉碼,就按照數據庫原始數據形式來發

方式二,使用serializers

class RolesSerializer(serializers.Serializer):
    # 字段名與數據庫名稱一致
    id = serializers.IntegerField()
    title = serializers.CharField()

class RolesView(APIView):
    def get(self,request,*args,**kwargs):
        roles = models.Role.objects.all()
        ser = RolesSerializer(instance=roles,many=True)
        ret = json.dumps(ser.data, ensure_ascii=False)
        
        # 對於單個對象,many=False
        role = models.Role.objects.all().first()
        ser = RolesSerializer(instance=role, many=False)
        # ser.data 已是轉換完成的結果
        ret = json.dumps(ser.data, ensure_ascii=False)

        return HttpResponse(ret)

print(ser.data,type(ser.data)) {'title': '開發', 'id': 1} <class 'rest_framework.utils.serializer_helpers.ReturnDict'> ser.data 是字典(有序字典)

自定義字段

user_type = serializers.CharField() 等價於 xxxxx = serializers.CharField(source="user_type") 轉換完的字段名 user_type 變爲 xxxxx

因此能夠這樣寫: user_type = serializers.CharField(source="user_type") 顯示存儲的代號 user_type = serializers.CharField(source="get_user_type_display") 顯示對應的名稱

外鍵關聯時: gp = serializers.CharField(source="group.title") 內部能夠根據 . 來split,一直向後取值

內部: 源碼會判斷傳入的值是否callable,若是可調用,自動加()執行 source="user_type" >>> row.user_type source="get_user_type_display" >>> row.get_user_type_display()

def fun(arg):
    if callable(arg):
        ret = arg()
    else:
        ret = arg
    print(ret)

def add(a,b):
    return a + b

fun('測試')
fun(add(2,6))

真正的自定義顯示,鉤子

rls = serializers.SerializerMethodField() # 自定義顯示

    def get_rls(self,row):
        role_obj_list = row.roles.all()
        ret = []
        for item in role_obj_list:
            ret.append({'id':item.id,'title':item.title})
        return ret

# my_field = serializer.SerializerMethodField(method_name='get_my_field')
# default_method_name = 'get_{field_name}'.format(field_name=field_name)

自定義filed

class MyField(serializers.CharField):

    def to_representation(self, value):
        print(value)
        return "真正的返回值"

使用:
class UserInfoSerializer(serializers.Serializer):
    my_field = MyField(source='username')

返回時,顯示的就是return內容

繼承ModelSerializer

也能夠支持自定義字段。
指定字段時能省點事,僅此而已

class UserInfoSerializer(serializers.ModelSerializer):
    oooo = serializers.CharField(source="get_user_type_display")
    rls = serializers.SerializerMethodField() # 自定義顯示鉤子
    gp = serializers.CharField(source="group.title")
    def get_rls(self,row):
        return ({'name':'jinshen'})
    class Meta:
        model = models.UserInfo
        # fields = "__all__"
        fields = ['id','username','password','group','roles']

class UserInfoView2(APIView):
    def get(self, request, *args, **kwargs):
        users = models.UserInfo.objects.all()

        ser = UserInfoSerializer2(instance=users, many=True, context={'request': request})
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

自動序列化連表/深度控制

class UserInfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        # fields = "__all__"
        fields = ['id','username','password','group','roles']
        depth = 1 # 0 ~ 10

生成連接

serializers.HyperlinkedIdentityField

# group字段對應生成url連接 - "group": "http://127.0.0.1:8000/api/v1/group/1",

class UserInfoSerializer3(serializers.ModelSerializer):
    group = serializers.HyperlinkedIdentityField(view_name='gp',lookup_field='group_id',lookup_url_kwarg='pk')
    '''
    view_name='gp' url別名
    lookup_field='group_id' 對應的字段列,默認pk
    lookup_url_kwarg='pk' url中的分組名,能夠本身改
    沒有source參數!
    '''
    class Meta:
        model = models.UserInfo
        fields = ['id','username','password','group','roles']
        depth = 0 # 0 ~ 10
        
class UserInfoView3(APIView):
    def get(self, request, *args, **kwargs):
        users = models.UserInfo.objects.all()
        '''若是要生成連接,必須加上context參數'''
        ser = UserInfoSerializer3(instance=users, many=True, context={'request': request})
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

序列化源碼探究

  1. 序列化應用第一步 --- 類實例化
    實例化時先執行構造方法和new方法,__new__ 比 __init__要早執行
    順藤往上摸,找父類的 __new__和__init__
  2. 找父類 - 以ModelSerializer爲例
    ModelSerializer >>> Serializer >>> BaseSerializer 在BaseSerializer裏發現 __new__和__init__
  3. new和init
kwargs.pop('many', False) 取many值,缺省默認False,

def __new__(cls, *args, **kwargs):
    # We override this method in order to automagically create
    # `ListSerializer` classes instead when `many=True` is set.
    
    if kwargs.pop('many', False):
        # many = True時執行,對queryset進行處理
        return cls.many_init(*args, **kwargs)
    # many缺省或爲False時執行,對obj進行處理
    return super(BaseSerializer, cls).__new__(cls, *args, **kwargs)
    
def __init__():
    pass 一坨賦值語句封裝參數

分析
many = False
return super(BaseSerializer, cls).__new__(cls, *args, **kwargs) 返回的是本身,啥都沒作。new就完事了 ,接下來執行init方法去了 many = True 執行 many_init() 方法 這個方法作了一件事 把處理序列化的類指定爲 ListSerializer,並返回

小結 ser = RolesSerializer(instance=role, many=False) 類實例化作的事:作區分 對象, Serializer類處理; self.to_representation QuerySet,ListSerializer類處理; self.to_representation

  1. 第二步:實例化完該調用了 ret = json.dumps(ser.data, ensure_ascii=False) ser.data 根據繼承關係找 data方法

Serializer - data方法

@property
    def data(self):
        ret = super(Serializer, self).data #調用父類data方法
        return ReturnDict(ret, serializer=self)

BaseSerializer - data方法

if not hasattr(self, '_data'):
            if self.instance is not None and not getattr(self, '_errors', None):
                self._data = self.to_representation(self.instance)
            elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
                self._data = self.to_representation(self.validated_data)
            else:
                self._data = self.get_initial()
        return self._data

to_representation方法, 依然是從下往上找,Serializer和BaseSerializer裏都有此方法 可是Serializer-to_representation 離得更近,它會執行

  1. Serializer-to - representation
def to_representation(self, instance):
        """
        Object instance -> Dict of primitive datatypes.
        """
        ret = OrderedDict() #這就是最後返回給咱們的那個有序字典實例
        fields = self._readable_fields

        for field in fields:
            try:
                attribute = field.get_attribute(instance)
            except SkipField:
                continue
        ···

field 是咱們在自定義序列化類時指定的 Charfiled IntegerField 這些字段對象 Charfiled 等,裏邊沒有get_attribute() 方法 Charfiled繼承自Filed class Filed() - get_attribute()

def get_attribute(self, instance):
        """
        Given the *outgoing* object instance, return the primitive value
        that should be used for this field.
        """
        try:
            return get_attribute(instance, self.source_attrs)

instance 是傳入的對象 self.source_attrs就是參數source, 在類的init裏能夠看到它的封裝 在Filed類下的bind()方法,能夠看到split . 取值 source_attrs 能夠是:group.title get_user_type_display roles.all等等 經過後邊的一系列騷操做,無論它是什麼,都能從裏邊拿出想要的東西

上來就跳了,返回單獨的get_attribute()方法 把兩個重要的參數傳入

  1. E:\space_env\env_for_blog\Lib\site-packages\rest_framework\fields.py py文件下的get_attribute()方法
def get_attribute(instance, attrs):
    for attr in attrs:
    # 循環全部的attr(source參數)
        try:
            if isinstance(instance, collections.Mapping):
                instance = instance[attr]
            else:
                instance = getattr(instance, attr)
        except ObjectDoesNotExist:
            return None
        if is_simple_callable(instance):
            try:
                instance = instance()
            except (AttributeError, KeyError) as exc:
                # If we raised an Attribute or KeyError here it'd get treated
                # as an omitted field in `Field.get_attribute()`. Instead we
                # raise a ValueError to ensure the exception is not masked.
                raise ValueError('Exception raised in callable attribute "{0}"; original exception was: {1}'.format(attr, exc))

    return instance

經過在這個函數裏遍歷,根據source取出各層的instance實例並逐層返回

序列化中的自定義驗證

字段級別的驗證① 自定義validator類

serializers.py 文件在開頭引入包的時候,就引入了fields.py的全部字段 example_field = serializers.CharField(...) 用的實際上是fileds裏邊的Charfiled class CharField(Field): 繼承自 class Field(object): 咱們看看Field支持哪些參數:

class Field(object):
    default_error_messages = {
        'required': _('This field is required.'),
        'null': _('This field may not be null.')
    }
    default_validators = []

    def __init__(self, read_only=False, write_only=False,
                 required=None, default=empty, initial=empty, source=None,
                 label=None, help_text=None, style=None,
                 error_messages=None, validators=None, allow_null=False):

default_error_messages 兩條默認的錯誤提示 分別對應的參數是:required和allow_null

validators參數對應的就是驗證方法(基於類),默認空列表

class MultipleOf(object):
    def __init__(self, base):
        self.base = base

    def __call__(self, value):
        # __call__()方法可以讓類的實例對象,像函數同樣被調用;
        # XXValidator(12)(34) 第一個()實例化,第二個()內的34會被識別爲參數
        if value % self.base != 0:
            message = 'This field must be a multiple of %d.' % self.base
            raise serializers.ValidationError(message)

使用:
    title = serializers.CharField(error_messages={'required': '標題不能爲空'}, validators=[MultipleOf('老男人'), ])

上邊這種方法,實現了對單個字段的驗證。哪一個須要驗,就在哪寫validators=[這個類]

【小總結】 自定義validator類,與form裏邊自定義validator類用法徹底一致 這個validator是filed參數,也就是隻能做爲參數對單個字段進行驗證 具體調用的地方在···\rest_framework\fields.py - class Field() - def run_validators()

try:
                validator(value)
            except ValidationError as exc:
                pass

【小總結over】

字段級別的驗證② 自定義字段

另外,還能夠經過自定義字段的方法實現驗證。 django-form中 全部字段對象都繼承自class Field ,在這個類裏邊留了一些鉤子方法 如:to_python() validate() 自定義一個字段類,覆寫以上方法

當調用表單的 is_valid() 方法時, is_valid()>>> self.errors>>>self.full_clean()>>>self._clean_fields()>>>value = field.clean(value) 調用自定義字段的clean()

def clean(self, value):
        """
        Validates the given value and returns its "cleaned" value as an
        appropriate Python object.

        Raises ValidationError for any errors.
        """
        value = self.to_python(value) # 字段裏自定義to_python,返回value
        self.validate(value) # 字段裏自定義validate,返回對象
        self.run_validators(value) # 自定義validator類
        return value

restful-serializer中 字段繼承自 ···\rest_framework\fields.py class Field() 沒留那麼多鉤子,可是能夠經過覆寫to_representation方法實現 須要注意的是,每一個Filed的這個方法作的事情不同

class MyField(serializers.CharField):
    # 自定義filed
    def to_representation(self, value):
        驗證或者其它操做
        return str(value)

這個沒應用過,但原理是通的。

基於函數的自定義驗證:

找鉤子函數,定義序列化類時,將鉤子函數寫進去 這個鉤子方法寫在序列化類,而非某個字段。

Serializer 爲例: Serializer內部能夠找到如下3個方法,不用去父類找 is_valid >>> run_validation >>> to_internal_value

for field in fields:
        validate_method = getattr(self, 'validate_' + field.field_name, None)
        ...
        if validate_method is not None:
            validated_value = validate_method(validated_value)
        ...

因此鉤子方法就是 validate_ + 字段名 + () 傳入的是value,return的也是value,中間怎麼搞本身發揮

整個鉤子方法都在try語句裏邊,後邊有except ValidationError跟着 因此不經過,直接raise ValidationError就好了

【類比form】 也是form級調用is_valid方法時: is_valid()調用self.is_bound 和 se lf.errors self.errors >>> self.full_clean() >>> ① self.clean_fields() ② self.clean_form() >>> clean()#純鉤子 ③ self.post_clean() #純鉤子 2和3是純鉤子,不作討論 clean_fields() 下 for循環內部 hasattr(self, 'clean%s' % name):#這裏的name形參其實就是form的字段名 value = getattr(self, 'clean%s' % name)() self.cleaned_data[name] = value 鉤子方法:clean + 字段名 + ()

validate_title() clean_title() 二者都是在驗證器/表單級別的類裏邊,提供了針對具體字段驗證的鉤子方法

就是這麼簡單的事情,官方文檔的解釋也太草率了,還給了個不知所云的even_number

驗證器能夠是任何可調用對象,在失敗時引起 serializers.ValidationError。 def even_number(value): if value % 2 != 0: raise serializers.ValidationError('This field must be an even number.') 你能夠經過向 Serializer 子類添加 .validate_<field_name>方法來指定自定義字段級驗證。

分頁

from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination

分頁相關的類:···\rest_framework\pagination.py BasePagination 基類 PageNumberPagination 普通分頁 LimitOffsetPagination 按照Limit、Offset分頁,此時page參數失效 CursorPagination 遊標/加密分頁

分頁裏邊惟一的亮點:在於遊標分頁,大幅度提高性能,但必定程度上限制了用戶的查詢便利性。

class MytestSerialiser(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = "__all__"

class MyPageNumberPagination(PageNumberPagination):
    # 默認每頁顯示的數據條數
    page_size = 4
    # 獲取URL參數中設置的每頁顯示數據條數
    page_size_query_param = 'page_size'

    # 獲取URL參數中傳入的頁碼key
    page_query_param = 'page'

    # 最大支持的每頁顯示的數據條數
    max_page_size = 10

class MyLimitOffsetPagination(LimitOffsetPagination):
    # 默認每頁顯示的數據條數
    default_limit = 5
    # URL中傳入的顯示數據條數的參數
    limit_query_param = 'limit'
    # URL中傳入的數據位置的參數
    offset_query_param = 'offset' #第幾條
    # 最大每頁顯得條數
    max_limit = None

class MyCursorPagination(CursorPagination):
    # URL傳入的遊標參數
    cursor_query_param = 'cursor'
    # 默認每頁顯示的數據條數
    page_size = 2
    # URL傳入的每頁顯示條數的參數
    page_size_query_param = 'page_size'
    # 每頁顯示數據最大條數
    max_page_size = 500

    # 根據ID從大到小排列
    ordering = "id"

class PagerView2(APIView):
    '''
    http://127.0.0.1:8000/api/v1/page/02/ 獲取數據
    http://127.0.0.1:8000/api/v1/page/02/?page=2 獲取具體頁碼數據
    http://127.0.0.1:8000/api/v1/page/02/?page=2&page_size=3 用戶還能本身指定每頁條數
    '''
    def get(self, request, *args, **kwargs):
        roles = models.Role.objects.all()

        # 實例化分頁對象,獲取數據庫中的分頁數據
        paginator = MyPageNumberPagination()
        page_user_list = paginator.paginate_queryset(roles, self.request, view=self) # 分頁的方法

        # 序列化分頁後的對象
        serializer = MytestSerialiser(page_user_list, many=True)

        # 生成分頁和數據
        response = paginator.get_paginated_response(serializer.data) # 內部執行Response方法
        return response

視圖

以GenericAPIView爲例,繼承自APIView。與django的GenericView思路及其類似(雞肋) 我的認爲類通用視圖比較雞肋,並且耦合性太強,能不用就不用 可是在使用視圖集時,這東西就必須用了

class View1View(GenericAPIView): # APIView
    queryset = models.Role.objects.all()
    serializer_class = PagerSerialiser
    pagination_class = PageNumberPagination
    def get(self,request,*args,**kwargs):
        # 獲取數據
        roles = self.get_queryset() # models.Role.objects.all()

        # [1, 1000,]     [1,10]
        pager_roles = self.paginate_queryset(roles)

        # 序列化
        ser = self.get_serializer(instance=pager_roles,many=True)

        return Response(ser.data)

視圖集

  • 官網解釋

Django REST framework 容許將一組相關視圖的邏輯組合到一個稱爲 ViewSet 的類中 ViewSet 類只是一種基於類的 View,它不提供任何處理方法,如 .get() 或 .post(),而是提供諸如 .list() 和 .create() 之類的操做。 ViewSet 只在用 .as_view() 方法綁定到最終化視圖時作一些相應操做。 一般,不是在 urlconf 中的視圖集中明確註冊視圖,而是使用路由器類註冊視圖集,這會自動爲您肯定 urlconf。

  • 說人話就是視圖集使用的路由方式改變了 並且,把路由方法由 put delete post get 改成 creat destroy update retrieve捎帶還贈送了一個List查詢 也就是把增刪改查對應的方法名變了,並且每一個方法寫在一個單獨的類裏邊

映射關係須要本身寫在urlpatterns裏:

'get': 'retrieve',
'get': 'list',
'post':'create'
'delete': 'destroy',
'put': 'update',
'patch': 'partial_update',

最全的類繼承,就是繼承ModelViewSet,包含則增刪改查全部的方法

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

增刪改查全繼承: class View2View(ModelViewSet): 少來點: class View3View(ListModelMixin,CreateModelMixin,GenericViewSet):

對應路由的寫法:

# 若是不使用router ,get的映射要本身寫兩遍,不帶pk參數映射list 帶pk映射retrieve
    # 由於視圖集把get方法拆開了,在視圖集裏對應兩個類
    url(r'^(?P<version>[v1|v2]+)/viewset/$', views.View2View.as_view({'get': 'list','post':'create'})),
    url(r'^(?P<version>[v1|v2]+)/viewset/(?P<pk>\d+)/$',
        views.View2View.as_view({'get': 'retrieve',
                                 'delete': 'destroy',
                                 'put': 'update',
                                 'patch': 'partial_update',
                                 })),

    url(r'^(?P<version>[v1|v2]+)/viewset3/$', views.View3View.as_view({'get': 'list','post':'create'})),

genericviewset的第一個父類 ViewSetMixin 改寫了as_view方法 路由規則改變了,因此要本身在url後邊加{'get','xxx'}參數

Modelviewset,它繼承6個類,功能最全

【小結】 使用的時候 APIView - Genericviewset - modelviewset 功能愈來愈全,代碼愈來愈少,耦合愈來愈強

Genericviewset 與增刪改查 聯合做爲父類 也行,靈活應用

補充了一個更細粒度的權限管理,以前是全局權限,引入這個以後能夠作對象級別的權限管理

Genericviewset惟一的好處:獲取列表/單個對象 作出了區分

路由 和 渲染器

這倆東西是摻和在一塊的

  • 本身寫路由 看下邊這4種路由的寫法:
urlpatterns = [
    url(r'^test/$', views.TestView.as_view()),
    url(r'^test/(?P<pk>[^/.]+)/$', views.TestView.as_view()),

    url(r'^test\.(?P<format>[a-z0-9]+)$', views.TestView.as_view()),
    url(r'^test/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)$', views.TestView.as_view())
]

前邊兩個是 爲列表/對象查詢作的區分 後邊兩個引入的那個format / pk+format 純粹是爲了支持渲染器寫的 若是公司項目僅適用json,那渲染器就沒什麼用了。

  • 視圖集中寫路由(半自動) 視圖集的部分已經寫過了
urlpatterns = [
    url(r'^test/$', views.UserViewSet.as_view({'get': 'list', 'post': 'create'})),
    url(r'^test/(?P<pk>\d+)/$', views.UserViewSet.as_view(
        {'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})),
]
  • 全自動模式
from django.conf.urls import url, include
from rest_framework import routers
from api import views


router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)

urlpatterns = [
    url(r'^', include(router.urls)),

視圖函數中:

from rest_framework.viewsets import ModelViewSet
from rest_framework import serializers
from .. import models


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"


class UserViewSet(ModelViewSet):
    queryset = models.UserInfo.objects.all()
    serializer_class = UserSerializer

include(router.urls) 一句幫咱們生成對應的4種url,省點事

這基本就是restful官網教程的quickstart了,寫的是爽了,但在生產環境中徹底沒意義。

渲染器

from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer

···\site-packages\rest_framework\settings.py 默認的渲染器

'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.TemplateHTMLRenderer',
    )

自定義: 全局settings裏設置RENDERER_CLASSES 值 或者 視圖裏本身指定 renderer_classes = [JSONRenderer,BrowsableAPIRenderer]

默認就有json啊:這三種方式都能訪問

http://127.0.0.1:8000/test/?format=json
http://127.0.0.1:8000/test.json
http://127.0.0.1:8000/test/

JSONRenderer 對應的 media_type = 'application/json' format = 'json'

BrowsableAPIRenderer 對應的 media_type = 'text/html' format = 'api'

一共有10來種渲染器,每種對應的format字段名稱都不同

路由和渲染器從根上說是兩碼事, 引入渲染器會致使url書寫變得很蛋疼,那就再引入個路由器,表面上掩蓋一下。

恩,就這樣

相關文章
相關標籤/搜索