DRF Create new object and link to (but not create) it's related items (nested serializer)?

Viewed 168

I'm trying to link multiple items (subdomains) to an item being created (evidence).

My form submits okay - but I'm trying to figure out the 'best' way to go about this.

According to the docs I have to override the create method - but their example shows them 'creating' the related objects.

I don't want to do that. I want to just just add those related items to the piece of evidence I am creating (to create the relationship)

Here are my serializers:

class SubdomainSerializer(serializers.ModelSerializer):
    class Meta:
        model = Subdomain
        fields = [
            "id",
            "domain",
            "short_description",
            "long_description",
            "character_code",
        ]


class EvidenceSerializer(serializers.ModelSerializer):
    """
    """

    created_by = serializers.HiddenField(
        default=serializers.CurrentUserDefault()
    )
    updated_by = serializers.HiddenField(
        default=serializers.CurrentUserDefault()
    )
    absolute_url = serializers.SerializerMethodField()
    created_by_name = serializers.SerializerMethodField()
    updated_by_name = serializers.SerializerMethodField()
    subdomains = SubdomainSerializer(many=True)

    class Meta:
        model = Evidence
        fields = "__all__"
        extra_kwargs = {
            "subdomains": {
                "error_messages": {
                    "null": "This field is required.",
                    "blank": "This field is required.",
                }
            },
        }

    def get_absolute_url(self, obj):
        return obj.get_absolute_url()

    def get_created_by_name(self, obj):
        return obj.created_by.full_name

    def get_updated_by_name(self, obj):
        return obj.updated_by.full_name

    def create(self, validated_data):
        subdomains_data = validated_data.pop("subdomains")
        evidence = Evidence.objects.create(**validated_data)
        for subdomain in subdomains_data:
            # This is where I want to add the subdomains to the evidence - not create ones - how best to accomplish that?
            Subdomain.objects.create(**subdomain)
        return evidence

    def update(self, instance, validated_data):
        # Add the requestor as the updater in a PATCH request
        request = self.context["request"]
        validated_data["updated_by"] = request.user
        return super().update(instance, validated_data)

Is there a better way to setup my serializers? Should I be doing something different to make this more achievable?

Edited to add my models:

class Subdomain(CreateUpdateMixin):
    """

    """

    domain = models.ForeignKey(Domain, on_delete=models.PROTECT)
    short_description = models.CharField(max_length=100)
    long_description = models.CharField(max_length=250)
    character_code = models.CharField(max_length=5)
    proficiency_levels = models.ManyToManyField(SubdomainProficiencyLevel)

    class Meta:
        verbose_name = "Subdomain"
        verbose_name_plural = "Subdomains"

    def __str__(self):
        """Unicode representation of Subdomain."""
        return f"{self.character_code}"

class Evidence(CreateUpdateMixin, CreateUpdateUserMixin, SoftDeletionModel):
    """

    """

    subdomains = models.ManyToManyField(Subdomain, related_name="evidences")
    evaluation = models.ForeignKey(
        Evaluation, related_name="evidences", on_delete=models.PROTECT
    )
    published = models.BooleanField(default=False)
    comments = models.CharField(max_length=500)

    class Meta:
        ordering = ["-created_at"]
        verbose_name = "Evidence"
        verbose_name_plural = "Evidence"

    def __str__(self):
        """Unicode representation of Evidence."""
        return f"{self.subdomain} : {self.comments}"

    def get_absolute_url(self):
        """Return absolute url for Evidence."""
        return reverse("evidence-detail", args=[str(self.id)])

1 Answers

Here you want to set the subdomain ForeignKey to the new created evidence. For that, you just have to retrieve the subdomain and update his evidence field like this :

def create(self, validated_data):
    subdomains_data = validated_data.pop("subdomains")
    evidence = Evidence.objects.create(**validated_data)
    for subdomain in subdomains_data:
        # Retrieve the subdomain and update the evidence attribute
        sub_id = subdomain['id']
        sub = Subdomain.objects.get(id=sub_id)
        sub.evidence = evidence
        sub.save()
    return evidence

NB : I use the default inverse ForeignKey relationship : sub.evidence

Related