Jackson H
Jackson H

Reputation: 87

Writable nested serializers with inherited django models

I'm struggling to update records with the writeable nested serializers I've created.

There are many Listing categories for a classifieds app I'm creating that each have a few unique attributes, but they also share many attributes. I have a handful of django models that inherit from a parent Listing model, and one of these models, Battery, contains some nested data. So far I've been able to create Battery records but keep getting AttributeErrors when I try to update them.

I've tried to include only the relevant code. Here are my views:

# views.py

class ListingCreateView(CreateAPIView):
    queryset = Listing.objects.all()
    def get_serializer_class(self):
        category = self.request.data['category']
        if category == 1:
            return PercussionSerializer
         elif category == 6:
            return BatterySerializer

        return ListingSerializer

class ListingUpdateView(UpdateAPIView):
    queryset = Listing.objects.all()
    def get_serializer_class(self):
        category = self.request.data['category']
        if category == 1:
            return PercussionSerializer
        elif category == 6:
            return BatterySerializer

        return ListingSerializer

here are my models:

# models.py

## Parent model
class Listing(models.Model):
    title = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    price = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True, default=0.00)

## One of the child models
class Battery(Listing):
    model_name = models.TextField(blank=True, null=True, default="")
    color = models.ForeignKey(Color, on_delete=models.CASCADE, blank=True, null=True)
    manufacture_year = models.IntegerField(null=True)

## Model for the nested data in Battery model
class Drum(models.Model):
    drum_type = models.CharField(max_length=50, blank=True)
    size = models.TextField(blank=True, null=True, default="")
    battery = models.ForeignKey(Battery, related_name='drums', on_delete=models.CASCADE, null=True)

and here are my serializers:

# serializers.py

class ListingSerializer(serializers.ModelSerializer):
    class Meta:
        model = Listing
        fields = '__all__'

class DrumSerializer(serializers.ModelSerializer):
    class Meta:
        model = Drum
        fields = ['drum_type', 'size', 'carrier', 'stand', 'cover', 'case', 'sold']

class BatterySerializer(serializers.ModelSerializer):
    drums = DrumSerializer(many=True)
    class Meta:
        model = Battery
        fields = ['id', 'title', 'description', 'price', 'model_name', 'color', 'manufacture_year', 'drums']

    def create(self, validated_data):
        drum_data = validated_data.pop('drums')
        battery = Battery.objects.create(**validated_data)
        for drum_data in drum_data:
            Drum.objects.create(battery=battery, **drum_data)
        return battery

    def update(self, instance, validated_data):
        # Update Listing field values
        instance.title = validated_data.get('title', instance.title)
        instance.description = validated_data.get('description', instance.description)
        instance.price = validated_data.get('price', instance.price)
        
        # Grab the Battery record for this Listing and update its values
        instance_battery = Battery.objects.get(pk=instance.pk)
        instance_battery.model_name = validated_data.get('model_name', instance_battery.model_name)
        instance_battery.color = validated_data.get('color', instance_battery.color)
        instance_battery.manufacture_year = validated_data.get('manufacture_year', instance_battery.manufacture_year)

        # Check for a list of drums
        drum_data = validated_data.pop('drums')
        # If it exists
        if drum_data:
            # Clear the existing drums
            instance_battery.drums.clear()
            # Create new drums
            Drum.objects.bulk_create(
                [
                    Drum(**drum)
                    for drum in drum_data
                ],
            )

        # Save the updated Listing & Battery
        instance.save()
        instance_battery.save()
        # Return the updated Battery
        return instance

I feel like I've followed the DRF documentation about writeable nested serializers correctly, but I continue to get this AttributeError when I try to post an update to a Battery record:

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

Judging by the error message I think the Django model inheritance requires a more specific solution that what the DRF documentation provides. Can anybody help me understand how to create the serializers I need to create/update a Battery record that inherits from a Listing model with a nested list of Drum records?

Thanks!

Upvotes: 1

Views: 840

Answers (1)

synodexine
synodexine

Reputation: 66

I think the problem appears because the view waits for a Listing instance. I can suggest the following: try to redefine def get_qyeryset(self)

For example:

class ListingUpdateView(UpdateAPIView):
    def get_queryset(self):
        if self.request.data['category'] == 6:
            return Battery.objects.all()
        else:
            return Listing.objects.all()

    def get_serializer_class(self):
        category = self.request.data['category']
        if category == 1:
            return PercussionSerializer
        elif category == 6:
            return BatterySerializer

        return ListingSerializer

Maybe it is not the best way, but it can solve your problem

Upvotes: 1

Related Questions