0

Objective: Creating a new entry in database from Postman using the "POST".

I am trying to send the data from Postman and I am using nested serializing. I have changed the create method i have shared the snippet below. Also, I tried this solution but it did not work. Can someone please point out the mistake I am making?

When I am trying to post as form-data the error is {"magazine":["This field is required."]}. When I am trying to post it as raw data the error is Direct assignment to the forward side of a many-to-many set is prohibited. Use magazine.set() instead.

Here is my models:

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    author = models.ForeignKey('authors.Author', on_delete=models.CASCADE)
    magazine = models.ManyToManyField('articles.Magazine')

    def __str__(self):
        return self.title


class Magazine(models.Model):
    name = models.CharField(max_length=30)
    title = models.CharField(max_length=100)

    def __str__(self):
        return self.name

This is my serializers:

class MagazineSerializer(serializers.ModelSerializer):
    class Meta:
        model = Magazine
        fields = '__all__'

class ArticleSerializer(serializers.ModelSerializer):
    author = AuthorSerializer(read_only=True, many=False)
    magazine = MagazineSerializer(many=True)
    class Meta:
        model = Article
        fields = [
            'title',
            'content',
            'author',
            'magazine',
        ]

    def create(self, validated_data):
        allmags = []
        magazine = validated_data.pop('magazine')
        for i in magazine:
            if Magazine.objects.get(id=magazine["id"]).exists():
                mags = Magazine.objects.get(id=magazine["id"])
                allmags.append(mags)
            else:
                return Response({"Error":  "No such magazine exists"}, status=status.HTTP_400_BAD_REQUEST)
            
        validated_data['author'] = self.context['request'].user
        validated_data['magazine'] = allmags
        return Article.objects.create(**validated_data)

Here is my view:

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

class MagazineViewSet(viewsets.ModelViewSet):
    queryset = Magazine.objects.all()
    serializer_class = MagazineSerializer

    serializer_action_class = {
        'get_articles': MagazineSerializer,
    }

    @action(detail=True, url_path='articles', url_name='articles')
    def get_articles(self, request, pk=None):
        articles = Article.objects.filter(magazine=self.kwargs.get('pk'))
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data, status=200)

This is how I tried sending data as raw:

{
    "title": "New Post form Postman",
    "content": "Postman content new",
    "magazine": [
        {
            "id": 1,
            "name": "The Times",
            "title": "All News"
        }
    ]
}

This is how I posted as form-data:

aGhosh1
  • 3
  • 1
  • 2

1 Answers1

0

First, can you please clarify whether you wish to also create new Magazine objects from the nested serializer or do you want the user to only be able to create Article for existing magazines (it seems like the 2nd since you are calling .exists()) ?

1st case:

If you were going for the 1st case, where you wish to also create new magazines within the same POST request, I'd recommend using the drf-writeable-nested package.

2nd case:

For this as well, you should use the drf-writeable-nested package. Or, you can use this hack instead:

from rest_framework.exceptions import ValidationError
from rest_framework import serializers
from .models import Article, Magazine

class ArticleSerializer(serializers.ModelSerializer):
    author = AuthorSerializer(read_only=True, many=False)
    magazines = MagazineSerializer(many=True, read_only=True)
    # accept list of PKs
    magazines_ids = serializers.PrimaryKeyRelatedField(
        many=True, write_only=True, queryset=Magazine.objects.all()
    )
    class Meta:
        model = Article
        fields = [
            'title',
            'content',
            'author',
            'magazines',
            'magazines_ids',
        ]

    def create(self, validated_data):
        magazines = validated_data.pop("magazines_ids", None)
        validated_data["author"] = self.context["request"].user
        article = Article.objects.create(**validated_data)
        if magazines:
            article.magazine.set(magazines)

        return article 


Now your POST request JSON body should look like:

{
    "title": "New Post form Postman",
    "content": "Postman content new",
    "magazines_ids": [1]
}

Where magazines parameter takes list of primary keys.


Bonus

Also, just out of my curiosity, are you sure you want to use a ManytoManyField ? I would assume that an Article can only belong to a single Magazine for which you should be using ForeignKeysuch as:

magazine = models.ForeignKey("articles.Magazine", related_name="articles")

and then in your "action", you can make this change:

@action(detail=True, url_path='articles', url_name='articles')
def get_articles(self, request, pk=None):
    articles = self.get_object().articles
    serializer = ArticleSerializer(articles, many=True)
    return Response(serializer.data, status=200)
eshaan7
  • 968
  • 2
  • 9
  • 17
  • Yes, I am trying to achieve the 2nd case as you said, and I did try out doing it like this but I keep on getting the error `"Invalid data. Expected a dictionary, but got int.".` I also tried passing in the whole object but it does not work. – aGhosh1 May 01 '21 at 11:11
  • Also I tried out your solution and I get `AttributeError: 'Article' object has no attribute 'magazine_set'` – aGhosh1 May 01 '21 at 11:15
  • Actually, what I am doing is quite the opposite, I am trying to create articles and map it to multiple magazines i.e: An article can be published in many magazine, hence the ManytoMany field. – aGhosh1 May 01 '21 at 11:19
  • I just greatly improved my answer and made it short too. It should work now. – eshaan7 May 01 '21 at 11:20
  • Tried this out as well but i get `AssertionError: The field 'magazines_ids' was declared on serializer ArticleSerializer, but has not been included in the 'fields' option.` – aGhosh1 May 01 '21 at 11:26
  • The "'magazines_ids" is missing from the `ArticleSerializer.Meta.fields`. Added it and updated my answer. I am sorry about this, I don't have a python environment available at this exact moment or I would have tested the code myself. – eshaan7 May 01 '21 at 11:36
  • Please consider marking it "accepted" if it worked for you after the latest changes. :) – eshaan7 May 01 '21 at 11:38
  • I tried it addin it but it shows, `"magazines_ids : "This field is required."` – aGhosh1 May 01 '21 at 11:46
  • Please see my answer carefully. I have also written an example request body. – eshaan7 May 01 '21 at 11:50
  • 1
    Sorry about not passing the correct params, it works fine but now I can see the data does not show the nested serializing. I do not see any magazines details. – aGhosh1 May 01 '21 at 11:58
  • Sorry but I didn't understand. _When_ exactly you are not seeing the magazines details > – eshaan7 May 01 '21 at 12:05
  • Please change the field `magazines` to `magazine` and everything works fine now. As I am using `magazine` in my models I was getting some kind of error. – aGhosh1 May 01 '21 at 12:11