如何在重新构架中使用自引用ForeignKey来prefetch_related GenericForeignKey

时间:2018-11-29 11:55:52

标签: python django django-rest-framework

我无法处理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的{​​{1}}和avatar,但是它将选择所有字段。而且我只需要UserSeriazlier的{​​{1}}和title,但是它将选择所有字段。

我该如何解决这些问题?

0 个答案:

没有答案