Как передать '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
Теперь объект сохраняется нормально.