Django REST GET查询参数序列化:如何优雅解析并过滤数据?
我希望在Django REST Framework的接口中接收GET查询参数,并在参数存在时用其过滤数据集。目前我采用了比较粗糙的实现方式,但该方式未校验日期参数是否可被正确解析;在其他接口场景中,我还需要接收可转换为整数的字符串类型参数。有没有无需编写大量样板代码的Pythonic实现方法?
我的当前实现代码:
class TrackList(APIView): @token_required def get(self, request, pk, **kwargs): # read query params date_from = self.request.query_params.get('date_from') date_to = self.request.query_params.get('date_to') # if present then filter if date_from and date_to: points = Track.objects.filter(user_id=pk, date__range=[date_from, date_to]) # otherwise don't filter else: points = Track.objects.filter(user_id=pk) points.order_by('date') serializer = TrackListSerializer(points, many=True) return Response(serializer.data, status=status.HTTP_200_OK)
补充说明:问题并非针对dateutil.parser,而是关于通用查询参数解析方案,是否应该使用Django REST序列化器?
当然有更优雅的Pythonic方案,完全不用写一堆重复的校验代码,下面给你推荐几个常用的思路:
1. 用DRF序列化器做参数校验
这其实是DRF官方推荐的方式之一,专门用来处理请求参数的校验和转换,完美契合你的需求。你可以定义一个专门的查询参数序列化器,把要接收的参数类型、校验规则都写进去,DRF会自动帮你做解析和校验,不合法的参数直接返回标准化错误响应,不用自己手动处理。
示例代码:
from rest_framework import serializers from rest_framework.exceptions import ValidationError class TrackFilterSerializer(serializers.Serializer): date_from = serializers.DateField(required=False) date_to = serializers.DateField(required=False) # 其他需要的参数,比如整数类型的参数 some_integer = serializers.IntegerField(required=False, min_value=1) def validate(self, data): # 自定义额外校验,比如date_from不能晚于date_to if data.get('date_from') and data.get('date_to'): if data['date_from'] > data['date_to']: raise ValidationError("date_from不能晚于date_to") return data class TrackList(APIView): @token_required def get(self, request, pk, **kwargs): # 校验查询参数 serializer = TrackFilterSerializer(data=request.query_params) serializer.is_valid(raise_exception=True) validated_data = serializer.validated_data # 构建查询条件 queryset = Track.objects.filter(user_id=pk) if validated_data.get('date_from') and validated_data.get('date_to'): queryset = queryset.filter(date__range=[validated_data['date_from'], validated_data['date_to']]) # 处理其他参数 if validated_data.get('some_integer'): queryset = queryset.filter(some_field=validated_data['some_integer']) queryset = queryset.order_by('date') serializer = TrackListSerializer(queryset, many=True) return Response(serializer.data, status=status.HTTP_200_OK)
这样做的好处:
- 自动处理参数类型转换(比如字符串转日期、转整数)
- 自动返回标准化的错误响应(比如日期格式不对时,会返回400错误和详细提示)
- 序列化器可以复用在其他需要相同参数的接口
- 自定义校验逻辑统一写在
validate方法里,代码更整洁
2. 使用django-filter库
如果你的接口有大量过滤需求,django-filter绝对是神器,它和DRF集成得非常好,能帮你快速构建复杂的过滤逻辑,完全不用手动写一堆if判断。
首先安装库:
pip install django-filter
然后配置到settings.py:
INSTALLED_APPS = [ ... 'django_filters', ] REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'], }
接着定义过滤器类:
import django_filters from .models import Track class TrackFilter(django_filters.FilterSet): date_from = django_filters.DateFilter(field_name='date', lookup_expr='gte') date_to = django_filters.DateFilter(field_name='date', lookup_expr='lte') some_integer = django_filters.NumberFilter(field_name='some_field', lookup_expr='exact') class Meta: model = Track fields = ['date_from', 'date_to', 'some_integer']
最后修改你的视图:
from django_filters.rest_framework import DjangoFilterBackend class TrackList(APIView): filter_backends = [DjangoFilterBackend] filterset_class = TrackFilter @token_required def get(self, request, pk, **kwargs): queryset = Track.objects.filter(user_id=pk) # 应用过滤 queryset = self.filter_queryset(queryset) queryset = queryset.order_by('date') serializer = TrackListSerializer(queryset, many=True) return Response(serializer.data, status=status.HTTP_200_OK)
这种方式更适合过滤需求复杂的场景,比如支持多种查询条件、模糊匹配、范围查询等,而且代码量极少,完全符合Pythonic的简洁风格。
3. 自定义通用参数解析装饰器
如果不想引入第三方库,也可以自己写一个通用的装饰器,用来处理参数的解析和校验,这样可以在多个视图里复用。
示例代码:
from functools import wraps from rest_framework.exceptions import ValidationError from datetime import datetime def parse_query_params(param_rules): def decorator(view_func): @wraps(view_func) def wrapped(request, *args, **kwargs): parsed_params = {} for param_name, rule in param_rules.items(): param_value = request.query_params.get(param_name) if not param_value: continue # 根据规则解析参数 try: if rule == 'date': parsed_params[param_name] = datetime.strptime(param_value, '%Y-%m-%d').date() elif rule == 'int': parsed_params[param_name] = int(param_value) # 可以扩展其他类型,比如float、bool等 except (ValueError, TypeError): raise ValidationError(f"参数{param_name}格式错误,应为{rule}类型") # 把解析后的参数放到request对象里 request.parsed_params = parsed_params return view_func(request, *args, **kwargs) return wrapped return decorator
然后在视图里使用:
class TrackList(APIView): @token_required @parse_query_params({'date_from': 'date', 'date_to': 'date', 'some_integer': 'int'}) def get(self, request, pk, **kwargs): parsed_params = request.parsed_params queryset = Track.objects.filter(user_id=pk) if parsed_params.get('date_from') and parsed_params.get('date_to'): queryset = queryset.filter(date__range=[parsed_params['date_from'], parsed_params['date_to']]) if parsed_params.get('some_integer'): queryset = queryset.filter(some_field=parsed_params['some_integer']) queryset = queryset.order_by('date') serializer = TrackListSerializer(queryset, many=True) return Response(serializer.data, status=status.HTTP_200_OK)
这个方式比较轻量,适合简单的参数解析场景,而且可以根据自己的需求灵活扩展规则。
内容的提问来源于stack exchange,提问作者Diman

