如何修改DjangoFilterBackend无匹配参数时的返回行为?
使用DjangoFilterBackend处理ModelViewSet的查询参数时,当传入的查询参数(如document_guid)在关联表中无匹配记录时,期望返回空数组[],但实际返回如下验证错误:
{ "document_guid": [ "Select a valid choice. That choice is not one of the available choices." ] }
需求:修改该行为,让无匹配时返回空数组,且希望在DjangoFilterBackend相关逻辑中修改,而非重写views.py中的视图逻辑。
示例请求URI(user_guid有匹配记录,但document_guid无匹配):
localhost/api/d2u/?user_guid=55d57235-79b0-ec11-9840-000d3a5a343c&document_guid=df4c9c11-49dd-ec11-a7b6-000d3a3667a1
相关代码:
views.py
class Document2UserSyncViewSet(viewsets.ModelViewSet): filter_backends = [DjangoFilterBackend] filter_fields = [ 'id', 'document_guid', 'user_guid' ] permission_classes = [PowerAutomatePermissions] queryset = Document2User.objects.all().order_by('id') serializer_class = Document2UserSerializer
serializer.py
class Document2UserSerializer(serializers.ModelSerializer): """Serializer for the d2u model. Returns all fields. """ class Meta: model = Document2User fields = '__all__'
models.py
class Document2User(models.Model): document_guid = models.ForeignKey( Documents, to_field='guid', on_delete=models.CASCADE, db_column='document_guid' ) user_guid = models.ForeignKey( CRMFields, to_field='guid', on_delete=models.CASCADE, db_column='user_guid' ) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) class Meta: db_table = 'document_to_user' unique_together = [['user_guid', 'document_guid']]
出现该问题的核心原因是:document_guid和user_guid为外键字段,使用filter_fields自动生成过滤器时,Django Filter默认会使用ModelChoiceFilter——这个过滤器会先验证传入的参数是否存在于关联模型(Documents、CRMFields)中,不存在则抛出验证错误,而非直接返回空查询集。
方法一:自定义FilterSet(推荐,精准控制字段)
放弃使用filter_fields,改用filterset_class自定义过滤器集合,将外键对应的过滤器替换为CharFilter,跳过存在性验证,直接执行数据库查询。
- 在
views.py所在目录新建filters.py(或直接在views.py中添加):
import django_filters from .models import Document2User class Document2UserFilter(django_filters.FilterSet): # 用CharFilter替代默认的ModelChoiceFilter,直接按guid字符串过滤关联字段 document_guid = django_filters.CharFilter(field_name='document_guid__guid') user_guid = django_filters.CharFilter(field_name='user_guid__guid') class Meta: model = Document2User fields = ['id', 'document_guid', 'user_guid']
- 修改
views.py中的视图集,替换filter_fields为自定义FilterSet:
class Document2UserSyncViewSet(viewsets.ModelViewSet): filter_backends = [DjangoFilterBackend] # 替换filter_fields为自定义的FilterSet filterset_class = Document2UserFilter permission_classes = [PowerAutomatePermissions] queryset = Document2User.objects.all().order_by('id') serializer_class = Document2UserSerializer
修改后,传入不存在的document_guid或user_guid时,过滤器会直接查询Document2User表中关联字段匹配的记录,无匹配则返回空数组[]。
方法二:自定义FilterBackend(全局修改行为)
如果需要全局统一修改所有外键字段的过滤行为,可以自定义继承自DjangoFilterBackend的类,重写get_filterset方法,自动将所有ModelChoiceFilter替换为CharFilter。
- 在项目中新建
backends.py(或在views.py中添加):
from django_filters.rest_framework import DjangoFilterBackend import django_filters class NoValidationDjangoFilterBackend(DjangoFilterBackend): def get_filterset(self, request, queryset, view): filterset_class = self.get_filterset_class(view, queryset) if not filterset_class: return None # 获取默认生成的过滤器集合 filterset = filterset_class(request.query_params, queryset=queryset) # 遍历过滤器,替换ModelChoiceFilter为CharFilter for name, filter_obj in filterset.filters.items(): if isinstance(filter_obj, django_filters.ModelChoiceFilter): # 拼接关联的guid字段路径 new_field_name = f"{name}__guid" filterset.filters[name] = django_filters.CharFilter(field_name=new_field_name) return filterset
- 修改
views.py中的filter_backends,使用自定义的FilterBackend:
class Document2UserSyncViewSet(viewsets.ModelViewSet): # 替换为自定义的FilterBackend filter_backends = [NoValidationDjangoFilterBackend] filter_fields = [ 'id', 'document_guid', 'user_guid' ] permission_classes = [PowerAutomatePermissions] queryset = Document2User.objects.all().order_by('id') serializer_class = Document2UserSerializer
这种方法无需逐个修改FilterSet,适合需要全局统一处理外键过滤行为的场景。
内容的提问来源于stack exchange,提问作者strumpy_strudel

