Jordan Akpoborie
Jordan Akpoborie

Reputation: 101

Many to Many field POST requests on API Django Rest Framework

So I have 3 models of interest:

models.py
class Author(models.Model): #Author of books
name = models.CharField(max_length = 100)

    @property    # This is code to get the books linked to an author 
    def books(self):
        book = Book.objects.filter(authors = self.id)
        return ', '.join(map(str,book)) #This makes the book list in this format "A, B, C"

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


class Genre(models.Model): #Genre of books
name = models.CharField(max_length = 50, unique = True)

    @property
    def books(self):
        book = Book.objects.filter(genre = self.id)
        return ', '.join(map(str,book))

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

class Book(models.Model):
    name = models.CharField(max_length = 150,)   #Name of books
    authors = models.ManyToManyField(Author,)     #Many to many because multiple books can have multiple authors
     genre = models.ManyToManyField(Genre)

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

Those are the models, and both genre and author have a many to many relation with books

serializers.py
class AuthorListSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Author
        fields = ('name',)

class GenreListSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Genre
        fields = ('name',)

class BookListSerializer(serializers.ModelSerializer):
    authors = AuthorListSerializer(many = True,) #To represent the relationship as a string instead of id
    genre = serializers.SlugRelatedField(many = True, queryset = models.Genre.objects.all(),slug_field = 'name')

    class Meta:
        model = models.Book
        fields = ('name','authors','rating', 'genre')

class BookDetailSerializer(serializers.ModelSerializer):
    publisher = serializers.SlugRelatedField(queryset = models.Publisher.objects.all(), slug_field= 'name') #To display publisher name instead of id

    authors = serializers.StringRelatedField(many = True,) #To represent the relationship as a string instead of id
    genre = serializers.StringRelatedField(many = True,)

    class Meta:
        model = models.Book
        fields = ('name', 'authors', 'rating','genre',
                  'publisher', 'total_qty', 'avail_qty',
                  'pub_date','isbn','price',)

So in my Django-Rest-Framework Browsable api, Normal ForeignKey fields have their options displayed to create a new object instance. For example The publishers in the "BookDetailSerializer" have all the publishers displayed as options for a POST or PUT or PATCH request. but for the many to many fields that include Genre and Author it is not only blank, but it appears that I cannot put any input whatsoever. I have tried using the DRF-writable-nested third party package but nothing changed:

    class BookListSerializer(WritableNestedModelSerializer):
        authors = AuthorListSerializer(many = True,) #To represent the relationship as a string instead of id
        genre = serializers.SlugRelatedField(many = True,
                                             queryset = models.Genre.objects.all(),slug_field = 'name')

My question is how can I be able to make POST request with my list of Authors and Genres available through the DRF browsable api? Thanks in advance!!

This image shows the options available for making a POST request involving publishers

This image shows that you can't add any input for POST request involving both Genre and Authors as many-to many relations.

Update: So I have added a create method and it still doesn't work, as shown below:

class BookListSerializer(serializers.ModelSerializer):
authors = AuthorListSerializer(many = True,) #To represent the relationship as a string instead of id
genre = serializers.SlugRelatedField(many = True,
                                             queryset = models.Genre.objects.all(),slug_field = 'name')


class Meta:
    model = models.Book
    fields = ('name','authors','rating', 'genre')


def create(self,validated_data):
    authors_data = models.Author.objects.all()
    book = Book.objects.create(authors = authors_data,**validated_data,)
    return book

What can I do?

Upvotes: 2

Views: 2961

Answers (1)

Jordan Akpoborie
Jordan Akpoborie

Reputation: 101

After struggling for 4 days I figured out the answer When handling many to many relationships and you want to want the code to be as follows.

 class BookListSerializer(serializers.ModelSerializer):

class Meta:
    model = models.Book
    fields = ('name','authors','rating', 'genre')
    depth = 1

Adding the depth allows the nested relationships to fully serialize. Because the relationship is nested, and is a many to many relationship you would have to create your own create function in the views.py as follows:

 class BookViewSet(GetSerializerClassMixin, viewsets.ModelViewSet):

queryset = models.Book.objects.all()
serializer_class = serializers.BookDetailSerializer
serializer_action_classes = {'list': serializers.BookListSerializer}
permission_classes = [IsAdminOrReadOnly, permissions.IsAuthenticated,] 

def create(self,request, *args, **kwargs):
    data = request.data

    new_book = models.Book.objects.create(name = data["name"], publisher = models.Publisher.objects.get(id = data["publisher"]),
                                        pub_date = data["pub_date"],
                                        price = data["price"],
                                        isbn = data['isbn'],)
    new_book.save()
    
    for author in data['authors']:
        author_obj = models.Author.objects.get(name = author['name'])
        new_book.authors.add(author_obj)

    for gen in data['genre']:
        gen_obj = models.Genre.objects.get(name = gen['name'])
        new_book.genre.add(gen_obj)

    serializer = serializers.BookListSerializer(new_book)
    
    return Response(serializer.data)

For the many to many relationships you have to create them after saving the object and add them to the object manually. There is a foriegn Key relationship that's present there "Publishers" For that relationship you have to manually point to its location in the database hence the code below.

 name = data["name"], publisher = models.Publisher.objects.get(id = data["publisher"]),

For the Detail book serializer in the question its the same thing with the BookListSerializer

That's how I was able to handle POST requests in the Many-to-Many relations

Upvotes: 4

Related Questions