90

I have the following code working perfectly. I can create a Post object from DRF panel by selecting an image and a user. However I want DRF to populate the user field by the currently logged in user.

models.py

class Post(TimeStamped):
    user = models.ForeignKey(User)
    photo = models.ImageField(upload_to='upload/')
    hidden = models.BooleanField(default=False)
    upvotes = models.PositiveIntegerField(default=0)
    downvotes = models.PositiveIntegerField(default=0)
    comments = models.PositiveIntegerField(default=0)

serializers.py

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ['id', 'user', 'photo']

views.py

class PhotoListAPIView(generics.ListCreateAPIView):
    queryset = Post.objects.filter(hidden=False)
    serializer_class = PostSerializer
    authentication_classes = (SessionAuthentication, BasicAuthentication)
    permission_classes = (IsAuthenticated,)

How can I do this?

ekad
  • 14,436
  • 26
  • 44
  • 46
MiniGunnR
  • 5,590
  • 8
  • 42
  • 66

10 Answers10

114

Off the top of my head, you can just override the perform_create() method:

class PhotoListAPIView(generics.ListCreateAPIView):
    ...
    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

Give that a shot and let me know if it works

DaveBensonPhillips
  • 3,134
  • 1
  • 20
  • 32
  • 3
    Hi. What if I use `def validate_user(self, value): return self.context['request'].user` on `serializers.py` file. That works too. Is there any internal difference between these two? – MiniGunnR Feb 20 '16 at 13:51
  • @MiniGunnR That's fine too, assuming you want the user to be part of the serialization. My method will work regardless. – DaveBensonPhillips Feb 20 '16 at 20:22
  • 15
    If the field 'user' is required, you will never reach that method - perform_create yet because the raise exception in create method. That is my case. – Dat TT Apr 16 '18 at 09:08
  • 1
    When you want to disable the field in the browsable api set it as read_only https://www.django-rest-framework.org/api-guide/serializers/#specifying-read-only-fields – Harry Moreno Apr 28 '19 at 16:56
  • 1
    To anyone reading this answer in 2019: you should also pass kwargs, so you can do it like this: ``` def perform_create(self, serializer, **kwargs): kwargs['user'] = self.request.user serializer.save(**kwargs) ``` – DataGreed Dec 17 '19 at 21:06
  • @DataGreed Is there a reason to pass `kwargs` in 2019? I fail to see how this is necessary when the `create` method doesn't provide `kwargs` by default.(source: https://github.com/encode/django-rest-framework/blob/master/rest_framework/mixins.py) – Bash Mar 03 '20 at 00:43
  • @DaveBensonPhillips how do I fetch when I have "Extended user" like Profile class which is connected as OneToOne with User model – Kenan Mar 14 '20 at 14:43
75

You can use CurrentUserDefault:

user = serializers.PrimaryKeyRelatedField(
    read_only=True, 
    default=serializers.CurrentUserDefault()
)
phoenix
  • 7,988
  • 6
  • 39
  • 45
Marc Gibbons
  • 1,190
  • 10
  • 5
45

It depends on your use case. If you want it to be "write-only", meaning DRF automatically populates the field on write and doesn't return the User on read, the most straight-forward implementation according to the docs would be with a HiddenField:

class PhotoListAPIView(generics.ListCreateAPIView):
    user = serializers.HiddenField(
        default=serializers.CurrentUserDefault(),
    )

If you want want it to be readable, you could use a PrimaryKeyRelatedField while being careful that your serializer pre-populates the field on write - otherwise a user could set the user field pointing to some other random User.

class PhotoListAPIView(generics.ListCreateAPIView):
    user = serializers.PrimaryKeyRelatedField(
        # set it to read_only as we're handling the writing part ourselves
        read_only=True,
    )

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

Finally, note that if you're using the more verbose APIView instead of generics.ListCreateAPIView, you have to overwrite create instead of perform_create like so:

class PhotoListAPIView(generics.ListCreateAPIView):
    user = serializers.PrimaryKeyRelatedField(
        read_only=True,
    )

    def create(self, validated_data):
        # add the current User to the validated_data dict and call
        # the super method which basically only creates a model
        # instance with that data
        validated_data['user'] = self.request.user
        return super(PhotoListAPIView, self).create(validated_data)
phoenix
  • 7,988
  • 6
  • 39
  • 45
olieidel
  • 1,505
  • 10
  • 10
  • 1
    I was able to use validated_data['created_by_id'] = self.context['request'].user in my serializer – Little Brain Jan 29 '19 at 20:35
  • 2
    This is the most well rounded answer for sure. Thanks olieidel – Derek Adair May 20 '19 at 18:21
  • 1
    Is there a reason that for the readable version we have to override `perform_create` whereas we can use `default=serializers.CurrentUserDefault()` with the HiddenField? – finngu Oct 28 '19 at 16:02
