简体   繁体   English

如何在 Django 中动态地向查询添加过滤器?

[英]How to add filters to a query dynamically in Django?

In my viewSet I am doing a query,在我的 viewSet 中,我正在做一个查询,

queryset= Books.objects.all();

Now from an ajax call I get my filter values from UI ie age,gender, etc. of auther.There will be a total of 5 filters.现在,从 ajax 调用中,我从 auther 的用户界面(即年龄、性别等)获取我的过滤器值。总共有 5 个过滤器。

Now the problem which I ran into is how am I going to add filters to my query(only those filters which have any value).现在我遇到的问题是如何将过滤器添加到我的查询中(只有那些具有任何值的过滤器)。

What I tried is I checked for individual filter value and did query, but that way it fails as if the user remove the filter value or add multiple filters.我尝试的是我检查了单个过滤器值并进行了查询,但是这样它就失败了,就好像用户删除了过滤器值或添加了多个过滤器一样。 Any better suggestion how to accomplish this?任何更好的建议如何实现这一点?

Here's a bit more generic one.这里有一个更通用的。 It will apply filters to your queryset if they are passed as the GET parameters.如果过滤器作为GET参数传递,它会将过滤器应用于您的查询集。 If you're doing a POST call, just change the name in the code.如果您正在进行POST调用,只需更改代码中的名称即可。

import operator
from django.db.models import Q


def your_view(self, request, *args, **kwargs):
    # Here you list all your filter names
    filter_names = ('filter_one', 'filter_two', 'another_one', )

    queryset = Books.objects.all(); 
    filter_clauses = [Q(filter=request.GET[filter])
                      for filter in filter_names
                      if request.GET.get(filter)]
    if filter_clauses:
        queryset = queryset.filter(reduce(operator.and_, filter_clauses))

    # rest of your view

Note that you can use lookup expressions in your filters' names.请注意,您可以在过滤器名称中使用查找表达式。 For example, if you want to filter books with price lower or equal to specified in filter, you could just use price__lte as a filter name.例如,如果您想过滤价格低于或等于过滤器中指定的价格的书籍,您可以使用price__lte作为过滤器名称。

You haven't shown any code, so you haven't really explained what the problem is:你没有显示任何代码,所以你没有真正解释问题是什么:

Start with the queryset Book.objects.all() .从查询集Book.objects.all() For each filter, check if there is a value for the filter in request.POST , and if so, filter the queryset.对于每个过滤器,检查request.POST是否有过滤器的值,如果有,过滤查询集。 Django querysets are lazy, so only the final queryset will be evaluated. Django 查询集是惰性的,因此只会评估最终的查询集。

queryset = Book.objects.all()
if request.POST.get('age'):
    queryset = queryset.filter(author__age=request.POST['age'])
if request.POST.get('gender'):
    queryset = queryset.filter(author__gender=request.POST['gender'])
...

You can simply get the request.GET content as a dict (making sure to convert the values to string or a desired type as they'd be list by default ie: dict(request.GET) would give you something like {u'a': [u'val']} .您可以简单地将 request.GET 内容作为 dict 获取(确保将值转换为字符串或所需的类型,因为默认情况下它们是列表,即: dict(request.GET)会给您类似{u'a': [u'val']}

Once you are sure you have a dictionary of keys matching your model fields, you can simply do:一旦你确定你有一个匹配你的模型字段的键字典,你可以简单地做:

filtered = queryset.filter(**dict_container)

Maybe django-filter would help simplify the solutions others have given?也许django-filter会帮助简化其他人给出的解决方案?

Something like:就像是:

class BookFilter(django_filters.FilterSet):
    class Meta:
        model = Book
        fields = ['author__age', 'author__gender', ...]

Then the view looks like:然后视图看起来像:

def book_list(request):
    f = BookFilter(request.GET, queryset=Book.objects.all())
    return render_to_response('my_app/template.html', {'filter': f})

For more information see the documentation .有关更多信息,请参阅文档

this worked for me, I've merged Alex Morozov answer with Dima answer这对我有用,我已将 Alex Morozov 的答案与Dima 的答案合并

import operator

def your_view(self, request, *args, **kwargs):
    # Here you list all your filter names
    filter_names = ('filter_one', 'filter_two', 'another_one', )

    queryset = Books.objects.all(); 
    filter_clauses = [Q(**{filter: request.GET[filter]})
                  for filter in filter_names
                  if request.GET.get(filter)]
    if filter_clauses:
    queryset = queryset.filter(reduce(operator.and_, filter_clauses))

    # rest of your view

You can do something like that你可以做这样的事情

class BooksAPI(viewsets.ModelViewSet):
    queryset = Books.objects.none()


def get_queryset(self):
    argumentos = {}
    if self.request.query_params.get('age'):
        argumentos['age'] = self.request.query_params.get('age')
    if self.request.query_params.get('gender'):
        argumentos['gender'] = self.request.query_params.get('gender')
    if len(argumentos) > 0:
        books = Books.objects.filter(**argumentos)
    else:
        books = Books.objects.all()
    return books

For a very simple equality check, here is my solution using a helper function in a ModelViewSet.对于非常简单的相等性检查,这是我在 ModelViewSet 中使用辅助函数的解决方案。

  1. The helper function check_for_params creates a dictionary of request parameters passed in the URL.辅助函数check_for_params创建一个包含在 URL 中传递的请求参数的字典。

  2. Alter the ModelViewSet get_queryset() method by filtering the Django QuerySet with a single filter clause which prevents multiple queries being called by chaining filters .通过使用单个过滤器子句过滤 Django QuerySet 来更改get_queryset()方法,该过滤器子句可防止链接过滤器调用多个查询

I could tried to use the Django Q() object but could not get it to only make a single call.我可以尝试使用 Django Q() 对象,但无法让它只进行一次调用。

def check_for_params(request, param_check_list: List) -> dict:
    """
    Create a dictionary of params passed through URL.

    Parameters
    ----------
    request - DRF Request object.
    param_check_list - List of params potentially passed in the url.
    """
    if not param_check_list:
        print("No param_check_list passed.")
    else:
        param_dict = {}
        for p in param_check_list:
            param_dict[p] = request.query_params.get(p, None)
        return param_dict
    

class MyModelViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
    authentication_classes = [SessionAuthentication]
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        """
        Return a queryset and apply filters, if applicable.
        
        Info
        ----
        Building the queryset.filter method by unpacking the key-value pairs this way,
        creates a single filter clause and prevents multiple queries from being called
        by chaining filters.
        """
        queryset = MyModel.objects.all()

        param_check_list = ['param1', 'param2', 'param3']
        params = check_for_params(self.request, param_check_list)
        filtered = {k: v for k, v in params.items() if v}

        # Calling filter() only once here prevents multiple queries. 
        return queryset.filter(**filtered)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM