#django #performance
#django #Производительность
Вопрос:
Я использую фреймворки комментариев 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 для выбора профиля пользователя для каждого комментария за один раз?
Комментарии:
1. вы не думали о том, чтобы применить некоторое кэширование к get_profile
Ответ №1:
Я протестировал рендеринг 100 комментариев для объекта с {% get_comment_list %}
тегом по умолчанию, и django выполнил 200 запросов, связанных с комментариями, чтобы перечислить комментарии user profile, потому что…
Comment.__unicode__
на самом деле вызываетComment.user
, если auser_id
существует. 1 запросget_profile
1 запрос
Ой!
Я перешел от 203 запросов за ~ 25 мс к 3 за ~ 2 мс.
Заполните список комментариев самостоятельно
Я бы настоятельно рекомендовал создать 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')
Комментарии:
1. Monkey patch — не самое красивое решение в мире, но поскольку у django такой «обезьяний» подход, ничего не осталось. Спасибо!
2. Я бы предпочел явно вызывать
select_related()
в рассматриваемых представлениях. Хотя, конечно, это ваш выбор!3. Monkeypatching — это не ответ. Приложение comments специально позволяет вам использовать ваши собственные модели комментариев (см., Например, ThreadedComments). Серьезно, все, что вам нужно сделать, это создать свою собственную ветку ThreadedComments.
4. возврат (комментарий.объекты . фильтр(content_type=content_type, object_pk=obj.id ) .select_related(‘user__profile__DOB’)) выбирает всю таблицу профилей, я хочу выбрать только DOB
Ответ №2:
Предполагая, что у вас есть такая настройка:
class UserProfile(models.Model):
user = models.ForeignKey(User, related_name='profile')
...
Вы можете использовать следующий select related: Comments.objects.select_related('user__pk','user__profile__pk')
и это должно делать то, что вы хотите.
Вам придется расширить рамки комментариев. Это довольно просто. По сути, создайте свое собственное приложение для комментариев. Вы можете посмотреть на django-threadedcomments для вдохновения (и, на самом деле, в некотором смысле это уже лучшая реализация для использования в любом случае).
Вот код, который вы можете вставить в приложение комментариев с потоком django, чтобы убедиться, что оно всегда использует связанный с выбором (в 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()
Следуйте инструкциям по интеграции threadedcomments в ваше приложение.
Затем, в шаблоне, я думаю, вам придется ссылаться .profile
вместо .get_profile
.
Возможно, Django автоматически учитывает это, поэтому get_profile
не будет генерировать еще одно попадание в базу данных до тех пор, пока .profile
оно доступно.
Ответ №3:
Вы не можете использовать select_related() в этом примере, потому что пользователь является внешним ключом профиля, а не наоборот. Чтобы избежать использования кэша (что, вероятно, является лучшим вариантом), вы могли бы создать прокси-модель для комментариев с внешним ключом к вашей модели профиля. тогда вы могли бы написать:
{{ comment.submit_date }} - {{ comment.user.profile.display_name }}
Комментарии:
1. На самом деле, я думаю, что смогу. Вопрос только в том, как мне это сделать, поскольку этот код является частью django.
2. get_profile — это метод, а не поле или любой тип отношения, который можно получить в одном запросе с помощью select_related . чтобы ответить на вопрос из темы, вы можете создать прокси-модель для пользователя / комментария с помощью пользовательского менеджера объектов, который будет применять select_related к набору запросов. Но это не решит вашу проблему с получением UserProfile без дополнительного запроса.
3. На самом деле, хитрость заключается в настройке UserProfile с помощью a
OneToOneField
, а не aForeignKeyField
. Таким образом,select_related
будет работать в обоих направлениях.