DRF SerializerMethodField配合django-filters使用报FieldError解决方案
报错原因
SerializerMethodField 是序列化执行阶段动态计算生成的只读字段,不会映射为数据库表中的实际列。django-filters 默认的过滤逻辑是直接将传入参数转换为ORM数据库查询条件作用于QuerySet,你直接将非模型字段的is_you配置到filterset_fields中时,ORM无法在数据库表中找到对应字段,就会抛出Cannot resolve keyword 'is_you' into field的FieldError。
实现方案
方案1:自定义FilterSet实现过滤(推荐,性能最优)
放弃使用filterset_fields自动生成过滤项,手动定义FilterSet类,为自定义字段编写对应的数据库查询逻辑,在查询层完成过滤,性能最高。
- 在app目录下新建
filters.py(也可直接写在视图文件中,单独维护更清晰),编写自定义过滤类:import django_filters from .models import User class MemberFilter(django_filters.FilterSet): # 声明自定义过滤字段,绑定过滤方法 is_you = django_filters.BooleanFilter(method='filter_is_you') # 其余自定义字段比如bookmark可以用同样方式声明 # bookmark = django_filters.BooleanFilter(method='filter_bookmark') class Meta: model = User # 模型原生字段正常配置在这里即可 fields = ['username', 'email'] def filter_is_you(self, queryset, name, value): request = self.request # 未登录状态不存在当前用户,筛选is_you=True时直接返回空集 if not request.user.is_authenticated: return queryset.none() if value else queryset if value: # 筛选出当前登录用户本人 return queryset.filter(pk=request.user.pk) # 筛选出非当前登录用户的条目 return queryset.exclude(pk=request.user.pk) - 修改视图配置,删除原有的
filterset_fields项,指定自定义的过滤类:from rest_framework import viewsets from django_filters.rest_framework import DjangoFilterBackend from .models import User from .serializers import MemberSerializer from .filters import MemberFilter class MemberApi(viewsets.ReadOnlyModelViewSet): queryset = User.objects.all() serializer_class = MemberSerializer filter_backends = [DjangoFilterBackend] # 替换原filterset_fields配置 filterset_class = MemberFilter
方案2:序列化后本地过滤(仅适合极小数据量场景,不推荐)
如果自定义字段的逻辑极度复杂,无法转换为ORM查询语句,可以重写视图的filter_queryset方法,在原生字段过滤完成后,手动遍历判断计算字段值做二次筛选。注意该方案会先拉取所有符合原生过滤条件的条目,全量实例化后再筛选,数据量超过百条就会出现明显性能问题。
class MemberApi(viewsets.ReadOnlyModelViewSet): queryset = User.objects.all() serializer_class = MemberSerializer filter_backends = [DjangoFilterBackend] filterset_fields = ['username', 'email'] # 只保留模型原生字段 def filter_queryset(self, queryset): # 先执行原生字段的默认过滤逻辑 queryset = super().filter_queryset(queryset) is_you_param = self.request.query_params.get('is_you') if is_you_param is None: return queryset # 转换参数为布尔值 is_you = is_you_param.lower() in ['true', '1', 'yes'] current_user = self.request.user # 遍历匹配符合条件的条目ID match_ids = [] for user in queryset: # 判断逻辑和SerializerMethodField中is_you的逻辑完全一致 check_val = user.pk == current_user.pk if current_user.is_authenticated else False if check_val == is_you: match_ids.append(user.pk) return queryset.filter(pk__in=match_ids)
补充说明
- 所有通过
SerializerMethodField生成的自定义序列化字段,本质是Python层面动态计算的属性,不对应数据库字段,不能直接写入filterset_fields配置项要求django-filters自动生成过滤规则。 - 优先选择自定义FilterSet的方案,直接在数据库查询层完成筛选,避免全量拉取数据序列化导致的性能损耗,数据量越大优势越明显。
- 你定义的
bookmark自定义字段,同样可以参照方案1的写法实现过滤,只需要在FilterSet中新增对应过滤方法,编写ORM查询逻辑判断当前登录用户与目标用户的收藏关联关系即可。
内容的提问来源于stack exchange,提问作者Abdul Hannan
相关产品推荐
相关产品推荐

