get_queryset篩選


 
 

篩選

Manager提供的根QuerySet描述了數據庫表中的所有對象。不過,通常,您只需要選擇完整對象集的一個子集即可。

— Django文檔

REST框架的通用列表視圖的默認行為是返回模型管理器的整個查詢集。通常,您會希望您的API限制查詢集返回的項目。

篩選子類的任何視圖的查詢集的最簡單方法GenericAPIView是覆蓋該.get_queryset()方法。

通過覆蓋此方法,您可以通過多種不同方式自定義視圖返回的查詢集。

針對當前用戶進行過濾

您可能希望過濾查詢集,以確保僅返回與發出請求的當前經過身份驗證的用戶相關的結果。

您可以根據的值進行過濾request.user

例如:

from myapp.models import Purchase from myapp.serializers import PurchaseSerializer from rest_framework import generics class PurchaseList(generics.ListAPIView): serializer_class = PurchaseSerializer def get_queryset(self): """ This view should return a list of all the purchases for the currently authenticated user. """ user = self.request.user return Purchase.objects.filter(purchaser=user)

根據網址過濾

另一種過濾方式可能涉及基於URL的某些部分限制查詢集。

例如,如果您的URL配置包含這樣的條目:

re_path('^purchases/(?P<username>.+)/$', PurchaseList.as_view()),

然后,您可以編寫一個視圖,該視圖返回按URL的用戶名部分過濾的購買查詢集:

class PurchaseList(generics.ListAPIView): serializer_class = PurchaseSerializer def get_queryset(self): """ This view should return a list of all the purchases for the user as determined by the username portion of the URL. """ username = self.kwargs['username'] return Purchase.objects.filter(purchaser__username=username)

根據查詢參數過濾

過濾初始查詢集的最后一個示例是根據url中的查詢參數確定初始查詢集。

我們可以重寫.get_queryset()以處理諸如的URL http://example.com/api/purchases?username=denvercoder9,僅username在URL中包含參數時才過濾查詢集

class PurchaseList(generics.ListAPIView): serializer_class = PurchaseSerializer def get_queryset(self): """ Optionally restricts the returned purchases to a given user, by filtering against a `username` query parameter in the URL. """ queryset = Purchase.objects.all() username = self.request.query_params.get('username') if username is not None: queryset = queryset.filter(purchaser__username=username) return queryset

通用過濾

REST框架不僅可以覆蓋默認查詢集,還包括對通用過濾后端的支​​持,使您可以輕松構建復雜的搜索和過濾器。

通用過濾器還可以將自己顯示為可瀏覽API和admin API中的HTML控件。

篩選器范例

設置過濾器后端

可以使用該DEFAULT_FILTER_BACKENDS設置全局設置默認過濾器后端例如。

REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'] }

您還可以使用GenericAPIView基於類的視圖基於每個視圖或每個視圖集設置過濾器后端

