Skip to content

Unexpected different behavior between exposed API and Unit Test APIClient request #9702

@jlandercy

Description

@jlandercy

My use case is the following: I have Items that have Properties. There is a Many 2 Many relationship between them. I want to be able to create Items with Properties that are created on the fly. I mean if property exists it is fetched, if it does not it is created. I can do so using the following pattern:

# models.py

class Property(models.Model):
    name = models.CharField(max_length=64, unique=True)


class Item(models.Model):
    name = models.CharField(max_length=64, unique=True)
    properties = models.ManyToManyField(Property, through="ItemProperty")


class ItemProperty(models.Model):
    property = models.ForeignKey(Property, on_delete=models.CASCADE)
    item = models.ForeignKey(Item, on_delete=models.CASCADE)

class PropertySerializer(serializers.Serializer):
    name = serializers.CharField()

# serializer.py

class ItemSerializer(serializers.ModelSerializer):

    properties = PropertySerializer(many=True, required=False)

    class Meta:
        model = models.Item
        fields = "__all__"

    def create(self, validated_data):
        data = copy.deepcopy(validated_data)
        properties = data.pop("properties", [])
        item = models.Item.objects.create(**data)
        for property in properties:
            current_property, _ = models.Property.objects.get_or_create(**property)
            item.properties.add(current_property)
        return item

# views.py

class PropertyViewSet(viewsets.ModelViewSet):
    queryset = models.Property.objects.all()
    serializer_class = serializers.PropertySerializer
    permissions_classes = [permissions.IsAuthenticated]


class ItemViewSet(viewsets.ModelViewSet):
    queryset = models.Item.objects.all()
    serializer_class = serializers.ItemSerializer
    permissions_classes = [permissions.IsAuthenticated]

router.register('property', PropertyViewSet, 'property')
router.register('item', ItemViewSet, 'item')

With this setup, when I perform a POST request with the following payload on the API directly (using Swagger):

{
        "name": "test",
        "properties": [{"name": "test"}, {"name": "dummy"}]
}

I get a 201 with the following body:

{
  "id": 3,
  "properties": [
    {
      "name": "test"
    },
    {
      "name": "dummy"
    }
  ],
  "name": "test"
}

Which is the expected result (property are created on the fly). Now if I want to automatize this check using Unit Test, I'll do something like this:

class TestAPIItemsAndProperties(APITestCase):

    fixtures = ["core/fixtures/users.yaml"]

    payload = {
        "name": "test",
        "properties": [{"name": "test"}, {"name": "dummy"}],
    }

    def setUp(self):
        self.user = models.CustomUser.objects.get(username="jlandercy")
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)

    def test_complete_payload_is_sent(self):
        response = self.client.post("/api/core/item/", data=self.payload)
        print(response)
        print(response.json())

And then I get a 201 where properties are totally ignored (seems like they are popped):

{'id': 1, 'properties': [], 'name': 'test'}

I wonder if I am doing something wrong in automatizing test or if it is potentially a bug.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions