Home > Blockchain >  Problem with related objects in REST Framework
Problem with related objects in REST Framework

Time:03-22

I have a simple django application with the following models:

class Product(models.Model):
    __metaclass__ = ABCMeta

    title = models.CharField(max_length=50)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    slug = models.SlugField(max_length=100, unique=True)
    price = models.IntegerField()
    is_published = models.BooleanField(default=True)

    @abstractmethod
    def __str__(self):
        pass

    @abstractmethod
    def get_absolute_url(self):
        pass


class SupplyType(models.Model):
    title = models.CharField(max_length=10)
    slug = models.SlugField(max_length=100, unique=True)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('supply_type_detail', kwargs={'slug': self.slug})


class Processor(Product):
    supply_type = models.ForeignKey(SupplyType, on_delete=models.CASCADE)
    cores_amount = models.IntegerField()
    threads_amount = models.IntegerField()
    technological_process = models.IntegerField()

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('processor_detail', kwargs={'slug': self.slug})

The corresponding serializers were written for them:

class SupplyTypeSerializer(ModelSerializer):
    class Meta:
        model = SupplyType
        fields = '__all__'


class ProcessorSerializer(ModelSerializer):
    class Meta:
        model = Processor
        fields = '__all__'
        depth = 1

The corresponding views were also written (I will give only the views of creation for an example):

class ProcessorCreateAPIView(CreateAPIView):
    model = Processor
    serializer_class = ProcessorSerializer


class SupplyTypeCreateAPIView(CreateAPIView):
    model = SupplyType
    serializer_class = SupplyTypeSerializer

When I try to add "Supply Type" using POST request, it works successfully.

However, when I try to add a processor like this:

{
    "title": "Intel Pentium Gold G6400",
    "slug": "intel-pentium-gold-g6400",
    "price": 19690,
    "is_published" : true,
    "cores_amount": 2,
    "threads_amount": 4,
    "technological_process": 14,
    "supply_type": 1
}

I get an error:

django.db.utils.IntegrityError: null value in column "supply_type_id" of relation "store_processor" violates not-null constraint DETAIL: Failing row contains (1, 2, 4, 14, null).

Ultimately, there are the following questions: how to fix this and how, in this case, to add a processor with the desired supply type through the API (still through the id) or in some other way?

As a result, when I do a GET request, I would like to get something like this:

{
    "title": "Intel Pentium Gold G6400",
    "slug": "intel-pentium-gold-g6400",
    "price": 19690,
    "is_published" : true,
    "cores_amount": 2,
    "threads_amount": 4,
    "technological_process": 14,
    "supply_type": 
    {
        "id": 1,
        "title": "OEM",
        "slug": "oem"
    }

And yeah, sorry for my english.

CodePudding user response:

You need to use the concept of nested serializers. See below code

class ProcessorSerializer(ModelSerializer):
    supply_type = SupplyTypeSerializer()

    class Meta:
        model = Processor
        fields = '__all__'

As a result, when you do a GET request, you would get something like this:

{
    "title": "Intel Pentium Gold G6400",
    "slug": "intel-pentium-gold-g6400",
    "price": 19690,
    "is_published" : true,
    "cores_amount": 2,
    "threads_amount": 4,
    "technological_process": 14,
    "supply_type": {
        "id": 1,
        "title": "OEM",
        "slug": "oem"
    }
}

In order to create a Processor you will have to pass the supply_type dict object similar to what you get in the output. But since you want to pass the supply_type id instead, you could override the to_internal_value method as follows and set supply_type field as read_only:

def to_internal_value(self, data):
    supply_type_id = data.get('supply_type')
    internal_data = super().to_internal_value(data)
    try:
        supply_type = SupplyType.objects.get(id=supply_type_id)
    except SupplyType.DoesNotExist:
        raise serializers.ValidationError(
            {'supply_type': ['Item does not exist']},
        )
    internal_data['supply_type'] = supply_type
    return internal_data

Now you can create a Processor like this:

{
    "title": "Intel Pentium Gold G6400",
    "slug": "intel-pentium-gold-g6400",
    "price": 19690,
    "is_published" : true,
    "cores_amount": 2,
    "threads_amount": 4,
    "technological_process": 14,
    "supply_type": 1
}

The final code:

class ProcessorSerializer(serializers.ModelSerializer):
    supply_type = SupplyTypeSerializer(read_only=True)

    class Meta:
        model = Processor
        fields = '__all__'

    def to_internal_value(self, data):
        supply_type_id = data.get('supply_type')
        internal_data = super().to_internal_value(data)
        try:
            supply_type = SupplyType.objects.get(id=supply_type_id)
        except SupplyType.DoesNotExist:
            raise serializers.ValidationError(
                {'supply_type': ['Item does not exist']},
            )
        internal_data['supply_type'] = supply_type
        return internal_data
  • Related