Files
2018-lpon-site/lpon_site/frontend/views.py
T

108 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#
from dataclasses import dataclass
from django.shortcuts import render
from django.http import HttpRequest, HttpResponse
# Create your views here.
@dataclass(frozen=True)
class BreadcrumbItem:
"""
Один пункт хлебных крошек (например, "Главная", "Каталог", "Винил").
ЧТО ТАКОЕ dataclass (если видите это в первый раз)
----------------------------------------------------
`@dataclass` — это декоратор из стандартной библиотеки Python (модуль `dataclasses`).
Он берёт класс, в котором просто перечислены имена полей и их типы (без ручного
__init__), и САМ генерирует за вас служебные методы:
- __init__(self, title, url=None) — конструктор класса. Благодаря нему можно
писать BreadcrumbItem(title="Каталог", url="/catalog") без единой строчки
кода конструктора;
- __repr__ — красивое текстовое представление для print()/логов, например:
BreadcrumbItem(title='Каталог', url='/catalog');
- __eq__ — сравнение двух объектов по значениям полей, то есть
BreadcrumbItem('Каталог', '/catalog') == BreadcrumbItem('Каталог', '/catalog')
вернёт True (без dataclass пришлось бы сравнивать id() объектов).
Без dataclass пришлось бы писать руками:
class BreadcrumbItem:
def __init__(self, title, url=None):
self.title = title
self.url = url
С dataclass — просто пишем поля (см. ниже) и всё перечисленное выше Python
сгенерирует сам. Это НЕ меняет то, как объект используется — просто экономит
код и снижает риск ошибок при ручном написании __init__/__repr__/__eq__.
Чем dataclass отличается от NamedTuple (typing.NamedTuple)?
---------------------------------------------------------------
Обе конструкции решают одну и ту же задачу — описать "структуру из нескольких
полей" без лишнего кода, но по-разному устроены внутри:
- `NamedTuple` — это, по сути, обычный tuple с именами полей. Он ВСЕГДА
неизменяем (immutable), поддерживает распаковку как обычный tuple
(title, url = item) и итерацию (for value in item);
- `dataclass` — это обычный класс. По умолчанию он изменяем (можно
присвоить item.title = "..." в любой момент), но можно сделать
неизменяемым через параметр frozen=True (как сделано ниже). В отличие
от NamedTuple, dataclass НЕ поддерживает распаковку как tuple, зато его
проще расширять методами, наследованием и сложными полями по умолчанию
(списками, словарями и т.п.).
Для хлебных крошек подошёл бы любой из двух вариантов. Здесь выбран dataclass
с frozen=True — то есть объект ведёт себя как неизменяемый: попытка сделать
`item.title = "другое значение"` после создания вызовет исключение. Это
логично: один и тот же пункт крошек не должен "мутировать" по ходу рендеринга
страницы.
КОНТРАКТ С ШАБЛОНОМ (важно!):
-------------------------------
Шаблон lpon_site/templates/block/breadcrumbs.html ожидает контекстную
переменную `breadcrumbs` — список объектов именно с такими двумя полями:
Атрибуты:
title (str):
Видимый текст пункта, например "Каталог", "Винил".
url (str | None):
Ссылка на пункт. Если None (значение по умолчанию) — пункт
считается ТЕКУЩЕЙ страницей: шаблон покажет его БЕЗ ссылки, с
атрибутом aria-current="page". Обычно url=None указывают только
у ПОСЛЕДНЕГО пункта в списке крошек.
ВАЖНО: пункт "Главная" в этот список включать НЕ нужно — ссылку на
главную страницу (в виде иконки домика) шаблон breadcrumbs.html
добавляет сам, одинаково для всех страниц. Список крошек должен
начинаться сразу со следующего уровня (например, с "Каталог").
Пример использования — см. функцию catalog() ниже.
"""
title: str
url: str | None = None
def index(request: HttpRequest | None) -> HttpResponse:
return render(request, 'index.html', {})
def catalog(request: HttpRequest | None) -> HttpResponse:
"""
Страница каталога (пока черновик вёрстки, см. lpon_site/templates/catalog.html).
ТЕСТОВЫЕ ДАННЫЕ ДЛЯ ХЛЕБНЫХ КРОШЕК:
В реальной вьюхе список крошек будет собираться динамически (в зависимости
от применённых фильтров, выбранной категории и т.п.). Пока каталог — черновик,
здесь захардкожен простой пример из двух пунктов, чтобы продемонстрировать
работу block/breadcrumbs.html: "Каталог" — обычный пункт со ссылкой, а
"Компакт-кассеты" — текущая страница (url не передан, поэтому он покажется без
ссылки). Пункт "Главная" передавать не нужно — его в виде иконки домика
сам добавляет шаблон breadcrumbs.html.
"""
breadcrumbs = [
BreadcrumbItem(title="Каталог", url="/catalog"),
BreadcrumbItem(title="Компакт-кассеты", url="/catalog/compact-cassettes"), # url передан -> обычный пункт со ссылкой
BreadcrumbItem(title="Для перезаписи" ), # url передан -> обычный пункт со ссылкой
]
return render(request, 'catalog.html', {"breadcrumbs": breadcrumbs})