如何在 Django 中动态地向查询添加过滤器?
How to add filters to a query dynamically in Django?
在我的 viewSet 中我正在做一个查询,
queryset= Books.objects.all();
现在,通过 ajax 调用,我从 UI 中获取过滤器值,即 auther.There 的年龄、性别等总共有 5 个过滤器。
现在我 运行 遇到的问题是如何将过滤器添加到我的查询中(仅那些具有任何值的过滤器)。
我尝试的是检查单个过滤器值并进行查询,但那样就失败了,就好像用户删除了过滤器值或添加了多个过滤器。
有更好的建议如何实现吗?
您没有显示任何代码,所以您没有真正解释问题所在:
从查询集开始 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'])
...
这里有一个更通用的。如果它们作为 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
作为过滤器名称。
也许 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。
您可以简单地将 request.GET 内容作为字典获取(确保将值转换为字符串或所需的类型,因为它们默认会列出,即:dict(request.GET)
会给您像 {u'a': [u'val']}
.
一旦你确定你有一个键字典匹配你的模型字段,你可以简单地做:
filtered = queryset.filter(**dict_container)
这对我有用,我已将 Alex Morozov 的回答与
合并
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
你可以这样做
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
对于非常简单的相等性检查,这是我在 ModelViewSet 中使用辅助函数的解决方案。
辅助函数 check_for_params
创建一个在 URL.
中传递的请求参数的字典
更改 ModelViewSet get_queryset()
方法,通过使用单个过滤器子句过滤 Django 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)
在我的 viewSet 中我正在做一个查询,
queryset= Books.objects.all();
现在,通过 ajax 调用,我从 UI 中获取过滤器值,即 auther.There 的年龄、性别等总共有 5 个过滤器。
现在我 运行 遇到的问题是如何将过滤器添加到我的查询中(仅那些具有任何值的过滤器)。
我尝试的是检查单个过滤器值并进行查询,但那样就失败了,就好像用户删除了过滤器值或添加了多个过滤器。 有更好的建议如何实现吗?
您没有显示任何代码,所以您没有真正解释问题所在:
从查询集开始 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'])
...
这里有一个更通用的。如果它们作为 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
作为过滤器名称。
也许 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。
您可以简单地将 request.GET 内容作为字典获取(确保将值转换为字符串或所需的类型,因为它们默认会列出,即:dict(request.GET)
会给您像 {u'a': [u'val']}
.
一旦你确定你有一个键字典匹配你的模型字段,你可以简单地做:
filtered = queryset.filter(**dict_container)
这对我有用,我已将 Alex Morozov 的回答与
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
你可以这样做
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
对于非常简单的相等性检查,这是我在 ModelViewSet 中使用辅助函数的解决方案。
辅助函数
中传递的请求参数的字典check_for_params
创建一个在 URL.更改 ModelViewSet
get_queryset()
方法,通过使用单个过滤器子句过滤 Django 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)