0

I have 3 models: Maker, Item and MakerItem that creates the relation between the items and their makers:

class Maker(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)
    name = models.CharField(max_length=100)
class Item(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)
    name = models.CharField(max_length=100)
class MakerItem(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)
    item_id = models.ForeignKey(Item, on_delete=models.CASCADE)
    maker_id = models.ForeignKey(Maker, on_delete=models.CASCADE)

the items can have a random amount of makers.

I want to create both the Item and the MakerItem objects at the same time with a single set of data, for example if a Maker with id = "abcd" already exists, and I go to /item and send a POST request with the following data:

{
    "name": "item1",
    "makers": [
        {
            "maker_id": "abcd"
        }
    ]
}

I want the serializer to create the Item object and the MakerItem object. I have achieved this, with the following setup:

views.py

class ItemListCreate(ListCreateAPIView):
    queryset = Item.objects.all()
    serializer_class = ItemSerializer

serializers.py

class ItemSerializer(serializers.ModelSerializer):
    class MakerItemSerializer(serializers.ModelSerializer):
        class Meta:
            model = MakerItem
            exclude = ['id', 'item_id']

    makers = MakerItemSerializer(many=True)

    class Meta:
        model = Item
        fields = ['id', 'name', 'makers']

    def create(self, validated_data):
        maker_item_data = validated_data.pop('makers')
        item_instance = Item.objects.create(**validated_data)
        for each in maker_item_data:
            MakerItem.objects.create(
                item_id=check_instance,
                maker_id=each['maker_id']
            )
        return item_instance

but when Django tries to return the created object, it always gives me the error:

AttributeError at /item/

Got AttributeError when attempting to get a value for field `makers` on serializer `ItemSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Item` instance.
Original exception text was: 'Item' object has no attribute 'makers'.

What am I doing wrong?

Thanks

EDIT: To clarify, the objects get created and populate the database correctly, but when the browsable API that DRF provides tries to display the created object, it gives me the error above.

obvionaoe
  • 136
  • 2
  • 11
  • Try removing "makers" from the Meta fields – Swift Nov 23 '21 at 17:29
  • @Swift that will only throw another error ```AssertionError at /item/ The field 'makers' was declared on serializer ItemSerializer, but has not been included in the 'fields' option.``` – obvionaoe Nov 23 '21 at 18:32
  • The serialisers aren't supposed to be declared and nested in this way. Declare both serialisers on their own – Swift Nov 23 '21 at 18:39
  • This may be against best practices, but it has no effect whatsoever, it still throws the same error I pointed out in the question when separated. – obvionaoe Nov 23 '21 at 18:47
  • change field name to `makers_set` ? Just guessing at this point I'm afraid! – Swift Nov 23 '21 at 19:48

1 Answers1

0

Change:

class ItemSerializer(serializers.ModelSerializer):
    class MakerItemSerializer(serializers.ModelSerializer):
        class Meta:
            model = MakerItem
            exclude = ['id', 'item_id']

    makers = MakerItemSerializer(many=True)

To:

class ItemSerializer(serializers.ModelSerializer):
    class MakerItemSerializer(serializers.ModelSerializer):
        class Meta:
            model = MakerItem
            exclude = ['id', 'item_id']

    makers = MakerItemSerializer(many=True, source="makeritem_set")

Hope this works!

For clarity, you're attempting to serialise the reverse relationship between MakerItem and Item for this serialiser.

This means that the attribute on your object is automatically set by Django as fieldname_set but you can override this behaviour by setting the related_name kwarg on the field and then makemigrations and migrate it.

In your case you would need to do:

maker_id = models.ForeignKey(Maker, on_delete=models.CASCADE, related_name="maker_items")

And then update the field in the Meta to match the new field name, this way you don't have to manually specify source. Because actually the attribute "makers" is misleading, due to the fact its actually the MakerItem, not the Maker itself.

See https://docs.djangoproject.com/en/3.2/ref/models/relations/ for further details about this behaviour.

Swift
  • 1,663
  • 1
  • 10
  • 21