DRF之過濾類原始碼分析
【一】過濾類介紹及BaseFilterBackend
- Django REST framework(DRF)中的過濾類允許你在API檢視中對查詢進行過濾,以根據特定條件篩選結果集。
- 過濾類是DRF的一部分,它允許你輕鬆地新增各種過濾選項,以滿足不同用例的需求。
class BaseFilterBackend:
"""
A base class from which all filter backend classes should inherit.
"""
def filter_queryset(self, request, queryset, view):
"""
Return a filtered queryset.
"""
raise NotImplementedError(".filter_queryset() must be overridden.")
def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
return []
def get_schema_operation_parameters(self, view):
return []
【二】內建過濾類SearchFilter
【1】使用
# 過濾:必須繼承 GenericAPIView 及其子類,才能使用(如果繼承APIView,則不能這麼寫)
# DRF 給我們提供了一個排序類
from rest_framework.filters import SearchFilter
class BookView(GenericViewSet, ListModelMixin):
queryset = models.Book.objects.all()
serializer_class = BookSerializer
filter_backends = [SearchFilter] # 排序類
# SearchFilter:必須配合一個類屬性
# 按照那些欄位進行篩選
# http://127.0.0.1:8000/app01/v1/books/?search=夢
# 只要name/price中帶 夢 都能被搜出來
search_fields = ['name']
【2】原始碼分析
class SearchFilter(BaseFilterBackend):
# The URL query parameter used for the search.
# 搜尋引數的名稱,預設為 api_settings.SEARCH_PARAM,通常是 search。
# 拼在路由尾部的關鍵字 http://127.0.0.1:8000/app01/v1/books/?search=夢
# 你可以透過在API檢視中自定義該引數來更改搜尋引數的名稱。
search_param = api_settings.SEARCH_PARAM
# 用於HTML渲染搜尋部件的模板路徑,預設為 'rest_framework/filters/search.html'。
template = 'rest_framework/filters/search.html'
# 定義了搜尋條件的字首和它們對應的查詢操作。
# 例如,^ 表示 "istartswith"(不區分大小寫的開始於)
# = 表示 "iexact"(不區分大小寫的精確匹配) 使用居多
# @ 表示 "search"(全文搜尋)
# $ 表示 "iregex"(不區分大小寫的正規表示式匹配)。
lookup_prefixes = {
'^': 'istartswith',
'=': 'iexact',
'@': 'search',
'$': 'iregex',
}
#
search_title = _('Search')
#
search_description = _('A search term.')
# 從檢視中獲取搜尋欄位列表
# 可以覆蓋此方法以動態更改搜尋欄位
def get_search_fields(self, view, request):
"""
Search fields are obtained from the view, but the request is always
passed to this method. Sub-classes can override this method to
dynamically change the search fields based on request content.
"""
# 從檢視類中中的 search_fields 列表中對映出所有的 過濾引數欄位
return getattr(view, 'search_fields', None)
# 解析查詢引數中的搜尋條件,將其分割成一個列表。
def get_search_terms(self, request):
"""
Search terms are set by a ?search=... query parameter,
and may be comma and/or whitespace delimited.
"""
# 獲取到路由尾部攜帶的引數
params = request.query_params.get(self.search_param, '')
# 空格代替 空
params = params.replace('\x00', '') # strip null characters
# 多欄位進行切分
params = params.replace(',', ' ')
# 返回切分後的引數
return params.split()
# 構建搜尋條件,根據字首選擇合適的查詢操作。
# field_name 引數是傳遞給過濾器的搜尋欄位名,可能包含字首
def construct_search(self, field_name):
# 字典定義了搜尋條件的字首和它們對應的查詢操作。
# 檢查 field_name 的第一個字元(字首),並使用 get 方法從 lookup_prefixes 字典中獲取對應的查詢操作。
lookup = self.lookup_prefixes.get(field_name[0])
# 如果字首存在
if lookup:
# 則將字首從 field_name 中去除,並將查詢操作儲存在 lookup 變數中。
field_name = field_name[1:]
else:
# 如果字首不存在,則預設使用 'icontains' 查詢操作,這表示執行不區分大小寫的部分匹配。
lookup = 'icontains'
# 使用 LOOKUP_SEP 連線欄位名和查詢操作,返回最終的搜尋條件字串。
# LOOKUP_SEP 是Django中用於連線查詢欄位和操作的分隔符,通常是雙下劃線 __。
# 例如,如果傳遞的搜尋欄位名是 ^name,那麼這個方法將返回 "name__istartswith",這表示要在 name 欄位上執行不區分大小寫的開始於匹配操作。
return LOOKUP_SEP.join([field_name, lookup])
# 返回一個布林值,指示是否應該使用 distinct() 方法來查詢結果集以避免重複項。
# queryset 引數是要進行過濾的查詢集,通常是資料庫查詢的結果集。
# search_fields 引數是用於搜尋的欄位列表,這些欄位可能包含字首。
def must_call_distinct(self, queryset, search_fields):
"""
Return True if 'distinct()' should be used to query the given lookups.
"""
# 迭代 search_fields 中的每個搜尋欄位
for search_field in search_fields:
# 對於每個搜尋欄位,它獲取與查詢集相關聯的模型的後設資料(opts = queryset.model._meta)
opts = queryset.model._meta
# 檢查搜尋欄位的第一個字元是否在 lookup_prefixes 中
if search_field[0] in self.lookup_prefixes:
# 如果字首存在,則將字首從搜尋欄位中去除(search_field = search_field[1:])。
search_field = search_field[1:]
# Annotated fields do not need to be distinct
# 檢查搜尋欄位是否已經在查詢集的註釋中。
if isinstance(queryset, models.QuerySet) and search_field in queryset.query.annotations:
# 如果欄位已經在註釋中,說明該欄位已經被標記為註釋欄位,通常不需要使用 distinct()。
continue
# 如果欄位不在註釋中,則進一步檢查欄位是否包含巢狀關係(例如,related_field__nested_field)。
parts = search_field.split(LOOKUP_SEP)
# 如果欄位是巢狀關係,它會更新模型的後設資料以跟蹤關係路徑,並檢查是否存在多對多關係(m2m 關係)。
for part in parts:
field = opts.get_field(part)
if hasattr(field, 'get_path_info'):
# This field is a relation, update opts to follow the relation
path_info = field.get_path_info()
opts = path_info[-1].to_opts
# # 如果欄位是多對多關係,就需要呼叫 distinct() 方法,因為多對多關係通常會導致結果集中的重複項。
if any(path.m2m for path in path_info):
# This field is a m2m relation so we know we need to call distinct
# 如果需要呼叫 distinct(),它將返回 True
return True
else:
# This field has a custom __ query transform but is not a relational field.
break
# 最後,方法返回一個布林值,指示是否應該呼叫 distinct()。
# 如果需要呼叫 distinct(),它將返回 True,否則返回 False。
return False
# 實際的查詢過濾操作,根據查詢引數中的搜尋條件修改查詢集
def filter_queryset(self, request, queryset, view):
# 獲取了檢視中定義的搜尋欄位列表(search_fields)和查詢引數中傳遞的搜尋條件(search_terms)。
search_fields = self.get_search_fields(view, request)
search_terms = self.get_search_terms(request)
# 如果沒有定義搜尋欄位或者沒有傳遞搜尋條件
if not search_fields or not search_terms:
# 就直接返回原始的查詢集 queryset,不進行任何過濾。
return queryset
# 構建了一個包含了所有搜尋欄位的ORM查詢操作列表 orm_lookups
# 透過呼叫 construct_search 方法將搜尋欄位名轉換為相應的查詢操作。
orm_lookups = [
self.construct_search(str(search_field))
for search_field in search_fields
]
# 始化了兩個變數,base 和 conditions。
# base 是原始的查詢集
# conditions 是用來儲存過濾條件的列表。
base = queryset
conditions = []
# 迭代每個搜尋條件(search_term)和每個查詢操作(orm_lookup),並建立一個 Q 物件,將查詢操作和搜尋條件傳遞給它。
# 這個 Q 物件表示了一個或多個查詢條件的邏輯或關係。
for search_term in search_terms:
# 建立的 Q 物件列表儲存在 queries 中
queries = [
models.Q(**{orm_lookup: search_term})
for orm_lookup in orm_lookups
]
# 然後使用 reduce(operator.or_, queries) 將它們組合成一個包含所有查詢條件的 Q 物件。
# 最終,conditions 列表包含了一個或多個 Q 物件,每個 Q 物件代表一個搜尋條件的查詢操作。
# 使用 reduce(operator.and_, conditions) 將所有的 Q 物件組合成一個包含所有搜尋條件的查詢操作,這個查詢操作表示了所有搜尋條件之間的邏輯與關係。
conditions.append(reduce(operator.or_, queries))
# 最後,使用 queryset.filter() 方法,將這個複合查詢操作應用於原始查詢集,以便執行過濾操作。
queryset = queryset.filter(reduce(operator.and_, conditions))
# 如果必須呼叫 distinct() 方法(透過呼叫 must_call_distinct 方法來確定)
if self.must_call_distinct(queryset, search_fields):
# Filtering against a many-to-many field requires us to
# call queryset.distinct() in order to avoid duplicate items
# in the resulting queryset.
# We try to avoid this if possible, for performance reasons.
# 在過濾後的查詢集上呼叫 distinct() 以確保結果集不包含重複項
queryset = distinct(queryset, base)
# 返回過濾後的資料集
return queryset
# 用於生成搜尋框的HTML表示形式,以便在API的瀏覽器瀏覽介面中顯示
def to_html(self, request, queryset, view):
'''
目的是生成一個包含搜尋框的HTML表示,以便在API的瀏覽器瀏覽介面中讓使用者輸入搜尋條件。
搜尋框的樣式和佈局通常由指定的模板檔案定義,這使得可以根據需要自定義搜尋框的外觀。
搜尋引數的名稱和搜尋條件的值也被傳遞到模板中,以便在HTML中動態生成搜尋框。
'''
# 檢查檢視是否定義了搜尋欄位列表(search_fields)。
if not getattr(view, 'search_fields', None):
# 如果沒有定義搜尋欄位,就直接返回空字串,表示不需要顯示搜尋框。
return ''
# 獲取查詢引數中傳遞的搜尋條件(search_terms)。
term = self.get_search_terms(request)
# 如果搜尋條件存在,就將第一個搜尋條件(通常只支援一個搜尋條件)儲存在 term 變數中。
term = term[0] if term else ''
# 建立一個名為 context 的字典,其中包含兩個鍵值對:
# 'param':用於指定搜尋引數的名稱,通常是 search。
# 'term':包含了搜尋條件的值,即使用者在搜尋框中輸入的文字。
context = {
'param': self.search_param,
'term': term
}
# 使用 Django 的 loader.get_template 函式獲取指定模板檔案(self.template)的模板物件。
# 這個模板通常包含了搜尋框的HTML表示。
template = loader.get_template(self.template)
# 使用 template.render(context) 渲染模板,將 context 字典中的資料傳遞給模板,生成包含搜尋框的HTML表示
return template.render(context)
def get_schema_fields(self, view):
'''
目的是生成一個包含搜尋引數的欄位定義,以便在API文件中顯示搜尋引數的相關資訊。
這有助於API使用者理解如何使用搜尋功能,並構建正確的搜尋請求。
'''
# 透過 assert 語句確保 coreapi 和 coreschema 這兩個庫已經安裝。
# 這些庫是用於構建API文件和描述API資料結構的工具。
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
# 建立一個列表,其中包含一個 CoreAPI 欄位定義物件。
# 這個欄位定義物件用於描述搜尋引數。
# 返回一個包含了搜尋引數欄位定義的列表。這個列表通常會與其他欄位定義一起用於生成API文件,並且可以讓API客戶端了解如何構建搜尋請求
return [
coreapi.Field(
# name:搜尋引數的名稱,通常是 search。
name=self.search_param,
# required:指定搜尋引數是否是必需的,這裡設定為 False 表示不是必需的。
required=False,
# location:指定搜尋引數在請求中的位置,這裡設定為 query,表示在查詢字串中。
location='query',
# schema:定義搜尋引數的資料模式,這裡使用 coreschema.String 來定義搜尋引數的資料型別為字串,並提供了標題(title)和描述(description)。
schema=coreschema.String(
title=force_str(self.search_title),
description=force_str(self.search_description)
)
)
]
# 用於生成API操作的引數定義,這些引數用於API文件的生成
# 返回包含了引數定義的列表。這個列表通常會與API操作一起用於生成API文件,以幫助API使用者瞭解如何構建請求和使用引數。
def get_schema_operation_parameters(self, view):
'''
API文件中顯示操作的引數資訊。這有助於API使用者理解如何構建API請求,並知道哪些引數是可選的、哪些是必需的
'''
# 建立了一個包含引數定義的列表。每個引數定義都是一個字典,描述了一個API操作的引數。
return [
{
# 'name':指定引數的名稱,通常是 search。
'name': self.search_param,
# 'required':指定引數是否是必需的,這裡設定為 False 表示不是必需的。
'required': False,
# 'in':指定引數在請求中的位置,這裡設定為 query,表示在查詢字串中。
'in': 'query',
# 'description':提供引數的描述,使用了 force_str 函式來確保描述是字串型別。
'description': force_str(self.search_description),
# 'schema':定義引數的資料模式,這裡指定引數的資料型別為字串('type': 'string')。
'schema': {
'type': 'string',
},
},
]
【三】第三方過濾類DjangoFilterBackend
【1】使用
# (2) 過濾:必須繼承 GenericAPIView 及其子類,才能使用(如果繼承APIView,則不能這麼寫)
# 第三方過濾類: pip3.9 install django-filter
from django_filters.rest_framework import DjangoFilterBackend
class BookView(GenericViewSet, ListModelMixin):
queryset = models.Book.objects.all()
serializer_class = BookSerializer
filter_backends = [DjangoFilterBackend] # 排序類
# SearchFilter:必須配合一個類屬性
# 按照那些欄位進行篩選
# http://127.0.0.1:8000/app01/v1/books/?search=夢
# 根據指定欄位進行篩選,按名字和價格精準匹配
filterset_fields = ['name', 'price']
【2】原始碼分析
class DjangoFilterBackend:
# 用於指定用於過濾的過濾器集的基類,預設為filterset.FilterSet
filterset_base = filterset.FilterSet
# 指示在過濾器驗證失敗時是否引發異常,預設為True,表示引發異常。
raise_exception = True
@property
# 用於確定渲染過濾器表單時要使用的模板
def template(self):
# 如果使用Crispy Forms
if compat.is_crispy():
# 則為"django_filters/rest_framework/crispy_form.html"
return "django_filters/rest_framework/crispy_form.html"
# 否則為"django_filters/rest_framework/form.html"。
return "django_filters/rest_framework/form.html"
# 返回過濾器集的例項
def get_filterset(self, request, queryset, view):
# 使用get_filterset_class方法來獲取用於過濾查詢集的FilterSet類。
# 這個類可以在檢視中透過filterset_class屬性指定,如果未指定,則會自動建立一個基於查詢集的FilterSet類。
filterset_class = self.get_filterset_class(view, queryset)
# 檢查過濾器集類:如果獲取到了過濾器集類(filterset_class不為None),則繼續執行下面的步驟。
# 否則,返回None,表示沒有可用的過濾器集。
if filterset_class is None:
return None
# 獲取過濾器集的關鍵字引數
# 呼叫get_filterset_kwargs方法,以獲取傳遞給過濾器集建構函式的關鍵字引數
# 。這些引數通常包括請求資料、查詢集和請求物件等。
kwargs = self.get_filterset_kwargs(request, queryset, view)
# 建立過濾器集物件
# 最後,它使用獲取到的過濾器集類和關鍵字引數來建立過濾器集物件,並將其返回。
return filterset_class(**kwargs)
# 返回用於過濾查詢集的FilterSet類
def get_filterset_class(self, view, queryset=None):
"""
# 這個方法會根據檢視和查詢集的情況來確定應該使用哪個 FilterSet 類
Return the `FilterSet` class used to filter the queryset.
"""
# 嘗試從檢視中獲取 filterset_class 屬性。這個屬性是檢視中定義的,可以指定要用於過濾的 FilterSet 類。
# 如果檢視中有指定,則直接返回這個類。
filterset_class = getattr(view, "filterset_class", None)
# 如果檢視中沒有指定 filterset_class,它會檢查是否有 filterset_fields 屬性。
# filterset_fields 屬性是一組用於過濾的欄位名稱,通常與查詢集的模型相關。
filterset_fields = getattr(view, "filterset_fields", None)
if filterset_class:
filterset_model = filterset_class._meta.model
# FilterSets do not need to specify a Meta class
if filterset_model and queryset is not None:
assert issubclass(
queryset.model, filterset_model
), "FilterSet model %s does not match queryset model %s" % (
filterset_model,
queryset.model,
)
return filterset_class
# 如果檢視中定義了這個屬性
if filterset_fields and queryset is not None:
# 它會自動建立一個臨時的 FilterSet 類,該類的 Meta 類中包含了模型和欄位資訊。
MetaBase = getattr(self.filterset_base, "Meta", object)
class AutoFilterSet(self.filterset_base):
class Meta(MetaBase):
model = queryset.model
fields = filterset_fields
return AutoFilterSet
# 如果既沒有指定 filterset_class 也沒有 filterset_fields,則返回 None,表示沒有可用的 FilterSet 類。
return None
# 此方法返回傳遞給過濾器集建構函式的關鍵字引數,包括請求資料、查詢集和請求物件。
def get_filterset_kwargs(self, request, queryset, view):
return {
"data": request.query_params,
"queryset": queryset,
"request": request,
}
# 此方法用於過濾查詢集。,,。
def filter_queryset(self, request, queryset, view):
# 透過呼叫 get_filterset 方法,獲取與檢視關聯的過濾器集例項 filterset。
filterset = self.get_filterset(request, queryset, view)
# 檢查 filterset 是否為 None。
if filterset is None:
# 如果 filterset 為 None,說明沒有可用的過濾器集與檢視關聯,此時直接返回原始的查詢集 queryset,不進行任何過濾。
return queryset
# 如果 filterset 存在,方法會進一步驗證過濾器集的有效性,即呼叫 is_valid 方法檢查過濾器集是否透過了驗證。
if not filterset.is_valid() and self.raise_exception:
# 如果過濾器集無效(is_valid 返回 False)並且 self.raise_exception 屬性為 True,則會丟擲驗證異常,該異常包含了過濾器集的錯誤資訊。
raise utils.translate_validation(filterset.errors)
# 然後將其應用於查詢集
return filterset.qs
# 此方法返回過濾器表單的HTML表示。
def to_html(self, request, queryset, view):
# 呼叫 get_filterset 方法獲取過濾器集例項 filterset。
filterset = self.get_filterset(request, queryset, view)
# 如果 filterset 為 None,則返回 None,表示沒有可用的過濾器集,因此無法生成過濾器表單的 HTML 表示。
if filterset is None:
return None
# 如果 filterset 存在,方法會根據指定的模板(self.template)渲染過濾器表單。
# 通常,模板會包含 HTML 表單元素,以顯示過濾器欄位和相應的輸入框、核取方塊等表單元件。
template = loader.get_template(self.template)
context = {"filter": filterset}
# 最終,方法返回渲染後的 HTML 表示,以便在前端頁面中顯示過濾器表單
return template.render(context, request)
# 此方法返回與過濾器欄位相關的CoreAPI欄位定義
def get_coreschema_field(self, field):
# 首先,它根據過濾器欄位的型別判斷,
if isinstance(field, filters.NumberFilter):
# 如果欄位型別是 filters.NumberFilter,則生成一個 compat.coreschema.Number 欄位
field_cls = compat.coreschema.Number
else:
# 否則生成一個 compat.coreschema.String 欄位。
field_cls = compat.coreschema.String
# 欄位的 description 屬性通常設定為過濾器欄位的幫助文字(help_text),以提供關於欄位用途的描述資訊。
# 最終,方法返回生成的 CoreAPI 欄位定義,用於 API 文件的生成和展示
return field_cls(description=str(field.extra.get("help_text", "")))
# 此方法返回API操作的引數定義列表,用於生成API文件。它檢查過濾器集的基本過濾器,併為每個欄位建立一個引數定義。
def get_schema_fields(self, view):
# This is not compatible with widgets where the query param differs from the
# filter's attribute name. Notably, this includes `MultiWidget`, where query
# params will be of the format `<name>_0`, `<name>_1`, etc...
# 檢查 Django 的 RemovedInDjangoFilter25Warning
from django_filters import RemovedInDjangoFilter25Warning
# 併發出警告,指出內建的模式生成已被棄用,建議使用 drf-spectacular。
warnings.warn(
"Built-in schema generation is deprecated. Use drf-spectacular.",
category=RemovedInDjangoFilter25Warning,
)
# 斷言 coreapi 和 coreschema 庫已安裝,因為這些庫用於生成 API 文件的引數欄位。
assert (
compat.coreapi is not None
), "coreapi must be installed to use `get_schema_fields()`"
assert (
compat.coreschema is not None
), "coreschema must be installed to use `get_schema_fields()`"
# 方法嘗試從檢視中獲取查詢集(queryset)物件。
try:
# 這是透過呼叫檢視的 get_queryset() 方法來完成的,以便獲取與檢視關聯的查詢集。
queryset = view.get_queryset()
except Exception:
# 如果無法獲取查詢集(通常是因為檢視沒有實現 get_queryset() 方法),則將 queryset 設定為 None
queryset = None
# 併發出警告,指出該檢視不相容模式生成。
warnings.warn(
"{} is not compatible with schema generation".format(view.__class__)
)
# 使用 get_filterset_class 方法來獲取與檢視關聯的過濾器集類(filterset_class)
filterset_class = self.get_filterset_class(view, queryset)
return (
[]
# 如果過濾器集類已經在檢視中定義(透過 filterset_class 屬性),則使用該類
if not filterset_class
# 如果沒有可用的過濾器集類,方法返回一個空列表 [],表示沒有需要生成引數欄位的過濾器。
else [
# 否則,如果檢視定義了 filterset_fields 屬性且查詢集不為 None,則會動態建立一個自動生成的過濾器集類 AutoFilterSet,該類繼承自 filterset_base,並使用查詢集的模型和 filterset_fields 屬性來定義過濾器集。
compat.coreapi.Field(
# name 屬性設定為欄位的名稱。
name=field_name,
# required 屬性根據欄位的 required 屬性設定。
required=field.extra["required"],
# location 屬性設定為 "query",表示這些引數位於查詢字串中
location="query",
# schema 屬性透過呼叫 get_coreschema_field 方法生成,該方法生成與欄位相關的 CoreAPI 欄位定義
schema=self.get_coreschema_field(field),
)
#
for field_name, field in filterset_class.base_filters.items()
]
)
# 此方法返回API操作的引數列表,用於生成API文件。
# 它與get_schema_fields類似,但提供了更詳細的引數資訊,包括型別、是否必需等。
def get_schema_operation_parameters(self, view):
# 檢查 Django 的 RemovedInDjangoFilter25Warning
from django_filters import RemovedInDjangoFilter25Warning
# 併發出警告,指出內建的模式生成已被棄用,建議使用 drf-spectacular
warnings.warn(
"Built-in schema generation is deprecated. Use drf-spectacular.",
category=RemovedInDjangoFilter25Warning,
)
try:
# 嘗試從檢視中獲取查詢集(queryset)物件,以便確定與檢視關聯的模型。
queryset = view.get_queryset()
except Exception:
# 如果無法獲取查詢集(通常是因為檢視沒有實現 get_queryset() 方法),則將 queryset 設定為 None
queryset = None
# 併發出警告,指出該檢視不相容模式生成。
warnings.warn(
"{} is not compatible with schema generation".format(view.__class__)
)
# 使用 get_filterset_class 方法來獲取與檢視關聯的過濾器集類(filterset_class)
# 如果過濾器集類已經在檢視中定義(透過 filterset_class 屬性),則使用該類
# 否則,如果檢視定義了 filterset_fields 屬性且查詢集不為 None,則會動態建立一個自動生成的過濾器集類 AutoFilterSet,該類繼承自 filterset_base,並使用查詢集的模型和 filterset_fields 屬性來定義過濾器集。
filterset_class = self.get_filterset_class(view, queryset)
# 如果沒有可用的過濾器集類,方法返回一個空列表 [],表示沒有需要生成引數欄位的過濾器。
if not filterset_class:
return []
parameters = []
# # 遍歷過濾器集類的基本過濾器欄位(base_filters)併為每個欄位建立一個引數定義
for field_name, field in filterset_class.base_filters.items():
#
parameter = {
# name 屬性設定為欄位的名稱。
"name": field_name,
# required 屬性根據欄位的 required 屬性設定。
"required": field.extra["required"],
# in 屬性設定為 "query",表示這些引數位於查詢字串中。
"in": "query",
# description 屬性設定為欄位的標籤(label),如果欄位沒有標籤,則設定為欄位的名稱。
"description": field.label if field.label is not None else field_name,
# schema 屬性是一個包含引數型別資訊的字典。在這裡,所有引數都被表示為字串("type": "string")。
"schema": {
"type": "string",
},
}
# 如果欄位的 extra 屬性包含了 "choices" 鍵(即欄位具有選項)
if field.extra and "choices" in field.extra:
# 將 schema 字典的 "enum" 鍵設定為欄位選項的列表。
parameter["schema"]["enum"] = [c[0] for c in field.extra["choices"]]
parameters.append(parameter)
return parameters
【四】自定義過濾類
【1】使用
from rest_framework import filters
from django.db.models import Q
class BookFilter(filters.BaseFilterBackend):
'''
def filter_queryset(self, request, queryset, view):
"""
Return a filtered queryset.
"""
raise NotImplementedError(".filter_queryset() must be overridden.")
'''
def filter_queryset(self, request, queryset, view):
# 返回的資料,都是過濾後的資料
# http://127.0.0.1:8000/app01/v1/books/?price=99&name=追夢赤子心
# 需要將篩選條件變成price=99或name=追夢赤子心,而第三方寫法中訪問地址只能是上面的格式
price = request.query_params.get('price')
name = request.query_params.get('name')
queryset = queryset.filter(Q(name=name) | Q(price=price))
return queryset
【2】分析
- 根據位址列中傳入的資料進行過濾
- 將過濾完的 queryset 資料集返回