Search code examples
pythondjangounit-testingdjango-rest-frameworkdjango-testing

DRF testing - create object with a many-to-many relation


I have the following model Project:

class Project(models.Model):
    name = models.CharField(max_length=128)
    slug = models.SlugField(blank=True)
    assigned_to = models.ManyToManyField(
        User, blank=True, related_name="assignees")
    created_date = models.DateField(auto_now_add=True)
    updated_date = models.DateField(auto_now_add=False, auto_now=True)

If I need to create a new project, all I need to do is supply the name alone. This works for both the Admin dashboard and DRF APIVIEW. But when I try to test the functionality with DRF with an API call, I get the error: [b'{"assigned_to":["This field is required."]}'] Although the field is not required.

My test code below

import datetime
from marshmallow import pprint

from rest_framework.test import APITestCase, APIClient
from freezegun import freeze_time

from accounts.models import User
from .models import Project


@freeze_time("2021-11-14")
class ProjectTests(APITestCase):

    client = APIClient()
    project = None

    name = 'IOT on Blockchain'
    dead_line = datetime.date(2021, 11, 21)
    data = {
        'name': name,
        'dead_line': dead_line,
    }

    def create_user(self):
        username = 'test_user1'
        email = '[email protected]'
        password = '@1234xyz@'
        user_type = 'regular'
        data = {'username': username,
                'email': email,
                'password': password,
                'user_type': user_type,
                }
        return User.objects.create_user(**data)

    def create_project(self):
        project = Project.objects.create(**self.data)
        user = self.create_user()
        project.assigned_to.add(user)
        return project

    def test_create_project_without_api(self):
        """
        Ensure we can create a new project object.
        """
        self.project = self.create_project()
        self.assertEqual(Project.objects.count(), 1)
        self.assertEqual(self.project.name, 'IOT on Blockchain')
        self.assertEqual(self.project.dead_line,
                        datetime.date(2021, 11, 21))
        self.assertFalse(self.project.reached_deadline)
        self.assertEqual(self.project.days_to_deadline, 7)
        # runs successfully

    def test_create_project_with_api(self):
        """
        Ensure we can create a new project object with an
        API call.
        """
        url = 'http://127.0.0.1:8000/api/projects'
        project = self.client.post(url, self.data, format='json')
        # project.data.assigned_to.set(self.create_user())
        pprint(project.__dict__)
        self.assertEqual(Project.objects.count(), 1)
        self.assertEqual(self.project.name, 'IOT on Blockchain')
        self.assertEqual(self.project.slug, 'iot-on-blockchain')
        # does not run successfully (error mentioned in text body)

    def test_delete_project(self):
        """
        We can delete a user
        """
        self.project = self.create_project()
        self.project.delete()
        self.assertEqual(Project.objects.count(), 0)

Edit: Added serializer code

class ProjectWriteSerializer(serializers.ModelSerializer):
    """
    This serializer is used for CREATE, UPDATE operations on the Project model.
    """

    # We receive list of user ids (ids[int] <= 0) by which we assign
    # users to a project
    assigned_to = serializers.PrimaryKeyRelatedField(
        queryset=User.objects.all(), many=True)

    class Meta:
        model = Project
        fields = ('id', 'name', 'slug', 'assigned_to')

Any insights and help is very appreciated.


Solution

  • You have specified your own serializer field. As a result, it will no longer look at the blank=True part, and by default serializer fields are required. You can make these optional with:

    class ProjectWriteSerializer(serializers.ModelSerializer):
        assigned_to = serializers.PrimaryKeyRelatedField(
            queryset=User.objects.all(),
            many=True,
            required=True
        )
    
        class Meta:
            model = Project
            fields = ('id', 'name', 'slug', 'assigned_to')