Home > OS >  Django Rest Framework - Passing 'user' from ModelViewSet to Serializer
Django Rest Framework - Passing 'user' from ModelViewSet to Serializer

Time:11-05

I've been stumped by this for a couple of hours now, and have read through a bunch of documentation and different tutorials, but I still do not know what I'm doing wrong.

I'm trying to make a POST request to create a Comment (my model, views, etc. defined below) and make an association with the User that is making the POST request, but I keep getting the error {"user":["This field is required."]}

I thought that adding

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

to my viewset would make this easy, but that doesn't appear to be working...

My models.py looks like:

import uuid
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import get_user_model
from django.db import models

User = get_user_model()


class Comment(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(User, on_delete=models.DO_NOTHING)
    text = models.TextField(blank=False, null=False)
    created = models.DateTimeField(auto_now_add=True)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.CharField(max_length=256)
    content_object = GenericForeignKey('content_type', 'object_id')

serializer.py

from rest_framework import serializers
from .models import Comment
from organization.serializers import UserSerializer
from django.contrib.auth import get_user_model

User = get_user_model()


class CommentSerializer(serializers.ModelSerializer):
    id = serializers.CharField(read_only=True, required=False)
    user = UserSerializer(required=True)
    text = serializers.CharField(required=True)
    created = serializers.DateTimeField(read_only=True)
    object_id = serializers.CharField(required=False)

    class Meta:
        model = Comment
        fields = ('id', 'user', 'text', 'created', 'object_id')
        read_only_fields = ('id', 'created')

views.py

from rest_framework import viewsets
from rest_framework import permissions
from .models import Comment
from .serializers import CommentSerializer


class CommentViewSet(viewsets.ModelViewSet):

    queryset = Comment.objects.all()
    serializer_class = CommentSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        return self.queryset.filter(object_id=self.kwargs['object_id'])

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

urls.py

from django.urls import include, path
from rest_framework import routers
from .views import CommentViewSet


router = routers.DefaultRouter()
router.register(r'(?P<content_type>[\w\-] )/(?P<object_id>[\w\-] )', CommentViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

CodePudding user response:

Ok - I got this working by overriding the create method of CommentViewSet and simplifying my CommentSerializer

serializer.py

class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = ('id', 'user', 'text', 'created', 'edited', 'object_id', 'content_type')
        read_only_fields = ('id', 'created')

views.py

from rest_framework import viewsets
from rest_framework import permissions
from rest_framework import status
from rest_framework.response import Response
from django.contrib.contenttypes.models import ContentType
from .models import Comment
from .serializers import CommentSerializer


class CommentViewSet(viewsets.ModelViewSet):

    queryset = Comment.objects.all()
    serializer_class = CommentSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        return self.queryset.filter(object_id=self.kwargs['object_id'])

    def create(self, request, **kwargs):
        """
        Overrides the standard create method so that we can set User,
        object_id and content_type based on requesting user and kwargs
        passed in URL
        """

        comment_data = self.request.data
        # change request data so that it's mutable, otherwise this will raise
        # a "This QueryDict instance is immutable." error
        comment_data._mutable = True
        # set the requesting user ID for the User ForeignKey
        comment_data['user'] = self.request.user.id
        comment_data['object_id'] = self.kwargs['object_id']

        # pass kwarg from URL to `model` to get the corresponding object
        content_type = ContentType.objects.get( model=self.kwargs['content_type'] )
        # pass the ID from the ContentType object to `content_type`, expected
        # by serializer
        comment_data['content_type'] = content_type.id

        serializer = CommentSerializer(data=comment_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)

CodePudding user response:

Serializer validation works before perform_create, change user field to read only and it works as expected.

class CommentSerializer(serializers.ModelSerializer):
     id = serializers.CharField(read_only=True, required=False)
     user = UserSerializer(read_only=True)
     text = serializers.CharField(required=True)
     created = serializers.DateTimeField(read_only=True)
     object_id = serializers.CharField(required=False)

     class Meta:
        model = Comment
        fields = ('id', 'user', 'text', 'created', 'object_id')
        read_only_fields = ('id', 'created')
  • Related