Ниже представлен последний выпуск серии исследований классовых основ представлений (CBV) Django, где мы подробно рассматриваем, как эффективно использовать ListView для реализации таких часто используемых функций, как пагинация, поиск и сортировка. Если в предыдущих 7 выпусках мы рассмотрели основы CBV и использование Mixin, то в этом выпуске мы обобщим это и посмотрим, как чисто и эффективно справляться с общими требованиями, с которыми мы сталкиваемся в реальной веб-разработке.

Вы можете найти ссылку на предыдущий выпуск ниже!

Классовая основа представлений (CBV) Исследовательская серия ⑦ - Использование Mixin и управление правами


“Расширяйте Django ListView для реализации мощных функций списков и улучшайте пользовательский опыт!”


1. ListView: больше возможностей

ListView — это очень полезное обобщенное представление для отображения списков данных. Однако в реальных веб-приложениях, помимо простого отображения списка, необходимо реализовать функции пагинации для эффективного управления большими объемами данных, поиск для быстрого нахождения нужной информации и сортировку по предпочтениям пользователя.

В этом разделе мы рассмотрим, как расширить ListView и элегантно реализовать эти практические требования, используя преимущества CBV.


2. Применение пагинации (Pagination)

Отображение большого объема данных за один раз негативно сказывается на пользовательском опыте. Пагинация помогает разбить данные на несколько страниц, снижая нагрузку на загрузку и упрощая поиск необходимой информации.

2.1 Настройка базовой пагинации

ListView предоставляет встроенную функцию пагинации. Вам просто нужно установить свойство paginate_by в классе представления.

Пример views.py

from django.views.generic import ListView
from .models import Article

class ArticleListView(ListView):
    model = Article
    template_name = 'articles/article_list.html'
    context_object_name = 'articles'
    paginate_by = 10 # Отображение 10 объектов Article на одной странице
  • paginate_by = 10: при таком установке ListView автоматически разбивает результаты на страницы по 10 объектов и передает объекты, связанные с пагинацией (paginator, page_obj, is_paginated), в контекст шаблона.

2.2 Реализация UI пагинации в шаблоне

В шаблоне можно использовать page_obj из контекста для отображения ссылок на номера страниц и реализации кнопок предыдущей/следующей страницы.

Пример articles/article_list.html

<ul>
    {% for article in articles %}
        <li>{{ article.title }} - {{ article.created_at }}</li>
    {% empty %}
        <li>Пока нет опубликованных статей.</li>
    {% endfor %}
</ul>

<div class="pagination">
    <span class="step-links">
        {% if page_obj.has_previous %}
            <a href="?page=1">&laquo; В начало</a>
            <a href="?page={{ page_obj.previous_page_number }}">Предыдущая</a>
        {% endif %}

        <span class="current">
            Страница {{ page_obj.number }} / {{ paginator.num_pages }}
        </span>

        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}&search_term={{ search_form.search_term.value|default:'' }}">Следующая</a>
            <a href="?page={{ paginator.num_pages }}&search_term={{ search_form.search_term.value|default:'' }}">Последняя &raquo;</a>
        {% endif %}
    </span>
</div>
  • page_obj: объект, содержащий данные текущей страницы. Предоставляет свойства, такие как has_previous, has_next, previous_page_number, next_page_number, number и др.
  • paginator: объект, содержащий информацию о всех страницах. Предоставляет свойства, такие как num_pages (общее количество страниц).

2.3 Сохранение URL-соединения

Создавая ссылки пагинации, мы можем увидеть использование параметра запроса ?page=2. ListView автоматически распознает этот параметр и отображает данные соответствующей страницы.


Быстрый поиск нужной информации на веб-сайте очень важен. Давайте добавим функцию поиска, расширив ListView.

3.1 Определение формы (Form)

Сначала определим простую форму для ввода поискового запроса.

Пример forms.py

from django import forms

class ArticleSearchForm(forms.Form):
    search_term = forms.CharField(label='Поисковый запрос', required=False)

3.2 Изменение ListView

Мы переопределим метод get_queryset() у ListView для реализации функции поиска.

Пример views.py

from django.views.generic import ListView
from django.db.models import Q
from .models import Article
from .forms import ArticleSearchForm

class ArticleListView(ListView):
    model = Article
    template_name = 'articles/article_list.html'
    context_object_name = 'articles'
    paginate_by = 10

    def get_queryset(self):
        queryset = super().get_queryset()
        self.form = ArticleSearchForm(self.request.GET) # Получение данных формы из GET-запроса

        if self.form.is_valid():
            search_term = self.form.cleaned_data.get('search_term')
            if search_term:
                # Фильтрация Article, включающего поисковый запрос в заголовок или в содержимое
                queryset = queryset.filter(
                    Q(title__icontains=search_term) | Q(content__icontains=search_term)
                )
        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['search_form'] = self.form # Передача формы в шаблон
        return context
  • get_queryset(): Переопределите этот метод, чтобы изменить основной набор данных.
  • Создайте экземпляр формы и, если она проходит проверку, извлеките поисковый запрос из cleaned_data.
  • Используя Q объект, фильтруйте объекты Article, включающие поисковый запрос в заголовок (title) или в содержимое (content) ( __icontains проверяет наличие без учета регистра).
  • get_context_data(): добавьте экземпляр формы в контекст, чтобы отобразить форму в шаблоне.

