如何让Django注释在“user”字段上使用select_related()?

时间:2011-10-25 09:18:51

标签: django performance

我正在使用django comments frameworks。所有评论都由经过身份验证的用户发布。在评论附近,我正在使用{{ comment.user.get_profile }}

显示一些用户个人资料信息
{# custom comment list templates #}
<dl id="comments">
  {% for comment in comment_list %}
    <dt id="c{{ comment.id }}">
        {{ comment.submit_date }} - {{ comment.user.get_profile.display_name }}
    </dt>
    <dd>
        <p>{{ comment.comment }}</p>
    </dd>
  {% endfor %}
</dl>

问题是django的评论查询不使用select_related(),而对于100条评论我在数据库中获得了101次。

有没有办法让django评论框架一次性为每个评论选择用户资料?

3 个答案:

答案 0 :(得分:10)

我测试了一个带有默认{% get_comment_list %}标签的对象的100条评论,而django做了200条评论相关的查询以列出评论+用户+个人资料,因为......

    如果Comment.__unicode__存在,
  1. Comment.user实际上会调用user_id。 +1查询
  2. get_profile +1查询
  3. 哎哟!

    我从~25ms的203个查询到~2ms的3个查询。

    自己填充comment_list

    我会强烈建议使用相应的comment_list调用自己构建QuerySet select_related()。如果经常使用它,请创建一个从其他视图调用的实用程序函数。

    def get_comments_with_user_and_profile(obj):
        content_type =ContentType.objects.get_for_model(obj)
        return (Comment.objects
            .filter(content_type=content_type, object_pk=obj.id)
            .select_related('user__profile'))
    

    如果您希望整个框架以这种方式运行......您将不得不修补补丁。

    这不是我会轻易做的事情。还有其他方法可以解决这个具体问题,但你确实“一气呵成”。

    将其放在INSTALLED_APPS models.py个文件中。我实际上有一个monkey_patch应用程序用于修改django.contrib.auth.User.username长度等等(这是最后的手段,不像这里)。

    from django.contrib.comments.models import Comment
    from django.contrib.comments.managers import CommentManager
    
    class CommentManager(CommentManager):
        def get_query_set(self):
            return (super(CommentManager, self)
                .get_query_set()
                .select_related('user__profile'))
    Comment.add_to_class('objects', CommentManager())
    

    使用个人资料和select_related()

    请注意,您的UserProfile课程需要OneToOneFieldUserrelated_name等于您传递给select_related()的课程。在我的例子中,它是profile,你需要django 1.2+。我记得以前磕磕绊绊。

    class UserProfile(models.Model):
        user = models.OneToOneField(User, related_name='profile') 
        # example to use User.objects.select_related('profile')
    

答案 1 :(得分:4)

假设你有这样的设置:

class UserProfile(models.Model):
    user = models.ForeignKey(User, related_name='profile')
    ...

您可以使用以下选择相关:Comments.objects.select_related('user__pk','user__profile__pk'),这应该做你想要的。

您必须扩展评论框架。这非常简单。基本上,创建自己的评论应用程序。您可以查看django-threadedcomments获取灵感(实际上,在某些方面,它已经是一个更好的实现方式)。

这里可以插入django-threaded评论应用程序的代码,以确保它始终使用相关的选择(在models.py中):

class RelatedCommentManager(CommentManager):
    def filter(self, *args, **kwargs):
        return super(RelatedCommentManager, self).select_related('user__pk','user__profile__pk').filter(*args, **kwargs)

    def exclude(self, *args, **kwargs):
        return super(RelatedCommentManager, self).select_related('user__pk','user__profile__pk').exclude(*args, **kwargs)

    def all(self)
        return super(RelatedCommentManager, self).select_related('user__pk','user__profile__pk').all()

并替换

    objects = CommentManager()

    objects = RelatedCommentManager()

按照说明将threcomments集成到您的应用中。

然后,在模板中,我认为您必须引用.profile而不是.get_profile

可能是Django会自动将其加入,因此只要get_profile可用,.profile就不会生成另一个数据库命中。

答案 2 :(得分:0)

在此示例中不能使用select_related(),因为User是配置文件的外键,反之亦然。 为了避免使用缓存(这可能是最佳选项),您可以使用外键创建Comment模型的代理模型。然后你可以写:

{{ comment.submit_date }} - {{ comment.user.profile.display_name }}