Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating and saving foreign key objects using a SlugRelatedField

Tags:

I've just started with Django REST framework and I'm having trouble with saving foreign keys. I have a Merchant model and a Phone model. The Phone has a foreign key to Merchant. When making a POST request to Merchant, I want to create Phone objects for the numbers provided in the request. But when I supply the phone numbers, it gives me the following error

Object with phone=0123456789 does not exist.

I just want it to create the Phone object itself. Here are the models that I am using:

class Merchant(models.Model):
    merchant_id       = models.CharField(max_length=255)
    name              = models.CharField(max_length=255)
    is_active         = models.BooleanField(default=True)

    class Meta:
        managed = True
        db_table = 'merchant'

    # Managers
    objects = models.Manager()
    active = managers.ActiveManager()

class Phone(models.Model):
    phone      = models.CharField(max_length=255)
    merchant   = models.ForeignKey('merchant.Merchant',
                                    related_name='phones',
                                    blank=True,
                                    null=True)

    class Meta:
        managed = True
        db_table = 'phone'

And here is the view and serializer that I am using them with

class MerchantSerializer(serializers.ModelSerializer):
    phones = serializers.SlugRelatedField(
        many=True,
        slug_field='phone',
        queryset=primitives.Phone.objects.all())

    class Meta:
        model = Merchant
        fields = (
            'merchant_id',
            'name',
            'is_active',
            'phones',
        )

class MerchantViewSet(viewsets.ModelViewSet):
    queryset = Merchant.active.all()
    serializer_class = MerchantSerializer

Here's what my request body looks like:

{
    "merchant_id": "emp011",
    "name": "Abhinav",
    "is_active": true,
    "phones": [
        "0123456789",
        "9876543210"
    ]
}

Here's the response:

400 Bad Request

{"phones":["Object with phone=0123456789 does not exist."]}
like image 447
Abhinav Desor Avatar asked Jan 18 '15 12:01

Abhinav Desor


People also ask

What is SlugRelatedField?

SlugRelatedField. SlugRelatedField may be used to represent the target of the relationship using a field on the target. For example, the following serializer: class AlbumSerializer(serializers. ModelSerializer): tracks = serializers.

What is Serializers in Django REST framework?

Serializers in Django REST Framework are responsible for converting objects into data types understandable by javascript and front-end frameworks. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.

What is nested serializer in Django?

DRF provides a Serializer class that gives you a powerful, generic way to control the output of your responses, as well as a ModelSerializer class that provides a useful shortcut for creating serializers that deal with model instances and querysets.


2 Answers

The SlugRelatedField provided by Django REST framework, like many of the related fields, is designed to be used with objects that already exist. Since you are looking to reference objects which already exist, or object which need to be created, you aren't going to be able to use it as-is.

You will need a custom SlugRelatedField that creates the new object when one doesn't exist.

class CreatableSlugRelatedField(serializers.SlugRelatedField):
    
    def to_internal_value(self, data):
        try:
            return self.get_queryset().get_or_create(**{self.slug_field: data})[0]
        except ObjectDoesNotExist:
            self.fail('does_not_exist', slug_name=self.slug_field, value=smart_text(data))
        except (TypeError, ValueError):
            self.fail('invalid')

class MerchantSerializer(serializers.ModelSerializer):
    phones = CreatableSlugRelatedField(
        many=True,
        slug_field='phone',
        queryset=primitives.Phone.objects.all()
    )

    class Meta:
        model = Merchant
        fields = (
            'merchant_id',
            'name',
            'is_active',
            'phones',
        )

By switching to get_or_create, the phone number object will be created if one doesn't already exist. You may need to tweak this if there are additional fields that have to be created on the model.

like image 63
Kevin Brown-Silva Avatar answered Sep 19 '22 16:09

Kevin Brown-Silva


Building on Kevin's answer, a little cleaner IMO due to not using get_or_create and [0]

class CreatableSlugRelatedField(serializers.SlugRelatedField):
    def to_internal_value(self, data):
        try:
            return self.get_queryset().get(**{self.slug_field: data})
        except ObjectDoesNotExist:
            return self.get_queryset().create(**{self.slug_field: data})  # to create the object
        except (TypeError, ValueError):
            self.fail('invalid')

Note the only required field in the related object should be the slug field.

like image 28
keyvanm Avatar answered Sep 18 '22 16:09

keyvanm