Как заставить комментарии Django использовать select_related() в поле "user"?

Я использую рамки комментариев 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_related() и за 100 комментариев я получаю 101 попадание в базу данных.

Есть ли способ сделать каркас комментариев django для выбора профиля пользователя для каждого комментария за один раз?

3 ответа

Решение

Я протестировал рендеринг 100 комментариев для объекта по умолчанию {% get_comment_list %} tag и django сделали 200 запросов, связанных с комментариями, чтобы перечислить комментарии + пользователя + профиль, потому что...

  1. Comment.__unicode__ на самом деле звонки Comment.user если user_id существует. +1 запрос
  2. get_profile +1 запрос

Ой!

Я перешел с 203 запросов за ~25 мс до 3 за ~ 2 мс.

Заполните comment_list самостоятельно

Я настоятельно рекомендую построить comment_listQuerySet самостоятельно используя соответствующий 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_APPSmodels.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 класс нуждается в OneToOneField в User с related_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')

Предполагая, что у вас есть настройки, как это:

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

Вы можете использовать следующие выберите связанные: Comments.objects.select_related('user__pk','user__profile__pk') и это должно делать то, что вы хотите.

Вы должны будете расширить рамки комментариев. Это довольно просто. По сути, создайте свое собственное приложение для комментариев. Вы можете посмотреть на http://code.google.com/p/django-threadedcomments/ для вдохновения (и, на самом деле, в некоторых случаях это уже лучшая реализация для использования в любом случае).

Вот код, который вы можете вставить в приложение django-threadaded comments, чтобы убедиться, что оно всегда использует select selected (в 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 автоматически учитывает это, поэтому get_profile не будет генерировать еще один удар дБ, пока .profile доступен.

Вы не можете использовать select_related() в этом примере, потому что пользователь является внешним ключом профиля, а не наоборот. Чтобы избежать использования кэша (что, вероятно, является лучшим вариантом), вы можете создать прокси-модель для комментариев с внешним ключом для вашей модели профиля. тогда вы могли бы написать:

{{ comment.submit_date }} - {{ comment.user.profile.display_name }}
Другие вопросы по тегам