Как заставить комментарии 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 запросов, связанных с комментариями, чтобы перечислить комментарии + пользователя + профиль, потому что...
Comment.__unicode__
на самом деле звонкиComment.user
еслиuser_id
существует. +1 запросget_profile
+1 запрос
Ой!
Я перешел с 203 запросов за ~25 мс до 3 за ~ 2 мс.
Заполните 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
класс нуждается в 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 }}