【问题标题】:While trying to post mptt model via django rest framework getting ValueError: Cannot use None as a query value尝试通过 django rest 框架发布 mptt 模型时出现 ValueError: Cannot use None as a query value
【发布时间】:2017-11-10 10:14:20
【问题描述】:

这是我要为其编写测试的视图:

class RestaurantsTreeView(generics.ListCreateAPIView):
    serializer_class = RestarauntsTreeSerializer

    def get_serializer_class(self):
        from rest_framework import serializers
        if self.request.method == 'GET':
            return RestarauntsTreeSerializer
        parent_choices = self.request.user.restaurants_set.filter(status=Restaurants.VISIBLE)

        class NestedRestaurantDetailSerializer(serializers.ModelSerializer):
            parent_id = serializers.RelatedField(queryset=parent_choices, allow_null=True, required=False)

            class Meta:
                model = Restaurants
                fields = ("id", "name", "parent_id")

        return NestedRestaurantDetailSerializer

这是我试图通过 POST 请求创建的模型:

from mptt.models import MPTTModel, TreeForeignKey


class Restaurants(MPTTModel):
    name = models.CharField(max_length=255, blank=True, null=True)
    parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True)

    class MPTTMeta:
        order_insertion_by = ['name']

    def __str__(self):
        return self.name

最后是我的测试:

class CreateRestaurantTestCase(TestCase):
    def setUp(self):
        self.user = UserFactory.create()
        self.user.user_permissions.add(Permission.objects.get(codename='add_restaurants'))
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)

    def test_authorization_required(self):
        response = self.client.post(reverse('api_v1:restaurants_list'), data={
            "parent_id": None,
            "name": "fake restaurant",
        })
        self.assertEqual(response.status_code, 401)

返回该错误:

Error
Traceback (most recent call last):
  File "project/cafe/tests/api/test_restaurants.py", line 26, in test_required_fields
    response = self.client.post(reverse('api_v1:restaurants_list'), data={})
  File "env/lib/python3.5/site-packages/rest_framework/test.py", line 299, in post
    path, data=data, format=format, content_type=content_type, **extra)
  File "env/lib/python3.5/site-packages/rest_framework/test.py", line 212, in post
    return self.generic('POST', path, data, content_type, **extra)
  File "env/lib/python3.5/site-packages/rest_framework/test.py", line 237, in generic
    method, path, data, content_type, secure, **extra)
  File "env/lib/python3.5/site-packages/django/test/client.py", line 416, in generic
    return self.request(**r)
  File "env/lib/python3.5/site-packages/rest_framework/test.py", line 288, in request
    return super(APIClient, self).request(**kwargs)
  File "env/lib/python3.5/site-packages/rest_framework/test.py", line 240, in request
    request = super(APIRequestFactory, self).request(**kwargs)
  File "env/lib/python3.5/site-packages/django/test/client.py", line 501, in request
    six.reraise(*exc_info)
  File "env/lib/python3.5/site-packages/django/utils/six.py", line 686, in reraise
    raise value
  File "env/lib/python3.5/site-packages/django/core/handlers/exception.py", line 41, in inner
    response = get_response(request)
  File "env/lib/python3.5/site-packages/django/core/handlers/base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "env/lib/python3.5/site-packages/django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "env/lib/python3.5/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File "env/lib/python3.5/site-packages/django/views/generic/base.py", line 68, in view
    return self.dispatch(request, *args, **kwargs)
  File "env/lib/python3.5/site-packages/rest_framework/views.py", line 489, in dispatch
    response = self.handle_exception(exc)
  File "env/lib/python3.5/site-packages/rest_framework/views.py", line 449, in handle_exception
    self.raise_uncaught_exception(exc)
  File "env/lib/python3.5/site-packages/rest_framework/views.py", line 486, in dispatch
    response = handler(request, *args, **kwargs)
  File "env/lib/python3.5/site-packages/rest_framework/generics.py", line 244, in post
    return self.create(request, *args, **kwargs)
  File "env/lib/python3.5/site-packages/rest_framework/mixins.py", line 21, in create
    self.perform_create(serializer)
  File "env/lib/python3.5/site-packages/rest_framework/mixins.py", line 26, in perform_create
    serializer.save()
  File "env/lib/python3.5/site-packages/rest_framework/serializers.py", line 214, in save
    self.instance = self.create(validated_data)
  File "env/lib/python3.5/site-packages/rest_framework/serializers.py", line 913, in create
    instance = ModelClass.objects.create(**validated_data)
  File "env/lib/python3.5/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "env/lib/python3.5/site-packages/django/db/models/query.py", line 394, in create
    obj.save(force_insert=True, using=self.db)
  File "env/lib/python3.5/site-packages/mptt/models.py", line 977, in save
    right_sibling = opts.get_ordered_insertion_target(self, parent)
  File "env/lib/python3.5/site-packages/mptt/models.py", line 216, in get_ordered_insertion_target
    queryset = node.__class__._tree_manager.db_manager(node._state.db).filter(filters).order_by(*order_by)
  File "env/lib/python3.5/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "env/lib/python3.5/site-packages/django/db/models/query.py", line 784, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "env/lib/python3.5/site-packages/django/db/models/query.py", line 802, in _filter_or_exclude
    clone.query.add_q(Q(*args, **kwargs))
  File "env/lib/python3.5/site-packages/django/db/models/sql/query.py", line 1250, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "env/lib/python3.5/site-packages/django/db/models/sql/query.py", line 1270, in _add_q
    current_negated, allow_joins, split_subq)
  File "env/lib/python3.5/site-packages/django/db/models/sql/query.py", line 1276, in _add_q
    allow_joins=allow_joins, split_subq=split_subq,
  File "env/lib/python3.5/site-packages/django/db/models/sql/query.py", line 1160, in build_filter
    value, lookups, used_joins = self.prepare_lookup_value(value, lookups, can_reuse, allow_joins)
  File "env/lib/python3.5/site-packages/django/db/models/sql/query.py", line 989, in prepare_lookup_value
    raise ValueError("Cannot use None as a query value")
