如何使 Django 注释在“用户”上使用 select_lated()场地?

发布于 2024-12-11 15:42:23 字数 679 浏览 0 评论 0原文

我正在使用 django 注释框架。所有评论均由经过身份验证的用户发布。在评论附近,我使用 {{ 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_lated() 并且100 条评论我在数据库中得到了 101 条点击。

有没有一种方法可以让 django 评论框架一次性为每个评论选择用户配置文件?

I'm using django comments frameworks. All the comments are posted by authenticated users. Near the comment, I'm showing some user profile info using {{ 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>

Problem is that django's comment queries does not use select_related() and for 100 comments I get 101 hit on the database.

Is there a way to make django comments framework to select user profile for each comment in one go?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

冰魂雪魄 2024-12-18 15:42:23

我测试了使用默认 {% get_comment_list %} 标记为对象渲染 100 条评论,django 执行了 200 条评论相关查询来列出评论 + 用户 + 配置文件,因为...

  1. Comment.__unicode__<如果 user_id 存在,/code> 实际上会调用 Comment.user。 +1 查询
  2. get_profile +1 查询

哎呀!

我从约 25 毫秒内的 203 个查询减少到约 2 毫秒内的 3 个查询。

自己填充 comment_list

强烈建议使用适当的 select_lated() 调用自己构建 comment_list QuerySet。如果经常使用,请创建一个从其他视图调用的实用函数。

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.models.User.username 长度等(这是与这里不同的最后手段)。

from django.contrib.comments.models import Comment
from django.contrib.comments.managers import CommentManager

class CommentManager(CommentManager):
    def get_queryset(self):
        return (super(CommentManager, self)
            .get_queryset()
            .select_related('user__profile'))
Comment.add_to_class('objects', CommentManager())

有关配置文件和 select_lated() 的问题

请注意,您的 UserProfile 类需要一个 OneToOneFieldUser,其 lated_name 等于您传递给 select_lated() 的内容。在我的示例中,它是 profile 并且您需要 django 1.2+。我记得以前偶然发现过这一点。

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

I tested rendering 100 comments for an object with the default {% get_comment_list %} tag and django did 200 comment related queries to list the comments + user + profile because...

  1. Comment.__unicode__ actually calls Comment.user if a user_id exists. +1 query
  2. get_profile +1 query

Ouch!

I went from 203 queries in ~25ms to 3 in ~2ms.

Populate comment_list yourself

I would highly suggest building the comment_list QuerySet yourself using the appropriate select_related() calls. If it's used often, create a utility function called from your other views.

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'))

If you want the entire framework to behave this way... You'll have to monkey patch.

It's not something I would do lightly. There are other ways around this specific problem but you did ask "in one go".

Put this somewhere in your INSTALLED_APPS models.py files. I actually have a monkey_patch app for modifying django.contrib.auth.models.User.username lengths and such (which is a last resort unlike here).

from django.contrib.comments.models import Comment
from django.contrib.comments.managers import CommentManager

class CommentManager(CommentManager):
    def get_queryset(self):
        return (super(CommentManager, self)
            .get_queryset()
            .select_related('user__profile'))
Comment.add_to_class('objects', CommentManager())

Gotchas with profiles and select_related()

Note that your UserProfile class needs a OneToOneField to User with a related_name equal to what you pass to select_related(). In my example it's profile and you need django 1.2+. I recall stumbling on that before.

class UserProfile(models.Model):
    user = models.OneToOneField(User, related_name='profile') 
    # example to use User.objects.select_related('profile')
伴我心暖 2024-12-18 15:42:23

假设您有这样的设置:

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

您可以使用以下选择相关:Comments.objects.select_lated('user__pk','user__profile__pk'),这应该可以满足您的要求。

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

您可以将以下代码插入到 django 线程注释应用程序中,以确保它始终使用 select 相关的(在 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()

将线程注释集成到您的应用程序中的说明进行操作。

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

可能是 Django 会自动考虑这一点,因此只要 .profile 可用,get_profile 就不会生成另一个数据库命中。

Assuming that you have a setup like so:

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

You can use the following select related: Comments.objects.select_related('user__pk','user__profile__pk') and that should do what you want.

You'll have to extend the comments framework. This is fairly straightforward. Basically, create your own comments app. You can look at django-threadedcomments for inspiration (and, actually, in some ways it's already a better implementation to use anyway).

Here's code you can insert into the django-threaded comments app to make sure it always uses the select related (in 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()

and replace

    objects = CommentManager()

with

    objects = RelatedCommentManager()

Follow the instructions for integrating threadedcomments into your app.

Then, in the template, I think you'll have to reference .profile instead of .get_profile.

It may be that Django automatically factors this in, so get_profile will not generate another db hit so long as .profile is available.

翻了热茶 2024-12-18 15:42:23

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

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

You can't use select_related() in this example, because User is foreign key of profile, not vice versa.
To avoid using cache (which is probably the best option) you could create proxy model for Comment with foreign key to your profile model. then you could write:

{{ comment.submit_date }} - {{ comment.user.profile.display_name }}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文