3.3 Отображение формы поиска и сохранение результатов в шаблоне

Добавим форму поиска в шаблон и настроим URL, чтобы можно было осуществлять пагинацию, сохраняя поисковые результаты.

Пример articles/article_list.html

<form method="get">
    {{ search_form }}
    <button type="submit">Поиск</button>
</form>

<ul>
    {% for article in articles %}
        <li>{{ article.title }} - {{ article.created_at }}</li>
    {% empty %}
        <li>Результатов поиска не найдено.</li>
    {% endfor %}
</ul>

<div class="pagination">
    <span class="step-links">
        {% if page_obj.has_previous %}
            <a href="?page=1&search_term={{ search_form.search_term.value|default:'' }}">&laquo; В начало</a>
            <a href="?page={{ page_obj.previous_page_number }}&search_term={{ search_form.search_term.value|default:'' }}">Предыдущая</a>
        {% endif %}

        <span class="current">
            Страница {{ page_obj.number }} / {{ paginator.num_pages }}
        </span>

        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}&search_term={{ search_form.search_term.value|default:'' }}">Следующая</a>
            <a href="?page={{ paginator.num_pages }}&search_term={{ search_form.search_term.value|default:'' }}">Последняя &raquo;</a>
        {% endif %}
    </span>
</div>
  • Установите метод формы на get, чтобы передать поисковый запрос как параметр URL.
  • При создании ссылки на страницу включите текущий поисковый запрос (search_form.search_term.value) как параметр URL, чтобы пагинация осуществлялась с сохранением поисковых результатов. Используйте фильтр |default:'', чтобы использовать пустую строку, если поискового запроса нет.

Пример экрана ListView – страница со списком, включая UI для поиска и сортировки


4. Добавление функции сортировки (Ordering)

Давайте добавим функцию сортировки, чтобы пользователи могли видеть данные в соответствии с выбранными критериями.

4.1 Добавление формы для выбора сортировки (по желанию)

Можно также добавить всплывающее меню формы для выбора критерия сортировки. Либо можно обойтись только URL-параметрами. Здесь мы объясним, как использовать URL-параметры.

4.2 Изменение ListView

Модифицируем метод get_queryset(), чтобы применить order_by().

Пример views.py (с функцией поиска)

from django.views.generic import ListView
from django.db.models import Q
from .models import Article
from .forms import ArticleSearchForm

class ArticleListView(ListView):
    model = Article
    template_name = 'articles/article_list.html'
    context_object_name = 'articles'
    paginate_by = 10

    def get_queryset(self):
        queryset = super().get_queryset()
        self.form = ArticleSearchForm(self.request.GET)
        ordering = self.request.GET.get('ordering', '-created_at') # Сортировка по умолчанию: по последним

        if self.form.is_valid():
            search_term = self.form.cleaned_data.get('search_term')
            if search_term:
                queryset = queryset.filter(
                    Q(title__icontains=search_term) | Q(content__icontains=search_term)
                )

        queryset = queryset.order_by(ordering)
        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['search_form'] = self.form
        context['ordering'] = self.request.GET.get('ordering', '-created_at') # Передача текущего критерия сортировки в шаблон
        return context
  • self.request.GET.get('ordering', '-created_at'): получение значения параметра ordering из URL-запроса. Если значения нет, по умолчанию используется -created_at (по последним).
  • queryset = queryset.order_by(ordering): сортировка набора данных по полученному значению ordering. Если перед значением -, сортировка будет обратной.

4.3 Предоставление ссылок сортировки в шаблоне

В шаблоне предоставьте ссылки для сортировки по каждому полю.

Пример articles/article_list.html

<form method="get">
    {{ search_form }}
    <button type="submit">Поиск</button>
</form>

<table>
    <thead>
        <tr>
            <th><a href="?ordering=title&search_term={{ search_form.search_term.value|default:'' }}">Заголовок</a></th>
            <th><a href="?ordering=-created_at&search_term={{ search_form.search_term.value|default:'' }}">Дата создания</a></th>
            </tr>
    </thead>
    <tbody>
        {% for article in articles %}
            <tr>
                <td>{{ article.title }}</td>
                <td>{{ article.created_at }}</td>
            </tr>
        {% empty %}
            <tr><td colspan="2">Результатов поиска не найдено.</td></tr>
        {% endfor %}
    </tbody>
</table>

<div class="pagination">
    <span class="step-links">
        {% if page_obj.has_previous %}
            <a href="?page=1&search_term={{ search_form.search_term.value|default:'' }}&ordering={{ ordering }}">&laquo; В начало</a>
            <a href="?page={{ page_obj.previous_page_number }}&search_term={{ search_form.search_term.value|default:'' }}&ordering={{ ordering }}">Предыдущая</a>
        {% endif %}

        <span class="current">
            Страница {{ page_obj.number }} / {{ paginator.num_pages }}
        </span>

        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}&search_term={{ search_form.search_term.value|default:'' }}&ordering={{ ordering }}">Следующая</a>
            <a href="?page={{ paginator.num_pages }}&search_term={{ search_form.search_term.value|default:'' }}&ordering={{ ordering }}">Последняя &raquo;</a>
        {% endif %}
    </span>
