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.