Как установить текущее пользовательское поле в Django Rest Framework?

У меня следующий код работает отлично. Я могу создать объект Post из панели DRF, выбрав изображение и пользователя. Однако я хочу, чтобы DRF заполнял пользовательское поле текущим пользователем.

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,)

Как я могу это сделать?

Ответ 1

Сверху моей головы вы можете просто переопределить метод perform_create():

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

Дайте мне выстрел и дайте мне знать, если он работает

Ответ 3

Это зависит от вашего варианта использования. Если вы хотите, чтобы это было "только для записи", то есть DRF автоматически заполняет поле при записи и не возвращает при чтении, самая простая реализация в соответствии с документами будет с HiddenField:

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

Если вы хотите, чтобы он был читабельным, вы можете использовать PrimaryKeyRelatedField, следя за тем, чтобы ваш сериализатор предварительно заполнял поле при записи - в противном случае пользователь может установить поле 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)

Наконец, обратите внимание, что если вы используете более подробный APIView вместо generics.ListCreateAPIView, вы должны перезаписать create вместо perform_create следующим образом:

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)

Ответ 4

Ответ @DaveBensonPhillips может работать в вашем конкретном случае в течение некоторого времени, но он не очень общий, так как он нарушает цепочку наследования ООП.

ListCreateAPIView наследует от CreateModelMixin который уже сохраняет сериализатор. Вы всегда должны стремиться к тому, чтобы полная цепочка переопределенных методов выполнялась, если у вас нет очень веской причины. Таким образом, ваш код остается сухим и надежным в отношении изменений:

class PhotoListAPIView(generics.ListCreateAPIView):
    ...
    def perform_create(self, serializer):
        serializer.validated_data['user'] = self.request.user
        return super(PhotoListAPIView, self).perform_create(serializer)

Ответ 5

Вам придется переопределить поведение по умолчанию того, как generics.ListCreateAPIView создает объект.

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)

Не .get_serializer_class(), так как вы можете указать, какие поля доступны только для чтения из вашего сериализатора, но на основе проектов, над которыми я работал, обычно я получаю "асимметричные" сериализаторы, то есть разные сериализаторы в зависимости от предполагаемой операции,

Ответ 6

Вы можете избежать передачи user в своем запросе и не увидите его в выводе, но DRF заполнит его автоматически:

from rest_framework import serializers

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

    class Meta:
        model = models.MyModel
        fields = (
            'user',
            'other',
            'fields',
        )

Ответ 7

Попробуй это:

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

Ответ 8

Это то, что работает для меня в serializers.py, где я также использую вложенные данные. Я хочу отобразить созданное_пользовательское имя без необходимости поиска других пользователей.

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

    # automatically set created_by_id as the current user 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

Ответ 9

Начиная с версии 3.8.0 DRF (обсуждение запроса на извлечение) вы можете переопределить функцию save() в сериализаторе.

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)