Как передать 'user_id' в CustomerSerializer?

tldr; Я не знаю, как передать user_id из requests.user.id и использовать его в CustomerSerializer для сохранения объекта в базу данных. Ошибка связана с тем. факт user_id существует в таблице customer, в базе данных, но он не отображается как поле для передачи во фронтенде rest_framework api (только phone и profile_image).

Вот модель клиента:

class Customer(models.Model):
    phone = models.CharField(max_length=14)
    profile_image = models.ImageField(blank=True, null=True)
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

Здесь ViewSet:

class CustomerViewSet(ModelViewSet):
    queryset = Customer.objects.all()
    permission_classes = [permissions.IsAdminUser]
    serializer_class = CustomerSerializer

    # Only admin users can make requests other than 'GET'
    def get_permissions(self):
        if self.request.method == 'GET':
            return [permissions.AllowAny()]
        return [permissions.IsAdminUser()]

    @action(detail=False, methods=['GET', 'PUT'])
    def me(self, request):
        customer, created = Customer.objects.get_or_create(user_id=request.user.id)
        if request.method == 'GET':
            serializer = CustomerSerializer(customer)
            return Response(serializer.data)
        elif request.method == 'PUT':
            serializer = CustomerSerializer(customer, data=request.data)
            serializer.is_valid(raise_exception=True)
            serializer.save()
            return Response(serializer.data)

... и вот сериализатор:

class CustomerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Customer
        fields = ['id', 'user_id', 'profile_image', 'phone']

... и когда я пытаюсь сохранить нового клиента, отправляя POST запрос на конечную точку со следующими данными:
{ "profile_image": "Images/image.png", "phone": "009293930" },

Я получаю следующую ошибку:

IntegrityError at /api/customers/
(1048, "Column 'user_id' cannot be null")
Request Method: POST
Request URL:    http://127.0.0.1:8000/api/customers/
Django Version: 4.0.6
Exception Type: IntegrityError
Exception Value:    
(1048, "Column 'user_id' cannot be null")
Exception Location: /home/caleb/.local/share/virtualenvs/Cribr-svgsjjVF/lib/python3.8/site-packages/pymysql/err.py, line 143, in raise_mysql_exception
Python Executable:  /home/caleb/.local/share/virtualenvs/Cribr-svgsjjVF/bin/python
Python Version: 3.8.10
Python Path:    
['/home/caleb/Desktop/Cribr',
 '/home/caleb/Desktop/Cribr',
 '/snap/pycharm-professional/290/plugins/python/helpers/pycharm_display',
 '/usr/lib/python38.zip',
 '/usr/lib/python3.8',
 '/usr/lib/python3.8/lib-dynload',
 '/home/caleb/.local/share/virtualenvs/Cribr-svgsjjVF/lib/python3.8/site-packages',
 '/snap/pycharm-professional/290/plugins/python/helpers/pycharm_matplotlib_backend']
Server time:    Thu, 28 Jul 2022 23:38:53 +0000

Я понял, что проблема в том, что класс сериализатора не получает значение user_id из POST-запроса. Я попробовал передать request.user.id сериализатору из набора представлений через объект контекста, т.е. context={'user_id': request.user.id}, но я не смог понять, как затем добавить его в **validated data, которые сериализатор передает методу сохранения.

Любая помощь по этому вопросу будет очень признательна, заранее спасибо.

Преимущество использования DRF и наборов видов в том, что большая часть работы уже сделана за вас. В таких случаях, как этот, вам обычно нужно только подправить несколько вещей, чтобы заставить его работать так, как вы хотите. Я переписал ваше решение для вас ниже:

class CustomerViewSet(ModelViewSet):
    queryset = Customer.objects.all()
    permission_classes = [permissions.IsAdminUser]
    serializer_class = CustomerSerializer

    # Only admin users can make requests other than 'GET'
    def get_permissions(self):
        if self.request.method == 'GET':
            return [permissions.AllowAny()]
        return [permissions.IsAdminUser()]

    def get_object(self):
        customer, created = Customer.objects.get_or_create(user_id=self.request.user.id)
        return customer

    @action(detail=False, methods=['GET'])
    def me(self, request):
        return self.retrieve(request)

Из документации DRF:

Класс ModelViewSet наследуется от GenericAPIView и включает реализации для различных действий, смешивая поведение различных классов mixin.

Действия, предоставляемые классом ModelViewSet: .list(), .retrieve(), .create(), .update(), .partial_update(), и .destroy().

ModelViewSet также настроит для вас urlconf, который, исключая list, будет ожидать, что в url будет предоставлен объект pk (первичный ключ), чтобы представление могло знать, к какому ресурсу в базе данных вы пытаетесь получить доступ. В вашем случае вы хотите определить этот ресурс на основе учетных данных аутентификации, предоставленных в запросе. Для этого мы можем переопределить get_object, чтобы получить или создать клиента на основе идентификатора аутентифицированного пользователя.

Следующее изменение, которое мы сделаем, это определим наше действие для метода GET. Мы хотим иметь возможность получить ресурс, не указывая pk в url conf, поэтому detail=False. Затем мы можем просто вызвать встроенную функцию retrieve из этого действия, которая, в свою очередь, будет использовать get_object для получения и возврата объекта customer.

В-третьих, ваш запрос PUT будет направлен на update, который наследуется от ModelViewSet, поэтому вам не нужно ничего делать здесь, так как вы уже переписали get_object.

def update(self, request, *args, **kwargs):
    partial = kwargs.pop('partial', False)
    instance = self.get_object()
    serializer = self.get_serializer(instance, data=request.data, partial=partial)
    serializer.is_valid(raise_exception=True)
    self.perform_update(serializer)

    if getattr(instance, '_prefetched_objects_cache', None):
        # If 'prefetch_related' has been applied to a queryset, we need to
        # forcibly invalidate the prefetch cache on the instance.
        instance._prefetched_objects_cache = {}

    return Response(serializer.data)

И последнее, вы хотите установить user в вашем сериализаторе только для чтения (или просто удалить его полностью), так как он всегда должен быть установлен на основе учетных данных, переданных в запросе.

class CustomerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Customer
        fields = ['id', 'user_id', 'profile_image', 'phone']
        read_only_fields = ['user_id']

Отличным ресурсом для просмотра всех функций, которые вы наследуете от классов DRF, является https://www.cdrf.co/.

Удачи, надеюсь, это поможет!

Хорошо, мне удалось решить эту проблему, переопределив метод create в сериализаторе. Я добавил следующее:

class CustomerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Customer
        fields = ['id', 'user_id', 'profile_image', 'phone']
        read_only_fields = ['user_id']

    
    # NEW ---------------------------------
        def create(self, validated_data):
            user = self.context['request'].user
            customer = Customer.objects.filter(user_id=user)
            if customer.exists():
                raise serializers.ValidationError(
                'Customer already exists')
            else:
                customer = Customer.objects.create(
                    user=user, **validated_data)
            return customer

Теперь объект сохраняется нормально.

Вернуться на верх