简体   繁体   中英

While trying to post mptt model via django rest framework getting ValueError: Cannot use None as a query value

Here is the view I'm trying to write tests for:

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

Here is the model I'm trying to create via POST request:

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

And finally my test:

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)

that returns that error:

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

I want to be able to create restaurant with null parent field. It can be post without parent field or with nulled parent field. Thanks django 1.11, drf 3.7, mptt 0.8.7

Firstly, the fact that you place your serializer definition inside the view make your code not quite readable... You can modify the queryset of your parent_id field while overriding the get_serializer method in your view.

# 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

Secondly, concerning your exception, in the documentation of django-mptt , you can read the following:

order_insertion_by

A list of field names which should define ordering when new tree nodes are being inserted or existing nodes are being reparented, with the most significant ordering field name first. Defaults to [].

It is assumed that any field identified as defining ordering will never be NULL in the database.

The important line is above. In your model definition, you have:

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

You define a nullable field as the parameter for the order_insertion_by attribute. The fact that your final exception is Cannot use None as a query value make me think it's linked...

The code of the test method at the origin of your exception is not shown, but I assume with a name like test_required_fields that you try to send an empty name parameter to your enpoint. Hence the Cannot use None as a query value . Since you defined your name field as nullable in your model, your serializer mark it at nullable too.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM