Установка Django Debug Toolbar
Для начала мы установим Django Debug Toolbar в наш Django блог проект с помощью команды в терминале: pip install django-debug-toolbar
Результат установки:
>>> Collecting django-debug-toolbar
>>> Downloading django_debug_toolbar-3.6.0-py3-none-any.whl (220 kB)
>>> ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 220.2/220.2 KB 1.3 MB/s eta 0:00:00
>>> Requirement already satisfied: sqlparse>=0.2.0 in c:\users\razilator\desktop\courses\app\venv\lib\site-packages (from django-debug-toolbar) (0.4.2)
>>> Requirement already satisfied: Django>=3.2.4 in c:\users\razilator\desktop\courses\app\venv\lib\site-packages (from django-debug-toolbar) (4.1)
>>> Requirement already satisfied: tzdata in c:\users\razilator\desktop\courses\app\venv\lib\site-packages (from Django>=3.2.4->django-debug-toolbar) (2022.2)
>>> Requirement already satisfied: asgiref<4,>=3.5.2 in c:\users\razilator\desktop\courses\app\venv\lib\site-packages (from Django>=3.2.4->django-debug-toolbar) (3.5.2)
>>> Installing collected packages: django-debug-toolbar
>>> Successfully installed django-debug-toolbar-3.6.0
Отлично, теперь нужно его подключить:
Убедитесь, что у вас установлена статика:
backend/settings.py
INSTALLED_APPS = [
# ...
"django.contrib.staticfiles",
# ...
]
STATIC_URL = "static/"
Теперь добавим наш дебагер:
backend/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sites',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'mptt',
'debug_toolbar',
'modules.blog.apps.BlogConfig',
'modules.system.apps.SystemConfig',
]
Также нам необходимо добавить middleware
backend/settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
]
Также добавим возле SITE_ID = 1
в settings.py
backend/settings.py
INTERNAL_IPS = [
'127.0.0.1',
]
И наконец в urls.py:
backend/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from backend import settings
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('modules.blog.urls')),
path('s/', include('modules.system.urls'))
]
if settings.DEBUG:
urlpatterns = [path('__debug__/', include('debug_toolbar.urls'))] + urlpatterns
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Проверяем на сайте:

Как видите из скриншота выше, у нас на странице 8 запросов. Давайте их оптимизируем.
Для этого посмотрим SQL:

В Django существует ряд полезных встроенных функций, и одна из них select_related()
позволяет нам точно указать Django, какие связанные модели мы хотим, чтобы он мог заранее выполнить JOINs.
В нашем случае, у нас есть модель Article, которая ссылается на Category в списке всех материалов. Давайте попробуем оптимизировать это.
Помните наш кастомный менеджер? ArticleManager()
через него мы и будем оптимизировать!
modules/blog/managers/articles.py
Напоминаю, что мы менеджер создали в уроке 17, свой кастомный менеджер QuerySet
from django.db import models
class ArticleManager(models.Manager):
"""
Кастомный менеджер для модели статей.
"""
def all(self):
"""
Список статей (SQL запрос с фильтрацией для страницы списка статей)
"""
return self.get_queryset().filter(is_published=True)
И так, давайте его дополним:
class ArticleManager(models.Manager):
"""
Кастомный менеджер для модели статей.
"""
def all(self):
"""
Список статей (SQL запрос с фильтрацией для страницы списка статей)
"""
return self.get_queryset().filter(is_published=True).select_related('category')
Отлично, давайте в нашем views/articles.py исправим кое-что если вы следовали всем урокам:
modules/blog/views/articles.py
class ArticleListView(ListView):
model = Article
template_name = 'modules/blog/articles/article-list.html'
context_object_name = 'articles'
paginate_by = 10
queryset = Article.objects.all().filter(is_published=True)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'Главная страница'
return context
Нужно изменить queryset = Article.objects.all().filter(is_published=True)
на queryset = Article.custom.all()
Кстати, оптимизировать мы также можем и в самом ListView в свойстве queryset, например сделать так:
class ArticleListView(ListView):
model = Article
template_name = 'modules/blog/articles/article-list.html'
context_object_name = 'articles'
paginate_by = 10
queryset = Article.objects.all().filter(is_published=True).select_related('category')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'Главная страница'
return context
Это будет аналогично тому, что вы делаете в кастомном менеджере, но ведь лучше хранить некоторые элементы по отдельности, чтоб их потом было легче найти из всей кучи, верно? Поэтому я и добавляю сразу это в наш кастомный менеджер. Давайте посмотрим на результат такой работы:

Отлично, для списка статей мы провели оптимизацию. А давайте сделаем тоже самое для полной странички статьи? Для примера я добавлю все ссылаемые элементы прям в шаблон:
templates/modules/blog/articles/article-detail.html
{% extends 'main.html' %}
{% block content %}
<div class="card border-0">
<div class="card-body">
<div class="row">
<div class="col-md-4">
<figure>
<img src="{{ article.get_thumbnail }}" width="200" alt="{{ article.title }}">
</figure>
</div>
<div class="col-md-8">
<h5 class="card-title">
{{ article.title }}
</h5>
<small class="card-subtitle">
{{ article.created_at }} / {{ article.category }}
</small>
<div class="card-text">
{{ article.full_description }}
</div>
<hr/>
Добавил: <img src="{{ article.author.profile.get_avatar }}" class="rounded-circle" width="26" height="26"/> {{ article.author }}
<hr/>
{% if article.reason %}{{ article.reason }} / обновил: {{ article.updated_by }}{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}
Из структуры видно, что мы видим и автора и категорию и того кто обновил.

Теперь давайте оптимизируем в нашем менеджере:
modules/blog/managers/articles.py
class ArticleManager(models.Manager):
"""
Кастомный менеджер для модели статей.
"""
def all(self):
"""
Список статей (SQL запрос с фильтрацией для страницы списка статей)
"""
return self.get_queryset().filter(is_published=True).select_related('category')
def detail(self):
"""
Детальная страница статьи с оптимизацией
"""
return self.get_queryset().filter(is_published=True).select_related('category', 'updated_by', 'author', 'author__profile')
Пояснения:
- Я добавил метод detail в наш кастомный менеджер, который мы будем использовать при детальном просмотре странице.
- В join я завел категорию, апдейтера, автора, и его профиль. Из другой модели (author) ссылаемой на модель (profile) - необходимо использовать два __
Добавим наш метод в ArticleDetailView:
modules/blog/views/articles.py
class ArticleDetailView(DetailView):
model = Article
template_name = 'modules/blog/articles/article-detail.html'
context_object_name = 'article'
queryset = Article.custom.detail()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = self.object.title
return context
Наконец посмотрим на результат:

Из 8 запросов получилось 4 запроса, что является отличным результатом.
Есть ещё метод prefetch_related()
Мы используем prefetch_related
, когда собираемся получить набор вещей. Это означает обработку ManyToMany и обратных ManyToMany, ForeignKey. prefetch_related
выполняет отдельный поиск для каждой связи и выполняет «объединение» в Python. Он отличается от select_related
. prefetch_related выполнял JOIN с использованием Python, а не в базе данных. Подробнее я вам расскажу об этом запросе когда мы создадим модель ссылающуюся на статью, в нашем случае это будут комментарии.
Подробнее о select_related()
и prefetch_related()