19

You can avoid passing the user in your request and you won't see it in the output but DRF will populate it automatically:

from rest_framework import serializers

class MyModelSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())

    class Meta:
        model = models.MyModel
        fields = (
            'user',
            'other',
            'fields',
        )
Babken Vardanyan
  • 14,090
  • 13
  • 68
  • 87
15

As of DRF version 3.8.0 (Pull Request discussion), you can override save() in serializer.

from rest_framework import serializers
...

class PostSerializer(serializers.ModelSerializer):
    user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())

    class Meta:
        model = Post
        fields = ['id', 'user', 'photo']

    def save(self, **kwargs):
        """Include default for read_only `user` field"""
        kwargs["user"] = self.fields["user"].get_default()
        return super().save(**kwargs)
phoenix
  • 7,988
  • 6
  • 39
  • 45
user3555315
  • 161
  • 1
  • 3
6

@DaveBensonPhillips's answer might work in your particular case for some time, but it is not very generic since it breaks OOP inheritance chain.

ListCreateAPIView inherits from CreateModelMixin which saves the serializer already. You should always strive to get the full chain of overridden methods executed unless you have a very good reason not to. This way your code stays DRY and robust against changes:

class PhotoListAPIView(generics.ListCreateAPIView):
    ...
    def perform_create(self, serializer):
        serializer.validated_data['user'] = self.request.user
        return super(PhotoListAPIView, self).perform_create(serializer)
knaperek
  • 2,113
  • 24
  • 39
4

You will have to override the default behavior of how generics.ListCreateAPIView creates an object.

class PhotoListAPIView(generics.ListCreateAPIView):
    queryset = Post.objects.filter(hidden=False)
    authentication_classes = (SessionAuthentication, BasicAuthentication)
    permission_classes = (IsAuthenticated,)

    def get_serializer_class(self):
        if self.request.method == 'POST':
            return CreatePostSerializer
        else:
            return ListPostSerializer

    def create(self, request, *args, **kwargs):
        # Copy parsed content from HTTP request
        data = request.data.copy()

        # Add id of currently logged user
        data['user'] = request.user.id

        # Default behavior but pass our modified data instead
        serializer = self.get_serializer(data=data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

The .get_serializer_class() is not necessary as you can specify which fields are read-only from your serializer, but based on the projects I have worked on, I usually end up with 'asymmetric' serializers, i.e. different serializers depending on the intended operation.

Wannabe Coder
  • 1,457
  • 12
  • 21
1

Try this:

def post(self, request, format=None)

        serializer = ProjectSerializer(data=request.data)
        request.data['user'] = request.user.id


        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
Community
  • 1
  • 1
1

This is what works for me in serializers.py, where I am also using nested data. I want to display created_by_username without having to lookup other users.

class ListSerializer(serializers.ModelSerializer):
    """
    A list may be created with items
    """
    items = ItemSerializer(many=True)

    # automatically set created_by_id as the current user's id
    created_by_id = serializers.PrimaryKeyRelatedField(
        read_only=True,
    )

    created_by_username = serializers.PrimaryKeyRelatedField(
        read_only=True
    )


    class Meta:
        model = List
        fields = ('id', 'name', 'description', 'is_public',
            'slug', 'created_by_id', 'created_by_username', 'created_at',
            'modified_by', 'modified_at', 'items')

    def create(self, validated_data):
        items_data = validated_data.pop('items', None)
        validated_data['created_by_id'] = self.context['request'].user
        validated_data['created_by_username'] = self.context['request'].user.username
        newlist = List.objects.create(**validated_data)

        for item_data in items_data:
            Item.objects.create(list=newlist, **item_data)
        return newlist
Little Brain
  • 2,647
  • 1
  • 30
  • 54
0

I wrote an extension to DRF's serializer below

from rest_framework import serializers

class AuditorBaseSerializer(serializers.Serializer):
    created_by = serializers.StringRelatedField(default=serializers.CurrentUserDefault(), read_only=True)
    updated_by = serializers.StringRelatedField(default=serializers.CurrentUserDefault(), read_only=True)

    def save(self, **kwargs):
        # if creating record.
        if self.instance is None:
            kwargs["created_by"] = self.fields["created_by"].get_default()

        kwargs["updated_by"] = self.fields["updated_by"].get_default()
        return super().save(**kwargs)

and it can be used as follows

class YourSerializer(serializers.ModelSerializer, AuditorBaseSerializer):
    class Meta:
        model = SelfEmployedBusiness
        fields = (
            'created_by',
            'updated_by',
        )
XPLOT1ON
  • 2,994
  • 2
  • 20
  • 36