Description
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.