【问题标题】:Django REST framework: how to respond with useful error messages with get_queryset()Django REST 框架:如何使用 get_queryset() 响应有用的错误消息
【发布时间】:2021-07-01 19:09:11
【问题描述】:

我有一个 django 模型,我想通过 Django Rest 框架显示它。我正在通过get_queryset() 显示模型中的所有对象。但是,我也有几个 query_params 会过滤掉某些对象。这是我工作正常的主要代码:

class PlanView(generics.ListAPIView):
    """
    API endpoint which allows prices to be viewed or edited
    """

    serializer_class = PlanSerializer
    permission_classes = (IsAuthenticatedOrReadOnly,)

    # override method
    def get_queryset(self):
        //get all objects in Plan model
        queryset = Plan.objects.all()

        // possible query parameters to be read from url
        size = self.request.query_params.get("size", None)
        price = self.request.query_params.get("price", None)

        if size is not None:
            if size == "large":
                queryset = queryset.filter(Large=True)
            elif size == "small":
                queryset = queryset.filter(Large=False)

        if price is not None:
            queryset = queryset.filter(price=price)

        return queryset

用这个urlpattern:

path(r'API/plans', views.PlanView.as_view(), name='prices'),

唯一的问题是,当我有目的地在浏览器中编写下面的 URL 时,

http://127.0.0.1:8000/API/plans?size=sm

如果 query_param 值错误/拼写错误,get_query() 代码将忽略它并显示对象,就好像没有过滤器一样。

我试着写一个 else 语句,例如:

    if size is not None:
            if size == "large":
                queryset = queryset.filter(Large=True)
            elif size == "small":
                queryset = queryset.filter(Large=False)
            else:
                return Response({"Error":"bad request"}, status=status.HTTP_400_BAD_REQUEST)

但是有了这个,我收到一条错误消息:

ContentNotRenderedError at /API/plans
The response content must be rendered before it can be iterated over.

如果用户在 API 中输入错误的参数值,我如何显示有用的错误响应/json?

【问题讨论】:

    标签: django rest api django-rest-framework http-status-codes


    【解决方案1】:

    您可以使用ValidationError

    from rest_framework.exceptions import ValidationError
    # ...
            raise ValidationError(detail="size must be either 'large' or 'small'")
    

    DRF 捕获这些异常并整齐地显示它们。它返回一个 JSON 格式

    {
        "detail": "size must be either 'large' or 'small'"
    }
    

    【讨论】:

    • 如果你真的需要手动提升它,你应该提出一个ValidationErrorAPIException 是 500(服务器错误,不是验证错误/400)。 code 参数不是 HTTP 状态,它是一个字符串,并且只能在值被序列化到客户端之前在 ErrorDetail 对象中访问。
    • @AndrewBacker 你说得对,我改变了答案来反映这一点。
    【解决方案2】:

    有两种处理方法:1) 手动验证或2) 使用django-filters 包。

    1) 手动引发 ValidationError(最简单)

    if size not in ['small', 'large']:
        raise ValidationError(f"Invalid size {size}.  Please use small/large")
    else:
        # filter normally.  remember to consider the '' value
    

    2) 使用django-filters(最佳)

    过滤器可帮助您将过滤和排序逻辑与视图集分开,并且无需手动检查/解析/验证传入数据。对于ChoiceFilter (docs),它还会验证输入并为您引发错误。

    from django_filters import TypedChoiceFilter
    from django_filters.rest_framework import FilterSet, DjangoFilterBackend
    from rest_framework.fields import CharField
    from rest_framework.generics import ListAPIView
    from rest_framework.permissions import AllowAny
    from rest_framework.serializers import ModelSerializer
    
    
    class MyFilter(FilterSet):
        # this will return a 400/validation error if not a or b
        # but it will ignore blank.
        size = TypedChoiceFilter(
            field_name='Large', # your column name was 'Large'
            choices=[('small', 'Small'), ('large', 'Large')],
            convert=lambda value: value == 'large'  # true if value is Large
        )
    
    class MyView(ListAPIView):
        filter_backends = [DjangoFilterBackend]
        filter_class = MyFilter
        ...
    
        def get_queryset(self):
            return TheModel.objects.all()
    
    

    查看您的架构,如果这个问题是准确的,您还可以使用过滤器包中记录的方法,或者如果您将名称更改为 is_large,则使用 BooleanFilter,例如is_large=True.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-20
      • 2017-11-29
      • 2020-11-17
      • 2021-11-05
      • 1970-01-01
      • 2020-10-11
      • 2019-08-09
      • 1970-01-01
      相关资源
      最近更新 更多