Search code examples
python-3.xdjangodjango-modelsdjango-rest-frameworkdjango-serializer

How do I get my Django serializer to save my foreign key objects?


I'm using Python 3.8 and Django 3. I have the following models. Notice the second has foreign keys to the first ...

class ContactMethod(models.Model):
    class ContactTypes(models.TextChoices):
        EMAIL = 'EMAIL', _('Email')
        PHONE = 'PHONE', _('Phone')

    type = models.CharField(
        null=False,
        max_length=5,
        choices=ContactTypes.choices,
    )
    phone = PhoneNumberField(null=True)
    email = models.EmailField(null=True)

    class Meta:
        unique_together = ('phone', 'email',)


class Coop(models.Model):
    objects = CoopManager()
    name = models.CharField(max_length=250, null=False)
    types = models.ManyToManyField(CoopType, blank=False)
    addresses = models.ManyToManyField(Address)
    enabled = models.BooleanField(default=True, null=False)
    phone = models.ForeignKey(ContactMethod, on_delete=models.CASCADE, null=True, related_name='contact_phone')
    email = models.ForeignKey(ContactMethod, on_delete=models.CASCADE, null=True, related_name='contact_email')
    web_site = models.TextField()

Using the Django rest framework, I have crated the following serializers to help save data ...

class ContactMethodPhoneSerializer(serializers.ModelSerializer):
    class Meta:
        model = ContactMethod
        fields = ['type', 'phone']
        read_only_fields = ['type']
        extra_kwargs = {'type': {'default': 'PHONE'}}


class CoopSerializer(serializers.ModelSerializer):
    types = CoopTypeSerializer(many=True, allow_empty=False)
    addresses = AddressTypeField(many=True)
    phone = ContactMethodPhoneSerializer()
    email = ContactMethodEmailSerializer()

    class Meta:
        model = Coop
        fields = '__all__'

    def to_representation(self, instance):
        rep = super().to_representation(instance)
        rep['types'] = CoopTypeSerializer(instance.types.all(), many=True).data
        rep['addresses'] = AddressSerializer(instance.addresses.all(), many=True).data
        return rep

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

        coop_types = validated_data.pop('types', {})
        phone = validated_data.pop('phone', {})
        email = validated_data.pop('email', {})
        instance = super().create(validated_data)
        for item in coop_types:
            coop_type, _ = CoopType.objects.get_or_create(name=item['name'])
            instance.types.add(coop_type)
        instance.phone = ContactMethod.objects.create(type=ContactMethod.ContactTypes.PHONE, **phone)
        instance.email = ContactMethod.objects.create(type=ContactMethod.ContactTypes.EMAIL, **email)
        return instance

However, in unit testing (and actual), when I attempt to save data like so

    serializer_data = {
        "name": name,
        "types": [
            {"name": coop_type_name}
        ],
        "addresses": [{
            "formatted": street,
            "locality": {
                "name": city,
                "postal_code": postal_code,
                "state": state.id
            }
        }],
        "enabled": enabled,
        "phone": {
          "phone": phone
        },
        "email": {
          "email": email
        },
        "web_site": web_site
    }

    serializer = CoopSerializer(data=serializer_data)
    assert serializer.is_valid(), serializer.errors
    coop_saved = serializer.save()
    coop = Coop.objects.get(pk=coop_saved.id)
    ...
    assert coop.phone.phone == phone

The foreign key fields (email and phone) are not saving (they are null). All the other fields save properly. What else am I missing in order to save my foreign key fields successfully?


Solution

  • Because you did not call save in the end:

    def create(self, validated_data):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """
    
        coop_types = validated_data.pop('types', {})
        phone = validated_data.pop('phone', {})
        email = validated_data.pop('email', {})
        instance = super().create(validated_data)
        for item in coop_types:
            coop_type, _ = CoopType.objects.get_or_create(name=item['name'])
            instance.types.add(coop_type)
        instance.phone = ContactMethod.objects.create(type=ContactMethod.ContactTypes.PHONE, **phone)
        instance.email = ContactMethod.objects.create(type=ContactMethod.ContactTypes.EMAIL, **email)
        # call save here
        instance.save()
        return instance