MohsenTamiz MohsenTamiz - 6 months ago 30x
Python Question

ManyToMany relation custom serializer

Assume I have two models:

class IPAddress(models.Model):
address = models.CharField()

class Rule(models.Model):
name = models.CharField()
ips = models.ManyToMany(IPAddress)

I want to able to add a rule by a request as below:

"ips":["", "", ""]

Also I want to construct ip in each request (there is no url to construct an ip directly) for new rule, so I have written a class for manager like this:

class RuleManager(models.Manager):
def create(self, validated_data):
rule = Rule(name=validate_data['name'])

rule.ips = [IPAddress.objects.get_or_create(item.lower()) for item in validated_data['ips']]

But in serializer I could not find a proper way to show this I have written a serializer like this:

class RuleSerializer(serializers.Serializer):
name = serializers.CharField()
ips = serializers.SlugRelatedField(many=True, slug_field='address', validators=[], queryset=models.IPAddress.objects.all())

But the problem is it validates the ip in request and if there is not such ip it returns an error, although I set the validators an empty list.

I have two questions, how can I disable this validation? And is my way for writing the serializer and model is appropriate for my scenario(I could not change the request I get and the response I must send)


If you need to return an instance of Rule in following format:

"ips":["", "", ""]

You could create a RuleSerializer and use SlugRelatedField.

SlugRelatedField only works with the objects which already exist. Since you would be creating objects as well, you could modify the to_internal_value implementation to create the objects which doesn't exist (referenced from here):

class CreatableSlugRelatedField(serializers.SlugRelatedField):

    def to_internal_value(self, data):
            return self.get_queryset().get_or_create(**{self.slug_field: data})[0]
        except ObjectDoesNotExist:
  'does_not_exist', slug_name=self.slug_field, value=smart_text(data))
        except (TypeError, ValueError):

class RuleSerializer(serializers.ModelSerializer):

    ips = serializers.CreatableSlugRelatedField(
        slug_field='address' # this is the ip address

    class Meta:
         model = Rule
         fields: ('name', 'ips')

UPDATE: Based on the comments in the question:

I could not change the request I get and the response I must send

But if you could then use nested serializers though your representation would need to change slightly:

    "name": "Foo",
    "ips": [
         {"address": ""}, 
         {"address": ""}, 
         {"address": ""}

and, then the nested serializers (more documentation here):

class IPAddressSerializer(serializers.ModelSerializer):

     class Meta:
         model = IPAddress
         fields: ('address',)

class RuleSerializer(serializers.ModelSerializer):

    ips = IPAddressSerializer(many=True)

    class Meta:
         model = Rule
         fields: ('name', 'ips')