Gonzalo Dambra
Gonzalo Dambra

Reputation: 980

The proper way to set user ForeignKey value in DjangoRestFramework

I have this model:

class Comment(models.Model):

    user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=False)
    post = models.ForeignKey('posts.Post', on_delete=models.CASCADE, null=False)

    content = models.TextField(max_length=1000, null=False, blank=False)

    def __str__(self):
        """Return the comment str."""
        return "'{}'".format(self.content)

And its serializer looks like this:

class CommentSerializer(serializers.ModelSerializer):

    class Meta:
        model = Comment
        fields = '__all__'

So far so good when it comes to retrieve data, but when I want to create a new Comment, what would be the proper way to do so?

My view:

class CommentViewSet(viewsets.ModelViewSet):

    def get_permissions(self):
        permissions = []
        if self.action == 'create':
            permissions.append(IsAuthenticated)
        return [p() for p in permissions]

    def create(self, request):
        """Create a comment in some post."""

        serializer = CommentSerializer(data=request.data)
        if serializer.is_valid():  # False because user value is missing.
            serializer.save()
            return Response(serializer.data)
        else:
            return Response(serializer.errors)

That's ok, I tried by overriding validate_user and letting it just pass, but validation is still running when I call is_valid. Also I tried to append request.user to serializer.data, but it's immutable. How could I achieve this if I need to use request.user? The value that I need to set to this field of the serializer is not in request.data. (I don't think letting the front end send the user id would be a good idea).

Another solution I have in mind is by making user ReadOnly in the serializer, and then adding it in the Django object when I call to save(). But I want to know what is the cleaner, restful way to do so with DRF.

Upvotes: 1

Views: 61

Answers (3)

satyajit
satyajit

Reputation: 694

models.py :

class Comment(models.Model):

    user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=False)
    post = models.ForeignKey('posts.Post', on_delete=models.CASCADE, null=False)

    content = models.TextField(max_length=1000, null=False, blank=False)

    def __str__(self):
        """Return the comment str."""
        return "'{}'".format(self.content)
   
    def get_username(self):
        return self.user.get_username()

    def get_post_title(self):
        return self.post.title
    
    # and so on ...

serializers.py :

class CommentSerializer(serializers.ModelSerializer):
    username = serializers.SerializerMethodField()
    post_title = serializers.SerializerMethodField()
    class Meta:
        model = Comment
        fields = [
              "id", "username", "post_title", "content"
          ]
    def get_username(self, obj):
        return obj.get_username()

    def get_post_title(self, obj):
        return obj.get_post_title()

views.py :

from rest_framework import generics
from rest_framework.permissions import IsAuthenticated

class CommentViewSet(generics.CreateAPIView):
    model = Comment
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer
    permission_classes = [IsAuthenticated]

Upvotes: 1

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477684

You should alter the serializer to use the context and patch the user field:

class CommentSerializer(serializers.ModelSerializer):
    user = serializers.PrimaryKeyRelatedField(read_only=True)

    class Meta:
        model = Comment
        fields = '__all__'

    def create(self, validated_data):
        validated_data['user'] = self.context['request'].user
        return super().create(validated_data)

In the CommentViewSet, you do not override create, since the create function is more complex, and will provide the context to the serializer:

class CommentViewSet(viewsets.ModelViewSet):

    def get_permissions(self):
        if self.action == 'create':
            return [IsAuthenticated()]
        return []

    # no create

Upvotes: 1

JPG
JPG

Reputation: 88669

First, you have to set user as read_only field, which will turn off the validation.

class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = '__all__'
        read_only_fields = ('user',)

Then, override the perform_create(...) method of the viewset,

class CommentViewSet(viewsets.ModelViewSet):

    def get_permissions(self):
        permissions = []
        if self.action == 'create':
            permissions.append(IsAuthenticated)
        return [p() for p in permissions]

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

Note:
You should remove the create(...) method of the viewset since it is doing nothing "in your case"

Upvotes: 2

Related Questions