ValueError: Cannot use None as a query value

Destroying test database for alias 'default'...

Process finished with exit code 1

我希望能够创建带有 null 父字段的餐厅。它可以在没有父字段的情况下发布,也可以在父字段为空的情况下发布。谢谢 django 1.11,drf 3.7,mptt 0.8.7

【问题讨论】:

  • Traceback(最近一次调用最后):文件“project/cafe/tests/api/test_restaurants.py”,第 26 行,在 test_required_fields response = self.client.post(reverse('api_v1:restaurants_list '), data={}) ---- 你的代码在哪里?
  • RestaurantTreeView(generics.ListCreateAPIView) is reverse('api_v1:restaurants_list')
  • 你能把测试方法的代码test_required_fields显示一下吗?

标签: python django python-3.x django-rest-framework django-mptt


【解决方案1】:

首先,将序列化程序定义放置在视图中的事实使您的代码不太可读...您可以修改 parent_id 字段的查询集,同时覆盖视图中的 get_serializer 方法。

# serializers.py
from rest_framework import serializers

class NestedRestaurantDetailSerializer(serializers.ModelSerializer):
    # We don't care about filtering the queryset here, we override it in the view
    parent_id = serializers.RelatedField(queryset=Restaurant.objects.all(), allow_null=True, required=False)

    class Meta:
        model = Restaurants
        fields = ("id", "name", "parent_id")

# views.py
from .serializers import (
    RestaurantsTreeSerializer,
    NestedRestaurantDetailSerializer
)

class RestaurantsTreeView(generics.ListCreateAPIView):
    def get_serializer(self, *args, **kwargs):
        if self.request.method == 'GET':
            kwargs['context'] = self.get_serializer_context()
            return RestaurantsTreeSerializer(*args, **kwargs)
        else:
            kwargs['context'] = self.get_serializer_context()
            nested_serializer = NestedRestaurantDetailSerializer(*args, **kwargs)

            # Here we modify the queryset of the `parent_id` field
            nested_serializer.fields['parent_id'].queryset = self.request.user.restaurants_set.filter(status=Restaurants.VISIBLE)
        
            return nested_serializer

其次,关于您的异常,在documentation of django-mptt 中,您可以阅读以下内容:

order_insertion_by

在插入新树节点或重新设置现有节点时应定义排序的字段名称列表,最重要的排序字段名称在前。默认为 []。

假定任何标识为定义排序的字段在数据库中永远不会为 NULL。

重要的一行在上面。在您的模型定义中,您有:

name = models.CharField(max_length=255, blank=True, null=True)

您将一个可为空的字段定义为order_insertion_by 属性的参数。您的最后一个例外是 Cannot use None as a query value 的事实让我认为它是相关的......

未显示异常来源处的测试方法的代码,但我假设您尝试使用 test_required_fields 之类的名称向您的 enpoint 发送一个空的 name 参数。因此Cannot use None as a query value。由于您在模型中将name 字段定义为nullable,因此您的序列化程序也将其标记为nullable

【讨论】:

  • 你是对的关于可为空的 order_insertion_by 字段。修复它让我的错误飞起来
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-11-23
  • 2018-09-13
  • 1970-01-01
  • 2022-01-07
  • 2019-10-27
  • 2013-07-19
  • 2017-12-25
相关资源
最近更新 更多