0

I'm struggling to make my API work, the tutorials are quite tricky about this part. I want to have a '/comments/' POST request with body {movie_id: 1, content="Some comment") and connect it to some Movie.

In serializer I'm getting: {'movie': [ErrorDetail(string='This field is required.', code='required')]}

How can I map movie_id to movie? By the way, I can change the name to movie if this would be easier.

Models.py:

from django.db import models
from django.utils import timezone


class Movie(models.Model):
    title = models.CharField(max_length=200)
    year = models.IntegerField()


class Comment(models.Model):
    content = models.TextField(max_length=300)
    publish_date = models.DateField(default=timezone.now())
    movie = models.ForeignKey(Movie, on_delete=models.CASCADE, related_name='movie_id')

serializers.py:

class MovieSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Movie
        fields = '__all__'


class CommentSerializer(serializers.HyperlinkedModelSerializer):
    movie_id = serializers.PrimaryKeyRelatedField(many=False, read_only=True)

    class Meta:
        model = Comment
        fields = '__all__'

views.py (for Comment, Movie works fine):

from .models import Movie, Comment
from rest_framework import viewsets, status
from rest_framework.response import Response
from .serializers import MovieSerializer, CommentSerializer

class CommentViewSet(viewsets.ModelViewSet):
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer

    def create(self, request, *args, **kwargs):
        serializer = CommentSerializer(data=request.data, context={'request': request})

        if serializer.is_valid(raise_exception=True): 
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)  
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
MKaras
  • 665
  • 7
  • 17
  • So have you tried sending the POST request with `{movie: 1, content="Some comment")`? – Kent Shikama Mar 15 '19 at 07:42
  • Yes, I've just tried changing all "movie_id" in whole project to "movie" and applied migrations. Now I'm getting: `django.db.utils.IntegrityError: NOT NULL constraint failed: movies_comment.movie_id` – MKaras Mar 15 '19 at 07:54
  • I've also tried changing read_only to False in: `movie = serializers.PrimaryKeyRelatedField(many=False, read_only=False)` Because this makes sense, but now I'm getting: `AssertionError: Relational field must provide a 'queryset' argument, override 'get_queryset', or set read_only='True'.` – MKaras Mar 15 '19 at 07:56
  • 1
    I suggest reading up on https://stackoverflow.com/q/2642613/2750819 as the name you give to related_name doesn't really make sense. – Kent Shikama Mar 15 '19 at 08:16
  • As @kshikama said, the `related_name` you chose makes no sense at all. Logically it should be called `comments`. When you have an object Movie, you can do `Movie.comments.all()` and retrieve all comments for a Movie. That would be very intutive for the reader. – cezar Mar 15 '19 at 08:24

2 Answers2

0

I think you can try like this:

class CommentSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Comment
        fields = '__all__'

Also, related name is used for reverse relationship. So it will work like this:

If Comment Model has related_name comments like this:

class Comment(models.Model):
    movie = models.ForeignKey(Movie, on_delete=models.CASCADE, related_name='comments')

Then you can access comments from movie like this:

for m in Movie.objects.all():
    m.comments.all()
cezar
  • 11,616
  • 6
  • 48
  • 84
ruddra
  • 50,746
  • 7
  • 78
  • 101
0

Nested data works a little differently to how I expected.

If you want to connect a comment to a movie, you need to pass the movie object to your comment, not the primary key of the movie object.

Under the hood, Django automatically creates a new field 'movie_id' on your comment object in which the movie's primary key is stored - but you don't need to worry about that. So I would call the field in the comment 'movie', otherwise Django will create a new field 'movie_id_id'.

I got something similar to work by defining a custom create method in my serializer:

In your serializer:

class CommentSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Comment
        fields = '__all__'


    def create(self, validated_data):
        themovieid = validated_data.pop('movie_id', None) # remove movie_id from the comment data
        themovie = Movie.objects.get(pk=themovieid) # find the movie object

        return Comment.objects.create(movie=themovie, **validated_data)

I have tried to adapt this to your code, I hope it will help you to get this working. I have removed movie_id from your serializer: your model defiines everything that is needed.

Edit: have you tried simply passing the movie's id as 'movie' in your comment data, with no custom create method and do not define 'movie_id' in your serializer?

Little Brain
  • 2,647
  • 1
  • 30
  • 54