Улучшаем дизайн блога с использованием Bootstrap 5
Зачем Bootstrap
Писать CSS с нуля долго, а результат часто выглядит «самодельно». Bootstrap — самый популярный CSS-фреймворк: сетка, готовые компоненты (навигация, карточки, кнопки, формы, модальные окна), адаптивность для телефонов. Пятая версия не требует jQuery.
Подключение
Самый быстрый способ — CDN в base.html:
{% load static blog_tags %}
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{% endblock %} · Мой блог</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="{% static 'css/blog.css' %}" rel="stylesheet">
</head>
<body class="bg-light">
...
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
meta viewport обязателен для корректного отображения на телефонах. Свой blog.css подключаем после Bootstrap — чтобы переопределять его стили.
Навигация
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
<div class="container">
<a class="navbar-brand" href="{% url 'blog:post_list' %}">Мой блог</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#nav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="nav">
<ul class="navbar-nav me-auto">
<li class="nav-item"><a class="nav-link" href="{% url 'blog:post_list' %}">Посты</a></li>
<li class="nav-item"><a class="nav-link" href="{% url 'blog:post_search' %}">Поиск</a></li>
</ul>
<ul class="navbar-nav">
{% if user.is_authenticated %}
<li class="nav-item"><a class="nav-link" href="{% url 'dashboard' %}">{{ user.username }}</a></li>
<li class="nav-item">
<form method="post" action="{% url 'logout' %}">{% csrf_token %}
<button class="btn btn-link nav-link">Выйти</button>
</form>
</li>
{% else %}
<li class="nav-item"><a class="nav-link" href="{% url 'login' %}">Войти</a></li>
<li class="nav-item"><a class="btn btn-primary ms-2" href="{% url 'register' %}">Регистрация</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>
На узких экранах меню сворачивается в «бургер» — за это отвечает JS-бандл Bootstrap.
Сетка
<div class="container">
<div class="row">
<main class="col-lg-8">
{% block content %}{% endblock %}
</main>
<aside class="col-lg-4">
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Последние посты</h5>
{% show_latest_posts 3 %}
</div>
</div>
</aside>
</div>
</div>
Строка делится на 12 колонок. col-lg-8 — 8 из 12 на больших экранах; на телефонах колонки встают друг под другом.
Карточки постов
{% for post in posts %}
<article class="card mb-4 shadow-sm">
<div class="card-body">
<h2 class="card-title h4"><a href="{{ post.get_absolute_url }}" class="text-decoration-none">{{ post.title }}</a></h2>
<p class="text-muted small">{{ post.publish|date:"j E Y" }} · {{ post.author }}</p>
<div class="card-text">{{ post.body|markdown|truncatewords_html:40 }}</div>
{% for tag in post.tags.all %}
<a href="{% url 'blog:post_list_by_tag' tag.slug %}" class="badge text-bg-secondary text-decoration-none">{{ tag.name }}</a>
{% endfor %}
</div>
</article>
{% endfor %}
Формы
Вариант 1 — классы в виджетах:
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ["name", "email", "body"]
widgets = {
"name": forms.TextInput(attrs={"class": "form-control"}),
"email": forms.EmailInput(attrs={"class": "form-control"}),
"body": forms.Textarea(attrs={"class": "form-control", "rows": 4}),
}
Вариант 2 — для всех полей сразу:
class BootstrapFormMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs.setdefault("class", "form-control")
Вариант 3 — пакет django-crispy-forms:
pip install django-crispy-forms crispy-bootstrap5
INSTALLED_APPS += ["crispy_forms", "crispy_bootstrap5"]
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5"
{% load crispy_forms_tags %}
<form method="post">{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary">Отправить</button>
</form>
Сообщения
Теги уровней Django (success, error) почти совпадают с классами Bootstrap. Поправим error → danger:
from django.contrib.messages import constants as messages
MESSAGE_TAGS = {messages.ERROR: "danger"}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
Пагинация
<nav>
<ul class="pagination">
{% if page.has_previous %}
<li class="page-item"><a class="page-link" href="?page={{ page.previous_page_number }}">«</a></li>
{% endif %}
{% for num in page.paginator.page_range %}
<li class="page-item {% if num == page.number %}active{% endif %}">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endfor %}
{% if page.has_next %}
<li class="page-item"><a class="page-link" href="?page={{ page.next_page_number }}">»</a></li>
{% endif %}
</ul>
</nav>
Картинки
<img src="{{ user.profile.photo.url }}" class="img-fluid rounded-circle" alt="">
img-fluid — картинка не шире родителя, rounded-circle — круглая аватарка.
Итог
- Bootstrap подключается двумя строками (CSS и JS-бандл), плюс
meta viewport. - Сетка:
container→row→col-*(12 колонок). - Компоненты:
navbar,card,badge,alert,pagination. - Поля форм стилизуются через
attrs, миксин илиdjango-crispy-forms.
Практика решено 0 из 5
Подключите в <head> CSS Bootstrap 5 с CDN jsDelivr (версия 5.3.3):
https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css.
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
Сетка Bootstrap делит строку на сколько колонок?
Сверстайте двухколоночный макет: контейнер container, строка row, основная колонка col-lg-8
с блоком {% block content %}{% endblock %} и боковая col-lg-4 с тегом {% show_latest_posts 3 %}.
<div class="container">
<div class="row">
<main class="col-lg-8">
{% block content %}{% endblock %}
</main>
<aside class="col-lg-4">
{% show_latest_posts 3 %}
</aside>
</div>
</div>
Как добавить CSS-классы Bootstrap к полям Django-формы?
Какой класс Bootstrap делает картинку адаптивной (не шире родителя)?
Войдите, чтобы отмечать пройденные уроки.