【问题标题】:How to add filters to a query dynamically in Django?如何在 Django 中动态地将过滤器添加到查询中?
【发布时间】:2016-04-16 19:59:36
【问题描述】:

在我的视图集中我正在做一个查询,

queryset= Books.objects.all();

现在通过 ajax 调用,我从 UI 中获取过滤器值,即 auther 的年龄、性别等。总共将有 5 个过滤器。

现在我遇到的问题是如何将过滤器添加到我的查询中(只有那些具有任何值的过滤器)。

我尝试的是检查单个过滤器值并进行查询,但这样会失败,就像用户删除过滤器值或添加多个过滤器一样。 有什么更好的建议如何做到这一点?

【问题讨论】:

    标签: python django django-orm


    【解决方案1】:

    这里有一个更通用的。如果它们作为GET 参数传递,它将对您的查询集应用过滤器。如果您正在拨打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
    

    请注意,您可以在过滤器名称中使用查找表达式。例如,如果您想过滤价格低于或等于过滤器中指定的图书,您可以使用price__lte 作为过滤器名称。

    【讨论】:

    • 这比我的回答更干。我认为首先理解我的答案更容易,因此这两个答案可以很好地协同工作:)
    • 请注意,您可能需要request.GET.get(filter) 而不是filter in request.GET,如果提交空字符串,它们会给出不同的结果。
    • @Alasdair,很好的收获,谢谢!用您的建议更新了答案。你完全正确,我个人不喜欢我的答案中的reduce 部分,希望我们能用更多的python 方式表达它。
    • @AmanGupta,是的,Django 还允许您查询链接模型的字段:filters = ('a_book_field', 'author__an_author_field', ) 等。混合它们完全没问题。
    • 我正试图做到这一点,但它总是说Cannot resolve keyword 'filter' into field。有什么想法吗?
    【解决方案2】:

    您没有显示任何代码,因此您还没有真正解释问题所在:

    从查询集 Book.objects.all() 开始。对于每个过滤器,检查request.POST 中是否有过滤器的值,如果有,则过滤查询集。 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'])
    ...
    

    【讨论】:

    • 值得一提的是,它们仅在需要结果时才进行评估 :) +1 anywho
    【解决方案3】:

    您可以简单地将 request.GET 内容作为 dict 获取(确保将值转换为字符串或所需的类型,因为它们会在默认情况下列出,即:dict(request.GET) 会给您类似 {u'a': [u'val']} 的内容。

    一旦你确定你有一个与你的模型字段匹配的键字典,你可以简单地这样做:

    filtered = queryset.filter(**dict_container)

    【讨论】:

      【解决方案4】:

      也许django-filter 会帮助简化其他人给出的解决方案?

      类似:

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

      那么视图看起来像:

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

      有关详细信息,请参阅documentation

      【讨论】:

        【解决方案5】:

        这对我有用,我已将 Alex Morozov 的答案与 Dima answer 合并

        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
        

        【讨论】:

          【解决方案6】:

          你可以这样做

          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
          

          【讨论】:

          • 我想要年龄范围过滤器,那么我应该怎么做
          • if self.request.query_params.get('age'): argumentos['age__gte'] = self.request.query_params.get('age') argumentos['age__lte'] = self.request.query_params.get('age') 。 @Deepak Chawla
          【解决方案7】:

          对于一个非常简单的平等检查,这是我在 ModelViewSet 中使用辅助函数的解决方案。

          1. 帮助函数check_for_params 创建一个在 URL 中传递的请求参数字典。

          2. 通过使用单个过滤器子句过滤 Django QuerySet 来更改 ModelViewSet get_queryset() 方法,从而阻止 multiple queries being called by chaining filters

          我可以尝试使用 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)
          

          【讨论】:

            猜你喜欢
            • 2021-04-29
            • 2020-11-13
            • 2010-10-25
            • 1970-01-01
            • 1970-01-01
            • 2021-07-09
            • 1970-01-01
            相关资源
            最近更新 更多