Django REST Framework UniqueTogetherValidator не работает с FK из kwargs
Я вляпался в неприятности с UniqueTogetherValidator
.
Проблема в том, что ReviewSerliazer
в отличие от CommentSerializer
, который почти идентичен, делает уникальную совместную валидацию перед фактическим получением значения title из kwargs и отправляет обратно 400 ответов с обязательным полем title. Я пытался идентифицировать его как HiddenField
, но хотя валидация сериализатора проходит нормально в тестах, валидация модели не проходит. И я получаю django.db.utils.IntegrityError: UNIQUE constraint failed: reviews_review.author_id, reviews_review.title_id
Ключевая идея заключается в том, что заголовок не должен включаться в ответ сериализатора. Делать его PrimaryKeyRelatedField и затем делать to_representation кажется мне странным.
Есть идеи, как исправить это без перехвата исключений в наборе представлений, что, очевидно, неправильно?
models.py
class BasePost(models.Model):
text = models.TextField()
author = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='%(class)ss')
pub_date = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-pub_date', )
abstract = True
def __str__(self):
return self.text[:30]
class Review(BasePost):
score = models.IntegerField(
default=0, validators=[MaxValueValidator(10), MinValueValidator(1)])
title = models.ForeignKey(
Title, on_delete=models.CASCADE, related_name='reviews')
class Meta(BasePost.Meta):
constraints = [
models.UniqueConstraint(
fields=('author', 'title', ), name='unique_title_review')]
class Comment(BasePost):
review = models.ForeignKey(
Review, on_delete=models.CASCADE, related_name='comments')
urls.py
router_v1.register(
r'^titles/(?P<title_id>\d+)/reviews', ReviewViewSet, basename='review')
router_v1.register(
r'^titles/(?P<title_id>\d+)/reviews/(?P<review_id>\d+)/comments',
CommentViewSet, basename='comment')
views.py
class ReviewViewSet(BasePostViewSet):
serializer_class = ReviewSerializer
def get_queryset(self):
return self.get_title().reviews.all()
def perform_create(self, serializer):
serializer.save(author=self.request.user, title=self.get_title())
def get_title(self, key='title_id'):
return get_object_or_404(Title, id=self.kwargs.get(key))
class CommentViewSet(BasePostViewSet):
serializer_class = CommentSerializer
def get_queryset(self):
return self.get_review().comments.all()
def perform_create(self, serializer):
serializer.save(author=self.request.user, review=self.get_review())
def get_review(self, key='review_id'):
return get_object_or_404(Review, id=self.kwargs.get(key))
serializers.py
class ReviewSerializer(BasePostSerializer):
title = serializers.HiddenField(default=None)
class Meta:
model = Review
fields = ('id', 'text', 'author', 'score', 'pub_date', 'title', )
validators = [UniqueTogetherValidator(
queryset=Review.objects.all(), fields=('author', 'title', ))]
class CommentSerializer(BasePostSerializer):
class Meta:
model = Comment
fields = ('id', 'text', 'author', 'pub_date', )
def create(self, validated_data):
try:
review = Review.objects.create(**validated_data)
except IntegrityError:
raise serializers.ValidationError(
{'detail': 'Вы можете оставить только один отзыв.'})
return review
В настоящее время исправлено так, но мне кажется, что это неправильно.
Во-первых, я предположу, что BasePostViewSet
действительно наследует от CreateModelMixin
. Мое предположение основано на том, что вы переопределяете perform_create
:
def perform_create(self, serializer):
serializer.save()
Согласно DRF документации о передаче дополнительных аргументов методу .save()
:
Дополнительные аргументы ключевых слов будут включены в аргумент
validated_data
при вызове.create()
или.update()
.
Оригинальный метод .create()
из CreateModelMixin
:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.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)
Он выполняет проверку перед вызовом метода .perform_create()
, поэтому ваша переопределенная версия .perform_create
:
def perform_create(self, serializer):
serializer.save(author=self.request.user, review=self.get_review())
Вызывается после валидации, следовательно, ваш валидатор сериализатора не проверяется с нужными вам аргументами.
Теперь, я думаю, вы можете достичь этого, включив ваши поля в данные функции to_internal_value
следующим образом (не пробовал, но определенно это запускается перед валидацией) (я предполагаю, что контекст сериализатора передается соответствующим образом):
def to_internal_value(self, data):
data['author'] = self.context['request'].user
data['review'] = self.context['view'].get_review()
return data
Надеюсь, это поможет.