import django_filters.rest_framework from django.contrib.auth.models import User from myapp.serializers import UserSerializer from rest_framework import generics class UserListView(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer filter_backends = [django_filters.rest_framework.DjangoFilterBackend]

過濾和對象查找

請注意,如果為視圖配置了過濾器后端,則該過濾器后端不僅用於過濾列表視圖,還將用於過濾用於返回單個對象的查詢集。

例如,給定前面的示例以及id為的產品4675,以下URL將返回相應的對象或返回404響應,具體取決於給定產品實例是否滿足過濾條件:

http://example.com/api/products/4675/?category=clothing&max_price=10.00

覆蓋初始查詢集

請注意,您可以同時使用覆蓋.get_queryset()過濾和通用過濾,一切都會按預期進行。例如,如果ProductUser名為的多對多關系purchase,則可能需要編寫如下視圖:

class PurchasedProductsList(generics.ListAPIView): """ Return a list of all the products that the authenticated user has ever purchased, with optional filtering. """ model = Product serializer_class = ProductSerializer filterset_class = ProductFilter def get_queryset(self): user = self.request.user return user.purchase_set.all()

API指南

DjangoFilterBackend

django-filter庫包含一個DjangoFilterBackend類,該類支持針對REST框架的高度可自定義的字段篩選。

要使用DjangoFilterBackend,請先安裝django-filter

pip install django-filter

然后添加'django_filters'到Django的INSTALLED_APPS

INSTALLED_APPS = [ ... 'django_filters', ... ]

現在,您應該將過濾器后端添加到您的設置中:

REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'] }

或將過濾器后端添加到單個View或ViewSet。

from django_filters.rest_framework import DjangoFilterBackend class UserListView(generics.ListAPIView): ... filter_backends = [DjangoFilterBackend]

如果只需要簡單的基於等式的過濾,則可以filterset_fields在視圖或視圖設置一個屬性,列出要過濾的字段集。

class ProductList(generics.ListAPIView): queryset = Product.objects.all() serializer_class = ProductSerializer filter_backends = [DjangoFilterBackend] filterset_fields = ['category', 'in_stock']

這將自動FilterSet為給定的字段創建一個類,並允許您發出如下請求:

http://example.com/api/products?category=clothing&in_stock=True

對於更高級的過濾要求,您可以指定FilterSet視圖應使用類。您可以FilterSetdjango-filter文檔中閱讀有關更多信息還建議您閱讀有關DRF集成的部分

SearchFilter

SearchFilter級支持簡單單的查詢參數基於搜索和基於該admin界面的搜索功能

使用時,可瀏覽的API將包含一個SearchFilter控件:

搜索過濾器

所述SearchFilter如果視圖有一類將只應用於search_fields屬性集。search_fields屬性應該是模型上文本類型字段名稱的列表,例如CharFieldTextField

from rest_framework import filters class UserListView(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer filter_backends = [filters.SearchFilter] search_fields = ['username', 'email']

這將允許客戶端通過執行以下查詢來過濾列表中的項目:

http://example.com/api/users?search=russell

您還可以使用查找API雙下划線表示法在ForeignKey或ManyToManyField上執行相關查找:

search_fields = ['username', 'email', 'profile__profession']

對於JSONFieldHStoreField字段,您可以使用相同的雙下划線符號根據數據結構內的嵌套值進行過濾:

search_fields = ['data__breed', 'data__owner__other_pets__0__name']

默認情況下,搜索將使用不區分大小寫的部分匹配。搜索參數可以包含多個搜索詞,應將其用空格和/或逗號分隔。如果使用了多個搜索詞,則僅當所有提供的詞都匹配時,對象才會在列表中返回。

可以通過在字符前面添加各種字符來限制搜索行為search_fields

  • '^'開始搜索。
  • '='完全匹配。
  • '@'全文搜索。(當前僅支持Django的PostgreSQL后端。)
  • '$'正則表達式搜索。

例如:

search_fields = ['=username', '=email']

默認情況下,搜索參數名為'search',但是此參數可能會被SEARCH_PARAM設置覆蓋

要根據請求內容動態更改搜索字段,可以對進行子類化SearchFilter並覆蓋該get_search_fields()函數。例如,以下子類僅title在查詢參數title_only在請求中時才搜索

from rest_framework import filters class CustomSearchFilter(filters.SearchFilter): def get_search_fields(self, view, request): if request.query_params.get('title_only'): return ['title'] return super(CustomSearchFilter, self).get_search_fields(view, request)

有關更多詳細信息,請參見Django文檔


訂購過濾器

OrderingFilter類支持的結果簡單的查詢參數進行控制順序。

訂購過濾器

默認情況下,查詢參數名為'ordering',但這可以被ORDERING_PARAM設置覆蓋

例如,按用戶名訂購用戶:

http://example.com/api/users?ordering=username

客戶端還可以通過在字段名稱前添加“-”來指定相反的順序,如下所示:

http://example.com/api/users?ordering=-username

也可以指定多個順序:

http://example.com/api/users?ordering=account,username

指定可以針對哪些字段進行排序

建議您明確指定API應在排序過濾器中允許的字段。您可以通過ordering_fields在視圖上設置屬性來做到這一點,如下所示:

class UserListView(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer filter_backends = [filters.OrderingFilter] ordering_fields = ['username', 'email']

這有助於防止意外的數據泄漏,例如允許用戶針對密碼哈希字段或其他敏感數據進行排序。

如果ordering_fields在視圖上指定屬性,則過濾器類將默認為允許用戶對由serializer_class屬性指定的序列化器上的任何可讀字段進行過濾

如果您確信該視圖使用的查詢集不包含任何敏感數據,則還可以通過使用special值明確指定一個視圖應允許對任何模型字段或查詢集集合進行排序'__all__'

class BookingsListView(generics.ListAPIView): queryset = Booking.objects.all() serializer_class = BookingSerializer filter_backends = [filters.OrderingFilter] ordering_fields = '__all__'

指定默認順序

如果ordering在視圖上設置屬性,則將其用作默認順序。

通常,您可以通過order_by在初始查詢集上進行設置來控制此操作,但是使用ordering視圖上參數可以指定順序,然后將其作為上下文自動傳遞到呈現的模板。如果使用列標題對結果進行排序,則可以自動呈現不同的列標題。

class UserListView(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer filter_backends = [filters.OrderingFilter] ordering_fields = ['username', 'email'] ordering = ['username']

ordering屬性可以是字符串,也可以是字符串列表/元組。


自定義通用過濾

您還可以提供自己的通用過濾后端,或編寫供其他開發人員使用的可安裝應用。

為此,請重寫BaseFilterBackend,並重寫該.filter_queryset(self, request, queryset, view)方法。該方法應返回一個經過過濾的新查詢集。

除了允許客戶端執行搜索和過濾外,通用過濾器后端對於限制任何給定請求或用戶應看到哪些對象也很有用。

例子

例如,您可能需要限制用戶只能看到他們創建的對象。

class IsOwnerFilterBackend(filters.BaseFilterBackend): """ Filter that only allows users to see their own objects. """ def filter_queryset(self, request, queryset, view): return queryset.filter(owner=request.user)

我們可以通過覆蓋get_queryset()視圖來實現相同的行為,但是使用過濾器后端可以使您更輕松地將此限制添加到多個視圖,或將其應用於整個API。

定制界面

通用過濾器也可以在可瀏覽的API中提供一個接口。為此,您應該實現一個to_html()方法,方法返回過濾器的呈現的HTML表示形式。此方法應具有以下簽名:

to_html(self, request, queryset, view)

該方法應返回呈現的HTML字符串。

分頁和模式

您還可以通過實現一種get_schema_fields()方法,使過濾器控件可用於REST框架提供的模式自動生成此方法應具有以下簽名:

get_schema_fields(self, view)

該方法應返回coreapi.Field實例列表

第三方套餐

以下第三方軟件包提供了其他過濾器實現。

Django REST框架過濾器軟件包

Django的休息框架過濾器封裝與一起工作DjangoFilterBackend類,並允許您輕松地在關系創建過濾器,或在指定字段創建多個過濾器查找類型。

Django REST框架全字搜索過濾器

djangorestframework詞過濾開發作為替代品filters.SearchFilter,這將搜索文本完整的單詞,或精確匹配。

Django URL過濾器

django-url-filter提供了一種通過人類友好的URL過濾數據的安全方法。從某種意義上說,它可以嵌套,但它們被稱為過濾器集和過濾器,在某種意義上來說,它的工作方式與DRF序列化器和字段非常相似。這提供了過濾相關數據的簡便方法。該庫也是通用的,因此可以用來過濾其他數據源,而不僅僅是Django QuerySet

drf-url-filters

DRF-URL過濾是一個簡單的Django應用程序對DRF應用濾鏡ModelViewSetQueryset一個清潔,簡單和可配置的方式。它還支持對傳入查詢參數及其值的驗證。一個漂亮的python包Voluptuous用於對傳入的查詢參數進行驗證。關於性感的最好的部分是您可以根據查詢參數要求定義自己的驗證。

class SearchFilter(BaseFilterBackend):
    # The URL query parameter used for the search.
    search_param = api_settings.SEARCH_PARAM
    template = 'rest_framework/filters/search.html'
    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.
        """
        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()

    def construct_search(self, field_name):
        lookup = self.lookup_prefixes.get(field_name[0])
        if lookup:
            field_name = field_name[1:]
        else:
            lookup = 'icontains'
        return LOOKUP_SEP.join([field_name, lookup])

    def must_call_distinct(self, queryset, search_fields):
        """
        Return True if 'distinct()' should be used to query the given lookups.
        """
        for search_field in search_fields:
            opts = queryset.model._meta
            if search_field[0] in self.lookup_prefixes:
                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:
                return False
            parts = search_field.split(LOOKUP_SEP)
            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
                    if any(path.m2m for path in path_info):
                        # This field is a m2m relation so we know we need to call distinct
                        return True
        return False

    def filter_queryset(self, request, queryset, view):
        search_fields = self.get_search_fields(view, request)
        search_terms = self.get_search_terms(request)

        if not search_fields or not search_terms:
            return queryset

        orm_lookups = [
            self.construct_search(str(search_field))
            for search_field in search_fields
        ]

        base = queryset
        conditions = []
        for search_term in search_terms:
            queries = [
                models.Q(**{orm_lookup: search_term})
                for orm_lookup in orm_lookups
            ]
            conditions.append(reduce(operator.or_, queries))
        queryset = queryset.filter(reduce(operator.and_, conditions))

        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.
            queryset = distinct(queryset, base)
        return queryset

    def to_html(self, request, queryset, view):
        if not getattr(view, 'search_fields', None):
            return ''

        term = self.get_search_terms(request)
        term = term[0] if term else ''
        context = {
            'param': self.search_param,
            'term': term
        }
        template = loader.get_template(self.template)
        return template.render(context)

    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 [
            coreapi.Field(
                name=self.search_param,
                required=False,
                location='query',
                schema=coreschema.String(
                    title=force_str(self.search_title),
                    description=force_str(self.search_description)
                )
            )
        ]

    def get_schema_operation_parameters(self, view):
        return [
            {
                'name': self.search_param,
                'required': False,
                'in': 'query',
                'description': force_str(self.search_description),
                'schema': {
                    'type': 'string',
                },
            },
        ]

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM