Обработка разрешения Django Rest Framework при создании связанных объектов
Метод has_object_permission
разрешения на DRF, очевидно, не выполняется при Create, поскольку объект еще не существует. Однако существуют случаи, когда разрешение зависит от связанного объекта. Например:
class Daddy(models.Model):
name = models.CharField(max_length=20)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
class Kiddy:
title = models.CharField(max_length=12)
daddy = models.ForeignKey(Daddy, on_delete=models.CASCADE)
Если мы хотим разрешить только владельцу Daddy
создавать Kiddy
этого Daddy
, нам придется где-то подтвердить это.
Я знаю, что это действительно распространенное обсуждение, также упоминается в этом вопросе и во многих других. Это также обсуждается на самом DRF GitHub, где для этой цели было сделано обновление документации, и, ссылаясь на документацию DRF, она решает проблему здесь следующим предложением:
... Чтобы ограничить создание объекта, вам нужно реализовать проверку разрешения либо в классе Serializer, либо переопределить метод perform_create() вашего класса ViewSet.
Итак, ссылаясь на документацию DRF, мы можем сделать одно из следующих решений:
class KiddySerializer(viewsets.ModelViewSet):
validate_daddy(self, daddy):
if not daddy.owner == self.context['request'].user:
raise ValidationError("You cannot create Kiddies of that Daddy")
return daddy
или
class KiddyViewSet(ModelViewSet):
def perform_create(self, serializer):
if (self.request.user != serializer.validated_data['daddy'].owner)
raise ValidationError("You cannot create Kiddies of that Daddy")
serializer.save()
Теперь возникает проблема, которая также вызывает мой вопрос. Что если меня волнует информация, которая передается пользователю при несанкционированном запросе. Так, в случаях, когда Daddy
не существует, пользователь получит:
Invalid pk \"11\" - object does not exist
, а в случаях, когда объект существует, но у пользователя нет доступа, он вернет You cannot create Kiddies of that Daddy
Я хочу показывать одно и то же сообщение в обоих случаях:
The Daddy does not exist or you don't have permission to use it.
Это возможно, если я использую PermissionClass, как показано ниже:
class OwnsDaddy(BasePermission):
def has_permission(self, request, view):
if not Daddy.objects.allowed_daddies(request.user).filter(pk=request.data['daddy']).exists():
return False
это тоже будет работать, но поскольку разрешения проверяются перед сериализатором, если ID папочки, переданный пользователем, будет неверным (допустим, строка), возникнет ошибка 500. Мы можем предотвратить это с помощью условия try-except, но все равно это не совсем правильно.
Итак, в конце. Каким был бы хороший подход к этой проблеме?
Валидируйте в вашем сериализаторе вместо представления. Вы можете валидировать конкретное поле в validate_<field>
или все поле в целом в validate
функции
class YourModleSerializer(serializers.ModelSerializer):
def validate_field(self, value):
return value
def validate(self, attrs):
validated_data = super().validate(attrs)
return validated_data
Создание пользовательского отношения полей было правильным подходом для моего случая.
from apps.models import Daddy
from rest_framework.serializers import PrimaryKeyRelatedField
class DaddyRelatedField(PrimaryKeyRelatedField):
default_error_messages = {
"required": _("This field is required."),
"does_not_exist": _("The Daddy does not exist or you don't have permission to use it."),
"incorrect_type": _("Incorrect type. Expected pk value, received {data_type}."),
}
def get_queryset(self):
if self.read_only:
return None
queryset = Daddy.objects.all()
return queryset.allowed_daddies(self.context["request"].user)
и затем на сериализаторе
class KiddySerializer(ModelSerializer):
daddy = DaddyRelatedField()