0

Let's say we have three Models : ModelA, related to ModelB and ModelC.

ModelA is defined as :

class ModelA(models.Model):

    field_b = models.ForeignKey(ModelB, on_delete=models.CASCADE)
    field_c = models.ForeignKey(ModelC, on_delete=models.CASCADE)
    other_field = models.CharField(max_length=30)

    class Meta:
        constraints = [
                       models.UniqueConstraint(fields=['field_b', 'field_c'], name='unique_modelA')
                        ]
  • How to generate a ModelASerializer instance from instances of ModelB and ModelC ?
  • Then, will there be a serialized representation of field_b and field_c into ModelASerializer ?
  • The UniqueConstraint will it be checked when calling .is_valid() onto ModelASerializer instance ?

I tried the following :

class ModelASerializer(serializers.ModelSerializer):
        field_b = ModelBSerializer(read_only=True)
        field_c = ModelCSerializer(read_only=True)
        other_field = serializers.CharField(required=False)

        class Meta:
            model = ModelA
            fields = ('field_b', 'field_c', 'other_field',)
            validators = [
                        UniqueTogetherValidator(
                            queryset=ModelA.objects.all(),
                            fields=['field_b', 'field_c'],
                            message='A Model A instance for this couple of ModelB and ModelC instances already exists.'
                        )
                    ]

    def create(self, validated_data):
        """
        Create and return a new `ModelA` instance, given the validated data.
        """
        return ModelA.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Update and return an existing `ModelA` instance, given the validated data.
        """
        instance.other_field= validated_data.get('other_field', instance.other_field)
        instance.save()
        return instance

But I cannot find any way to create the serializer :

model_b = ModelB()
model_c = ModelC()
model_b.save()
model_c.save()
other_field = "Dummy content"

First try

model_a_serializer = ModelASerializer(model_b, model_c, other_field)
  • The serializer is looking for an ID field and can't find it
  • Anyway, no data field being provided, we can't call .is_valid() onto the serializer, and thus, can't check the integrity constraint

Second try

model_b_serializer = ModelBSerializer(model_b)
model_c_serializer = ModelCSerializer(model_c)
data = {'model_b':model_b_serializer , 'model_c':model_c_serializer , 'other_field':other_field}

model_a_serializer = ModelASerializer(data=data)
if model_a_serializer.is_valid():
    model_a_serializer.save()

Here, the serializer tries to recreate the ModelB and ModelC instances when is_valid() is called... And I don't want that.

Any ideas? Thank you very much by advance.

  • you should read the documentation [serializer relations] (https://www.django-rest-framework.org/api-guide/relations/) – Ishan Bassi Aug 27 '21 at 15:11
  • Thanks, I already read it but couldn't find any solution to my problem. Which I think should not be so exceptionnal... Maybe I am missing something – William Afonso Aug 29 '21 at 19:32

1 Answers1

0

Not sure if I understood your question properly but...

  • How to generate a ModelASerializer instance from instances of ModelB and ModelC ?

If you want to generate a ModelA instance from ModelB and ModelCthen you should remove read_only=True argument and you have to explictly specify the relationship in create and update method.

class ModelBSerializer(serializers.ModelSerializer):
    modelA_set = ModelASerializer() # this field name can be changed by adding related_name attribute in ModelA in models.py
    class Meta:
        model = ModelB
        fields = ('modelA_set' , 'other_fields')
    
    def create(self,validate_data):
        modelA_data = validate_data.pop('modelA_data') # whatever field name you will use to send data for modelA
        b = ModelB.objects.create(**validate_data)
        for data in modelA_data:
            modelA_instance  = ModelA.objects.create(field_b=b ,**data)
        return b

modelA_set is an automatically generated field name when we are trying to serialize ModelA from ModelB serializer . It can be changed by passing related_name='some_name' argument like this:

field_b = models.ForeignKey(ModelB, on_delete=models.CASCADE , related_name="field_a")

  • Then, will there be a serialized representation of field_b and field_c into ModelASerializer ?

Yes there will be.

Again correct me if I misunderstood your question.
Ishan Bassi
  • 490
  • 1
  • 5
  • 13
  • Thanks for your answer. To be clearer, I will try to be more concrete. I have 3 models : Account (model B), Community (model C) and JoinRequest (model A). I try to make a JoinRequestSerializer. Since a JoinRequest binds an already existing Account with an already existing Community, they should not be recreated when the is_valid() method of the JoinRequestSerializer is called. I don't understand why it is the ModelBSerializer and ModelCSerializer that should be defined in function of ModelA. Shouldn't it be the opposite : ModelASerializer in function of ModelB and modelC? – William Afonso Aug 29 '21 at 18:07
  • So I want to create the JoinRequest from its serializer. This serializer itself should be created upon the account and community instances related to this JoinRequest. The thing is that whenever I pass the account and community data into the data dict field of the JoinRequestSerializer, the is_valid() method tries to re-instanciate the account and community instances. And if I don't validate these data, there won't be any trace of them into the joinrequest_serializer.data ... – William Afonso Aug 29 '21 at 19:36
  • 1
    In your **first try** , you should pass `pk` of the modelB and modelC. `model_a_serializer = ModelASerializer(data = {'model_b':model_b.pk, 'model_c':model_c.pk , 'other_field':other_field})` – Ishan Bassi Aug 30 '21 at 09:15
  • Thank you very much. I finally found a thread talking about this subject. I implemented the answer provided by JPG, which works for me. And indeed, it is based upon models pk, as you said. The thread in question : https://stackoverflow.com/questions/29950956/drf-simple-foreign-key-assignment-with-nested-serializers – William Afonso Aug 30 '21 at 17:41