</div>
  • Для каждого заголовка таблицы предоставьте ссылки на критерий сортировки, включая параметр ordering.
  • При создании ссылок на страницы не забывайте включать как текущий поисковый запрос, так и текущий критерий сортировки (ordering) в параметры URL, чтобы пагинация происходила с сохранением состояния сортировки.

Диаграмма процесса поиска/сортировки – визуализация потока расширения ListView


5. Объединение функций с помощью Mixin

Используя ранее изученные Mixin, можно более эффективно управлять такими функциями, как пагинация, поиск и сортировка. Например, можно отделить функцию поиска в Mixin.

Пример mixins.py

from django.db.models import Q
from .forms import ArticleSearchForm

class ArticleSearchMixin:
    def get_queryset(self):
        queryset = super().get_queryset()
        self.form = ArticleSearchForm(self.request.GET)

        if self.form.is_valid():
            search_term = self.form.cleaned_data.get('search_term')
            if search_term:
                queryset = queryset.filter(
                    Q(title__icontains=search_term) | Q(content__icontains=search_term)
                )
        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['search_form'] = getattr(self, 'form', ArticleSearchForm()) # Используется стандартная форма, если формы нет
        return context

Пример views.py (с применением Mixin)

from django.views.generic import ListView
from .models import Article
from .mixins import ArticleSearchMixin

class ArticleListView(ArticleSearchMixin, ListView):
    model = Article
    template_name = 'articles/article_list.html'
    context_object_name = 'articles'
    paginate_by = 10

    def get_queryset(self):
        queryset = super().get_queryset()
        ordering = self.request.GET.get('ordering', '-created_at')
        queryset = queryset.order_by(ordering)
        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['ordering'] = self.request.GET.get('ordering', '-created_at')
        return context
  • ArticleSearchMixin создан для отделения логики поиска.
  • ArticleListView наследует как ArticleSearchMixin, так и ListView, объединяя функции поиска и представления списков.

6. Преимущества и ограничения CBV (Завершение серии)

В этой серии из 8 выпусков мы рассмотрели причины перехода от функций-основ представления (FBV) к классовым основам представления (CBV), основные структуры и методы использования CBV, использование различных обобщенных представлений и расширение функций с помощью Mixin.

Основные преимущества CBV:

  • Переиспользование кода: С помощью обобщенных представлений и Mixin можно сократить количество повторяющегося кода и добиться более эффективной разработки.
  • Структурированный код: Код, основанный на классах, четко разделяет и упрощает управление логикой.
  • Расширяемость: Наследование и использование Mixin позволяют легко расширять существующие функции и добавлять новые возможности.
  • Поддерживаемость: Код модульный, что облегчает его поддержку и минимизирует влияние изменений на весь код.
  • Увеличение производительности разработки: Использование различных обобщенных представлений и Mixin, предоставляемых Django, позволяет быстро разрабатывать приложения.

Ограничения и аспекты, которые следует учитывать в CBV:

  • Кривая обучения на начальном уровне: Понимание структуры классов может потребоваться для изучения CBV по сравнению с FBV.
  • Чрезмерное наследование: Использование слишком большого количества Mixin может сделать код трудным для понимания. Важно использовать Mixin в разумных пределах.
  • Перегрузка при простых логиках: Для очень простых страниц или функций FBV могут быть более лаконичными. Нужно выбирать подходящий способ представления в зависимости от ситуации.

В заключение, CBV Django является мощным и полезным инструментом для разработки веб-приложений, требующих сложных и различных функций. Понимание и правильное использование преимуществ CBV позволит вам писать более эффективный и удобный для поддержки код.

Спасибо, что читали серию исследований классовых основ представлений (CBV)! Надеюсь, эта серия помогла вам понять CBV Django и применить его в реальных проектах.


Посмотрите предыдущие статьи

  1. Классовая основа представлений (CBV) Исследовательская серия #1 – Причины перехода от FBV к CBV и подход разработчика
  2. Классовая основа представлений (CBV) Исследовательская серия #2 – Понимание базовых классов представлений Django
  3. Классовая основа представлений (CBV) Исследовательская серия #3 – Упрощенная обработка форм с помощью FormView
  4. Классовая основа представлений (CBV) Исследовательская серия #4 – Использование ListView и DetailView
  5. Классовая основа представлений (CBV) Исследовательская серия #5 – Реализация CRUD с помощью CreateView, UpdateView и DeleteView
  6. Классовая основа представлений (CBV) Исследовательская серия #6 – Использование TemplateView и RedirectView
  7. Классовая основа представлений (CBV) Исследовательская серия #7 – Использование Mixin и управление правами

“Расширите функции списков, чтобы сделать их более полными и удобными для пользователя, и
повышайте уровень своих навыков разработки Django, овладевая CBV!”