Search code examples
djangodjango-rest-frameworkpytestpytest-django

Django Rest Framework Not Null Error when creating from payload


I'm trying to write a test to check that the create method of my Viewset works ok:

@pytest.mark.django_db
def test_create(admin_user):
    parent = factories.ParentFactory()
    payload = {
        'name': 'child',
        'parent_id': parent.pk,
    }
    view = views.ParentViewSet.as_view({'post': 'create'})
    request = factory.post('/', payload, format='json')
    force_authenticate(request, user=admin_user)
    response = view(request)
    assert response.status_code == status.HTTP_201_CREATED
    assert models.Child.objects.count() == 1
    child = models.Child.objects.first()
    assert child.name == 'child'

However, I get the following error when I run the code:

psycopg2.errors.NotNullViolation: null value in column "parent_id" violates not-null constraint

But the test for the Parent create method runs fine:

def test_create(admin_user):
    payload = {
        'name': 'parent',
        'children': [
            {
                'name': 'child',
            }
        ],
    }
    view = views.ParentViewSet.as_view({'post': 'create'})
    request = factory.post('/', payload, format='json')
    force_authenticate(request, user=admin_user)
    response = view(request)
    assert response.status_code == status.HTTP_201_CREATED
    assert models.Parent.objects.count() == 1
    season = models.Parent.objects.first()
    assert season.name == 'parent'
    assert season.children.count() == 1

Can someone tell me the correct way to write the payload for the child test create?

Edit:

serializers.py

class ChildSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.Child
        fields = ['id', 'name']


class ParentSerializer(serializers.ModelSerializer):
    children = ChildSerializer(many=True)

    class Meta:
        model = models.Parent
        fields = ['id', 'name', 'children']

    def create(self, validated_data):
        children = validated_data.pop('children')
        parent = super().create(validated_data)
        for child in children: 
            child['parent'] = parent
        self.fields['children'].create(children)
        return parent

views.py

class ParentViewSet(
    viewsets.GenericViewSet,
    mixins.CreateModelMixin,
    mixins.ListModelMixin,
    mixins.RetrieveModelMixin,
    mixins.DestroyModelMixin,
):
    queryset = models.Parent.objects.all().prefetch_related(
        'children'
    )
    serializer_class = serializers.ParentSerializer


class ChildViewSet(
    viewsets.GenericViewSet,
    mixins.CreateModelMixin,
    mixins.ListModelMixin,
    mixins.RetrieveModelMixin,
    mixins.DestroyModelMixin,
):
    queryset = models.Child.objects.all().prefetch_related(
        'children'
    )
    serializer_class = serializers.ChildSerializer

Solution

  • I think you have a field "parent" in the child model, but as you are not including that field in ChildSerializer, even if you send it in the request body, it is not included while creating a Child instance, hence the error you're getting. You have several ways to solve it

    1 - You can add parent field to ChildSerializer:

    class ChildSerializer(serializers.ModelSerializer):
    
        class Meta:
            model = models.Child
            fields = ['id', 'name', 'parent']
    

    and send the request like this:

    payload = {
        'name': 'child',
        'parent': parent.pk,
    }
    

    2 - If you do not want that, you may keep the ChildSerializer as it is now, and add parent only while saving the child instance, with something like this:

    class ChildViewSet(
        ...
    ):
        queryset = models.Child.objects.all().prefetch_related('children')
        serializer_class = serializers.ChildSerializer
    
        def perform_create(self, serializer):
            serializer.save(parent_id=self.request.data.get('parent'))
    

    Again you'd need to send the request like this:

    payload = {
        'name': 'child',
        'parent': parent.pk,
    }
    

    But in this second scenario, you're not including parent field in request validation, and if a client does not send parent field or sends an invalid value there, you'll get an exception. If you are to go with this solution, I'd advise to somehow validate parent field as well.