Как заставить комментарии Django использовать select_related () в поле «user»? - PullRequest
6 голосов
/ 25 октября 2011

Я использую рамки комментариев 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 ]

10 голосов
/ 03 ноября 2011

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

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

Ой!

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

Заполните список комментариев самостоятельно

Я бы настоятельно рекомендовал построить 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_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 классу нужно от 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')
4 голосов
/ 02 ноября 2011

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

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

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

Вы должны будете расширить рамки комментариев. Это довольно просто. По сути, создайте свое собственное приложение для комментариев. Вы можете взглянуть на django-threadadedcomments для вдохновения (и, на самом деле, в некоторых отношениях это уже лучшая реализация для использования в любом случае).

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

Возможно, 1027 *, что Django автоматически учитывает это, поэтому get_profile не будет генерировать еще один удар по дб, пока доступно .profile.

0 голосов
/ 25 октября 2011

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

{{ comment.submit_date }} - {{ comment.user.profile.display_name }}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...