简体   繁体   中英

Allow related field in post request in DRF

I have created model with many to many relationship and I have join table when I keep additional variable for it:

class BorderStatus(models.Model):
    STATUS_CHOICES = [("OP", "OPEN"), ("SEMI", "CAUTION"), ("CLOSED", "CLOSED")]
    origin_country = models.ForeignKey(OriginCountry, on_delete=models.CASCADE, default="0")
    destination = models.ForeignKey(Country, on_delete=models.CASCADE, default="0")
    status = models.CharField(max_length=6, choices=STATUS_CHOICES, default="CLOSED")
    extra = 1
    class Meta:
        unique_together = [("destination", "origin_country")]
        verbose_name_plural = "Border Statuses"

    def __str__(self):
        return (
            f"{self.origin_country.origin_country.name} -> {self.destination.name}"
            f" ({self.status})"
        )

Other models:

# Create your models here.
class Country(models.Model):
    name = models.CharField(max_length=100, unique=True, verbose_name='Country')

    class Meta:
        verbose_name_plural = "Countries"

    def __str__(self):
        return self.name

class OriginCountry(models.Model):
    origin_country = models.ForeignKey(
        Country, related_name="origins",  on_delete=models.CASCADE
    )
    destinations = models.ManyToManyField(
        Country, related_name="destinations", through="BorderStatus"
    )

    class Meta:
        verbose_name_plural = "Origin Countries"

    def __str__(self):
        return self.origin_country.name

Here is my serializer for the endpoint:

class BorderStatusEditorSerializer(serializers.ModelSerializer):
    """Create serializer for editing single connection based on origin and destination name- to change status"""
    origin_country = serializers.StringRelatedField(read_only=True)
    destination = serializers.StringRelatedField(read_only=True)

    class Meta:
        model = BorderStatus
        fields = ('origin_country', 'destination', 'status')

And my endpoint:

class BorderStatusViewSet(viewsets.ModelViewSet):
    queryset = BorderStatus.objects.all()
    serializer_class = BorderStatusEditorSerializer
    filter_backends = (DjangoFilterBackend,)
    filter_fields=('origin_country','destination')

The problem Im having is that I cant create any new combination for the BorderStatus model in this serializer via post request. If I remove the lines:

origin_country = serializers.StringRelatedField(read_only=True)
destination = serializers.StringRelatedField(read_only=True)

Then the form will work, but then I wont have the string representation of those variables, instead I get IDs.

Is there any way to allow request to accept origin_country and destination while being related fields?

EDIT:

To clarify how OriginCountry works, it is has a nested field:

 [{     "id": 1
        "origin_country": "Canada",
        "dest_country": [
            {
                "id": 1,
                "name": "France",
                "status": "CLOSED"
            },
            {
                "id": 2,
                "name": "Canada",
                "status": "OP"
            }
        ]
    },
]

You can try to override perform_create method of the viewset to make the necessary adjustments on-the-fly when new entry is posted:

class BorderStatusViewSet(viewsets.ModelViewSet):
    queryset = BorderStatus.objects.all()
    serializer_class = BorderStatusEditorSerializer
    filter_backends = (DjangoFilterBackend,)
    filter_fields=('origin_country','destination')

    def perform_create(self, serializer):
        origin_country, _ = models.Country.get_or_create(name=self.request.data.get('origin_country')
        destination, _ = models.Country.get_or_create(name=self.request.data.get('destination')
        return serializer.save(origin_country=origin_country, destination=destination)
            

Maybe you will also need to adjust your serializer to have:

class CountrySerializer(serializers.ModelSerializer):
    class Meta:
        model = Country
        fields = ['name']


class BorderStatusEditorSerializer(serializers.ModelSerializer):

    origin_country = CountrySerializer()
    destination = CountrySerializer()
    ...

Yes, I will try to give this combination.

You get this error because of Incorrect Type exception. Django checks data type validation on the serializer. For example here your dest_country returns a list of dicts but in your model it is a primary key (pk)

That's why on post django says: pk value expected, list received

But you can solve this error by using two different serializers (one to post another by default)

1. Create two different serializers

class BorderStatusEditorSerializer(serializers.ModelSerializer):
    """The First serialiser by default"""
    origin_country = serializers.StringRelatedField(read_only=True)
    destination = serializers.StringRelatedField(read_only=True)

    class Meta:
        model = BorderStatus
        fields = ('origin_country', 'destination', 'status')



class BorderStatusEditorCreateSerializer(serializers.ModelSerializer):
    """The Second serialiser for create"""

    class Meta:
        model = BorderStatus
        fields = ('origin_country', 'destination', 'status')

2.Add get_serializer_class method to for Viewset

class BorderStatusViewSet(viewsets.ModelViewSet):
    queryset = BorderStatus.objects.all()
    filter_backends = (DjangoFilterBackend,)
    filter_fields=('origin_country','destination')

    serializer_classes = {
        'create': BorderStatusEditorCreateSerializer, # serializer used on post
    }
    default_serializer_class = BorderStatusEditorSerializer # Your default serializer

    def get_serializer_class(self):
        return self.serializer_classes.get(self.action, self.default_serializer_class)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM