8

My models.py looks like this:

class IP(models.Model):
    name = models.CharField(max_length=30, unique=True)
    address = models.CharField(max_length=50, unique=True)

class IPGroup(models.Model):
    name = models.CharField(max_length=50, unique=True)
    ips = models.ManyToManyField('IP', through='IPGroupToIP')

class IPGroupToIP(BaseModel):
    ip_group = models.ForeignKey('IPGroup', on_delete=models.CASCADE)
    ip = models.ForeignKey('IP', on_delete=models.CASCADE)

Now, in order to create an IPGroup, I have the following serializer:

class IPGroupCreateSerializer(serializers.ModelSerializer):
    ips = serializers.ListField()

class Meta:
    model = IPGroup
    fields = ['name', 'ips']

@transaction.atomic()
def create(self, validated_data):
    ips_data = validated_data.pop('ips', None)
    ip_group = IPGroup.objects.create(name=validated_data['name'])

    if ips_data:
        for ip in ips_data:
            ip_obj, created = IP.objects.get_or_create(name=ip['name'], address=ip['address'])
            IPGroupToIP.objects.create(ip_group_id=ip_group.id, ip_id=ip_obj.id)
    return ip_group

My views are a simple class based view as follows:

class IPGroupCreateView(generics.CreateAPIView):
    queryset = IPGroup.objects.get_queryset()
    serializer_class = IPGroupCreateSerializer

My JSON payload looks like this:

{ "ips": [{"name":"example.com", "address":"10.1.1.9"}], "name": "ipgroup1" }

This how ever gives me an error stating:

TypeError at /api/v1/ip-group/
'ManyRelatedManager' object is not iterable

The strange thing is that when i check the DB, the IPGroup is created along with the M2M ips. So, the code is working as expected but the view is somehow returning a 500 server error instead of a 201 created. What am i doing wrong ?

Amistad
  • 7,100
  • 13
  • 48
  • 75

3 Answers3

21

Due to some complications, ListField() will become handy only while writing to the DB ( I'm not sure why this behavior).

In your context, adding write_only=True in ListField solve the exception. Hence the IPGroupCreateSerializer will be as

class IPGroupCreateSerializer(serializers.ModelSerializer):
    ips = serializers.ListField(write_only=True)



I personally reccomend to use Nested Serializers to handle this situation. So,define a new IPSerializer class and use it instead of serializers.ListField()

class IPSerializer(serializers.ModelSerializer):
    class Meta:
        model = IP
        fields = ('name', 'address')


class IPGroupCreateSerializer(serializers.ModelSerializer):
    ips = IPSerializer(many=True)


class Meta:
    model = IPGroup
    fields = ['name', 'ips']
JPG
  • 82,442
  • 19
  • 127
  • 206
  • yes..i know that it works with nested serializers..However, i want it to work with a Listfield..would you know what am i doing wrong ? – Amistad Sep 26 '18 at 09:02
  • @Amistad Updated the answer, Hope It' will solve the problem – JPG Sep 26 '18 at 14:41
  • 2
    That worked !! ..just to understand, how did you figure this out ?..I went through the entire documentation and did not see this being mentioned anywhere.. – Amistad Sep 26 '18 at 18:49
  • Should be written in the docs, that could lead a long debugging session – Heroe__ May 13 '22 at 17:38
0

I had a serializer that contains a list field(named communities) and this list field's child parameter was PrimaryKeyRelatedField.

What I did to tackle this problem is as below.

  • I defined a method in the model of instance called get_communities.
  • pop the list field in the to_representation method on serializer.
  • call super.
  • update returned value with update method because it is a dictionary.
  • and return the updated ret.

def to_representation(self, instance):
    if isinstance(instance, OrderedDict):
        return super(SerializerClassName, self).to_representation(instance)
    self.fields.pop('communities')
    ret = super(SerializerClassName, self).to_representation(instance)
    ret.update({'communities': instance.get_communities()})
    return ret
Oğuzhan
  • 91
  • 2
  • 9
0

This can be bypassed locally for a specific field, problem in a 'to_representation'

class MyListFileField(serializers.ListField):

    def to_representation(self, data):
        return [
            self.child.to_representation(item.your_field) for item in data.all()
        ]


class MySerializer(serializers.ModelSerializer):

    field= MyListFileField(child=serializers.FileField())
ilia
  • 1
  • 1