Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Instead of Primary Key Send Different Field in Django REST Framework

serializers.py

class MovieSerializer(serializers.ModelSerializer):

    class Meta:
        model = Movie
        fields = [
            'popularity',
            'director',
            'genre',
            'imdb_score',
            'name',
        ]  

views.py

class MovieList(generics.ListCreateAPIView):
    queryset = Movie.objects.all().order_by('-id')[:10]
    serializer_class = MovieSerializer
    permission_classes = (IsAuthenticated,)

    def list(self, request):
        queryset = self.get_queryset()
        serializer = MovieSerializer(queryset, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        data = request.data
        if isinstance(data, list):
            serializer = self.get_serializer(data=request.data, many=True)
        else:
            serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

I send my data like this and it works correctly.

{
   "popularity": 83.0,
   "director": "Victor Fleming",
   "genre": [
      1,
      2,
      3,
      4
   ],
   "imdb_score": 8.3,
   "name": "The Wizard of Oz"
}

But I want to send the data somewhat like this:

{
   "popularity": 83.0,
   "director": "Victor Fleming",
   "genre": [
      "Adventure",
      "Family",
      "Fantasy",
      "Musical"
   ],
   "imdb_score": 8.3,
   "name": "The Wizard of Oz"
}

See the Genre List - Instead of Primary Key I am sending name.

These are my Models:

class Genre(models.Model):
    name = models.CharField(max_length=30, unique=True)  # make unique

    def __str__(self):
        return self.name

class Movie(models.Model):
    popularity = models.FloatField(max_length=10)
    director = models.CharField(max_length=30)
    genre = models.ManyToManyField(Genre)
    imdb_score = models.FloatField(max_length=10)
    name = models.CharField(max_length=30)

Even when I am listing data or getting the data it should send genre name instead of id. I tried to use StringRelatedField, but it gave me error.

NotImplementedError at /api/movie/ StringRelatedField.to_internal_value() must be implemented.

like image 894
Cipher Avatar asked Mar 14 '19 11:03

Cipher


People also ask

How do you save a foreign key field in django REST framework?

Your code is using serializer only for validation, but it is possible use it to insert or update new objects on database calling serializer. save() . To save foreign keys using django-rest-framework you must put a related field on serializer to deal with it. Use PrimaryKeyRelatedField .

What is the difference between ModelSerializer and HyperlinkedModelSerializer?

The HyperlinkedModelSerializer class is similar to the ModelSerializer class except that it uses hyperlinks to represent relationships, rather than primary keys. By default the serializer will include a url field instead of a primary key field.

What is write only field django REST framework?

From DRF v3 onwards, setting a field as read-only or write-only can use serializer field core arguments mentioned as follows. write_only. Set this to True to ensure that the field may be used when updating or creating an instance, but is not included when serializing the representation.

What are Mixins in django REST?

mixins are classes that generally inherit from object (unless you are django core developer) mixins are narrow in scope as in they have single responsibility. They do one thing and do it really well. mixins provide plug-in functionality.


1 Answers

Assuming that you used StringRelatedField in your MovieSerializer like this:

class MovieSerializer(serializers.ModelSerializer):
    genre = serializers.StringRelatedField(many=True)

    class Meta:
        model = Movie
        fields = [
            'popularity',
            'director',
            'genre',
            'imdb_score',
            'name',
        ]

the result would look like this when retrieving a list of movies:

[
    {
        "popularity": 83.0,
        "director": "Victor Fleming",
        "genre": [
             "Adventure",
             "Family",
             "Fantasy",
             "Musical"
        ],
        "imdb_score": 8.3,
        "name": "The Wizard of Oz"
    }
]

But if you want to create a new movie, then it won't work because StringRelatedField is read-only.

You can however create your custom related field.

This is the complete serializers.py:

from rest_framework import serializers

from .models import Genre, Movie


class GenreRelatedField(serializers.RelatedField):
    def display_value(self, instance):
        return instance

    def to_representation(self, value):
        return str(value)

    def to_internal_value(self, data):
        return Genre.objects.get(name=data)


class MovieSerializer(serializers.ModelSerializer):
    genre = GenreRelatedField(
        queryset=Genre.objects.all(),
        many=True
    )

    class Meta:
        model = Movie
        fields = (
            'popularity',  
            'director',     
            'genre',                         
            'imdb_score',
            'name',
        )   

This is a simple example that can be highly customized in many ways.

The method display_value defines how the object Genre is displayed, for example in the form. Here it just returns the object Genre i.e. the output of __str__.

The method to_representation defines how the object Genre is displayed in the output (JSON or XML). It's very similar to the previous method, but here we explicitly have to convert Genre to string. Certainly you can create a more complex output according to your requirements.

The method to_internal_value solves your actual problem by getting an object Genre for the given value. If you have a more complex method to_representation here you would need expanded logics to get the appropriate object.

Using this approach you can post a JSON in your desired form, specifying the genre names instead of their ids.

I hope this example helps other people too.

like image 56
cezar Avatar answered Sep 18 '22 13:09

cezar