Jekson
Jekson

Reputation: 3262

DRF: Right way to use ListCreateAPIView with nested serializer

I make a page that lists all the existing vendors and modules that apply to each vendor. Here I need to change the status of modules (active or unactive), and if the module does not exist, but need to make it active then create it. It looks roughly like this.

Vendor1   module1/false  module2/true   module3/true .....
Vendor2   module1/false  module2/true   module3/true .....
.....
.....

models.py

class RfiParticipation(models.Model):
    vendor = models.ForeignKey('Vendors', models.DO_NOTHING, related_name='to_vendor')
    m = models.ForeignKey('Modules', models.DO_NOTHING, related_name='to_modules')
    active = models.BooleanField(default=False)
    user_id = models.IntegerField()
    rfi = models.ForeignKey('Rfis', models.DO_NOTHING, related_name='to_rfi', blank=True, null=True)
    timestamp = models.DateTimeField(auto_now=True)

To display it, I use ListCreateAPIView() class and nested serializer

serializer.py

class VendorModulesListManagementSerializer(serializers.ModelSerializer):
    to_vendor = RfiParticipationSerializer(many=True)

    class Meta:
        model = Vendors
        fields = ('vendorid', 'vendor_name', 'to_vendor',)
        read_only_fields = ('vendorid', 'vendor_name', )

    def create(self, validated_data):
        validated_data = validated_data.pop('to_vendor')
        for validated_data in validated_data:
            module, created = RfiParticipation.objects.update_or_create(
                rfi=validated_data.get('rfi', None),
                vendor=validated_data.get('vendor', None),
                m=validated_data.get('m', None),
                defaults={'active': validated_data.get('active', False)})
        return module


class RfiParticipationSerializer(serializers.ModelSerializer):

    class Meta:
        model = RfiParticipation
        fields = ('pk', 'active', 'm', 'rfi', 'vendor', 'timestamp')
        read_only_fields = ('timestamp', )

views.py

class AssociateModulesWithVendorView(generics.ListCreateAPIView):
    """
    RFI: List of vendors with participated modules and modules status
    """
    permission_classes = [permissions.AllowAny, ]
    serializer_class = VendorModulesListManagementSerializer
    queryset = Vendors.objects.all()

I have a question about using the create serializer method when sending a POST request.

Now the input format looks like this

{
    "to_vendor": [
            {
                "active": false,
                "m": 1,
                "rfi": "20R1",
                "vendor": 15

            }]
}

I.e. the dictionary key for the current code implementation is the list of one dictionary. If I remove " [] " from dict value I got

{
    "to_vendor": {
        "non_field_errors": [
            "Expected a list of items but got type \"dict\"."
        ]
    }
}

And this is the reason why I need to add a for loop in the create method to iterate through the list with just one element. I already have any doubts that I'm doing the right thing. Maybe I chose the wrong implementation way?

But now question is why do I get a mistake?

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

I would be very grateful for your help and advice!

upd

Get request format data:

[
    {
        "vendorid": 15,
        "vendor_name": "Forest Gamp",
        "to_vendor": [
            {
                "pk": 35,
                "active": true,
                "m": "Sourcing",
                "rfi": "1",
                "vendor": 15,
                "timestamp": "2020-03-29T08:15:41.638427"
            },
            {
                "pk": 39,
                "active": false,
                "m": "CLM",
                "rfi": "20R1",
                "vendor": 15,
                "timestamp": "2020-03-29T09:09:03.431111"
            }
        ]
    },
    {
        "vendorid": 16,
        "vendor_name": "Test21fd2",
        "to_vendor": [
            {
                "pk": 41,
                "active": false,
                "m": "SA",
                "rfi": "20R1",
                "vendor": 16,
                "timestamp": "2020-03-30T11:05:16.106412"
            },
            {
                "pk": 40,
                "active": false,
                "m": "CLM",
                "rfi": "20R1",
                "vendor": 16,
                "timestamp": "2020-03-30T10:40:52.799763"
            }
        ]
    }
]

Upvotes: 0

Views: 763

Answers (2)

Dhruv Agarwal
Dhruv Agarwal

Reputation: 558

I have gone through your implementation, and I suppose you might have used wrong generics class inheritance in views.py.

You should try to replace

class AssociateModulesWithVendorView(generics.ListCreateAPIView):
    permission_classes = [permissions.AllowAny, ]
    serializer_class = VendorModulesListManagementSerializer
    queryset = Vendors.objects.all()

With

class AssociateModulesWithVendorView(generics.CreateAPIView, generics.ListAPIView):
    permission_classes = [permissions.AllowAny, ]
    serializer_class = VendorModulesListManagementSerializer
    queryset = Vendors.objects.all()

You can check below links for reference: CreateAPIView : https://www.django-rest-framework.org/api-guide/generic-views/#createmodelmixin ListCreateAPIView : https://www.django-rest-framework.org/api-guide/generic-views/#listcreateapiview

Upvotes: 0

loicgasser
loicgasser

Reputation: 1543

It tries to access to_vendor on your model RfiParticipation and it complains that this property does not exist. related_name refers to the back relation on your Vendors model. So that you can do something like Vendors.to_vendor.all() and fetch all the RfiParticipation instances.

This happens when it tries to validate your input data, so even before it gets to the create function of your serializer.

If you are trying to create new RfiParticipation, why would you use a view that defines queryset = Vendors.objects.all()?

Instead define a view that takes care of creating RfiParticipation, since it seems that you already have a reference to Vendors. If I understand correctly, what you are trying to do is basically batch create RfiParticipation, so make a view that points to these.

Upvotes: 1

Related Questions