Я не могу справиться с проблемой restframework
N+1
.
Например, у меня есть следующие модели:
class User(models.Model):
username = ...
email = ...
avatar = ...
last_login = ...
class Article(models.Model):
user = models.ForeignKey(User)
title = ...
abstract = ...
content = ...
category = ...
class Comment(models.Model):
user = models.ForeignKey(User)
article = models.ForeignKey(Article)
content = ...
## This field! ##
reply_to = models.ForeignKey('self', null=True)
time = ...
То есть пользователь может комментировать статью иликомментарий к статье.
А теперь мне нужно построить систему уведомлений, поэтому я добавляю:
class Notification(models.Model):
actor_type = models.ForeignKey(
ContentType, related_name='notify_actor', on_delete=models.CASCADE)
actor_id = models.PositiveIntegerField()
actor = GenericForeignKey('actor_type', 'actor_id')
verb = models.CharField(max_length=255)
receiver = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='notifications')
is_read = models.BooleanField(default=False, db_index=True)
target_type = models.ForeignKey(
ContentType,
related_name='notify_target',
blank=True,
null=True,
on_delete=models.CASCADE
)
target_id = models.PositiveIntegerField()
target = GenericForeignKey('target_type', 'target_id')
time = models.DateTimeField(auto_now_add=True)
source_type = models.ForeignKey(
ContentType, blank=True, null=True,
related_name='notify_subject_object',
on_delete=models.CASCADE
)
source_id = models.PositiveIntegerField()
source = GenericForeignKey('source_type', 'source_id')
Уведомление выглядит так:
Yriuns commented Article 1: good job
<actor> <verb> <target> <source>
или:
Yriuns commented Your Comment of Article 1: I doubt it
<actor> <verb> <target> <source>
И serializers
:
class UserSeriazlier(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'avatar')
class ArticleSeriazlier(serializers.ModelSerializer):
class Meta:
model = Article
fields = ('title', 'abstract')
class SimpleCommentSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = ArticleComment
fields = ('id', 'user', 'content')
class CommentSeriazlier(serializers.ModelSerializer):
user = UserSeriazlier()
class Meta:
model = Comment
fields = ('id', 'content', 'user', 'time', 'reply_to')
class NotificationSerializer(serializers.ModelSerializer):
actor = GenericRelatedField(read_only=True)
target = GenericRelatedField(read_only=True)
subject = GenericRelatedField(read_only=True)
@classmethod
def setup_eager_loading(cls, queryset):
queryset = queryset.prefetch_related(
'actor',
'verb',
'target',
'source',
)
return queryset
class Meta:
model = Notification
fields = ('actor', 'verb', 'target', 'source', 'time', 'is_read')
class GenericRelatedField(serializers.RelatedField):
def to_representation(self, value):
if isinstance(value, User):
return UserSerializser(value).data
elif isinstance(value, Article):
return ArticleSerializser(value).data
elif isinstance(value, Comment):
return CommentSerializser(value).data
raise Exception('Unexpected type')
Проблема 1
Когда я сериализую список уведомлений пользователя:
qs = Notification.objects.filter(receiver=user)
qs = NotificationSerializer.setup_eager_loading(qs)
return NotificationSerializer(qs, many=True).data
, он запрашивает в базе данных source
один за другим, что на самом делемедленно.
Задача 2
Кроме того, он выбирает много полей, которые мне не нужны.
Например, мне нужны только username
и avatar
для UserSeriazlier
, но он выбирает все поля.И мне нужно только title
и Abstract
для ArticleSeriazlier
, но он выбирает все поля.
Как я могу решить эти проблемы?