Как prefetch_related GenericForeignKey с самостоятельной ссылкой ForeignKey в restframwork - PullRequest
0 голосов
/ 29 ноября 2018

Я не могу справиться с проблемой 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, но он выбирает все поля.

Как я могу решить эти проблемы?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...