Django 5 для начинающих

Прогресс по курсу:  9/1004

7.6 Улучшаем дизайн блога с использованием Bootstrap 5
7 из 7 шагов пройдено

Изменение базового шаблона проекта

По мере работы со стилями Bootstrap вам может очень пригодится данная шпаргалка.

Чтобы подключить Bootstrap 5 стили CSS, дополнительный JS-код в нашем проекте нам необходимо изменить базовый шаблон base.html и добавить в него следующий код внутри тега <head>:

{# Load the django_bootstrap5 library #}
{% load django_bootstrap5 %}

{# Load CSS and JavaScript separately#}
{% bootstrap_css %}
{% bootstrap_javascript %}

И в результате давайте приведем наш base.html к следующему виду:

{% load blog_tags %}
{% load static %}
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}{% endblock %}</title>
    {% load django_bootstrap5 %}
    {% bootstrap_css %}
    {% bootstrap_javascript %}

</head>
<body class="d-flex flex-column min-vh-100">
<!-- Responsive navbar-->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container">
        <a class="navbar-brand" href="/">My First Blog Django 5</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
                aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span
                class="navbar-toggler-icon"></span></button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                {% if user.is_authenticated %}
                    <li class="nav-item"><a class="nav-link" href="{% url 'users-profile' %}"><img
                            class="rounded-circle" width="30" src="{{ user.profile.avatar.url }}"/> {{ user.username }}
                    </a></li>
                    <li class="nav-item">
                        <form action="{% url 'logout' %}" method="post">{% csrf_token %}
                            <a href="#" class="nav-link" onclick="parentNode.submit();">Log Out</a>
                        </form>
                    </li>
                {% else %}
                    <li class="nav-item"><a class="nav-link" href="{% url 'login' %}">Log In</a></li>
                {% endif %}
            </ul>
        </div>
    </div>
</nav>
<!-- Page content-->
<div class="container mt-5">
    <div class="row">
        <div class="col-lg-8">
            {% if messages %}
                {% for message in messages %}
                    <div class="container d-flex align-items-center justify-content-center">
                        <div class="alert alert-warning alert-dismissible fade show w-50  d-block" role="alert">
                            {{ message }}
                            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
                        </div>
                    </div>
                {% endfor %}
            {% endif %}

            {% block content %}
            {% endblock %}
        </div>
        <!-- Widgets-->
        <div class="col-lg-4">
            <div class="card mb-4">
                <div class="card-header">This is my blog.</div>
                <div class="card-body">
                    I've written {% total_posts %} posts so far.
                </div>
            </div>
            <!-- Search widget-->
            <div class="card mb-4">
                <div class="card-header">Search</div>
                <div class="card-body">
                    <div class="input-group">
                        <form action="/search/" method="get" class="input-group">
                            <input type="text" name="query" class="form-control" placeholder="Enter search term..."
                                   required="" id="id_query">
                            <button class="btn btn-primary" id="button-search" type="submit">Go!</button>
                        </form>
                    </div>
                </div>
            </div>
            <!-- Latest posts widget-->
            <div class="card mb-4">
                <div class="list-group">
                    <a class="list-group-item list-group-item-action active" aria-current="true">
                        Latest posts
                    </a>
                    {% show_latest_posts 3 %}
                </div>
            </div>
            <div class="card mb-4">
                <div class="list-group">
                    <a class="list-group-item list-group-item-action active" aria-current="true">
                        Most commented posts
                    </a>
                    {% get_most_commented_posts as most_commented_posts %}
                    {% for post in most_commented_posts %}
                        <a class="list-group-item list-group-item-action"
                           href="{{ post.get_absolute_url }}">{{ post.title }}</a>
                    {% endfor %}
                </div>
            </div>
        </div>
    </div>
</div>
<!-- Footer-->
<footer class="py-5 bg-dark mt-2 mt-auto">
    <div class="container"><p class="m-0 text-center text-white">Copyright &copy; Your Website 2023, Powered by
        Django</p></div>
</footer>
</body>
</html>

А теперь разберем все блоки кода по порядку, в самом верху кода мы подключили Bootstrap стили и скрипты, и далее мы добавили адаптивный блок навигации:

<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container">
        <a class="navbar-brand" href="/">My First Blog Django 4</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
                aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span
                class="navbar-toggler-icon"></span></button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                {% if user.is_authenticated %}
                    <li class="nav-item"><a class="nav-link" href="{% url 'users-profile' %}"><img
                            class="rounded-circle" width="30" src="{{ user.profile.avatar.url }}"/> {{ user.username }}
                    </a></li>
                    <li class="nav-item"><a class="nav-link" href="{% url 'logout' %}">Log Out</a></li>
                {% else %}
                    <li class="nav-item"><a class="nav-link" href="{% url 'login' %}">Log In</a></li>
                {% endif %}
            </ul>
        </div>
    </div>
</nav>

И теперь мы имеем вот такой вид нашей шапки сайта:

И для мобильных устройств:

В следующем блоке кода мы размещаем наш основной контент сайта. Также мы сделали обработку ошибок с помощью всплывающего модального окна:

<div class="container mt-5">
    <div class="row">
        <div class="col-lg-8">
            {% if messages %}
                {% for message in messages %}
                    <div class="container d-flex align-items-center justify-content-center">
                        <div class="alert alert-warning alert-dismissible fade show w-50  d-block" role="alert">
                            {{ message }}
                            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
                        </div>
                    </div>
                {% endfor %}
            {% endif %}
            {% block content %}
            {% endblock %}
        </div>

Также мы полностью переработали наш сайдбар. А именно мы добавили в него поиск по нашему блогу и добавили новый дизайн:

<!-- Widgets-->
        <div class="col-lg-4">
            <div class="card mb-4">
                <div class="card-header">This is my blog.</div>
                <div class="card-body">
                    I've written {% total_posts %} posts so far.
                </div>
            </div>
            <!-- Search widget-->
            <div class="card mb-4">
                <div class="card-header">Search</div>
                <div class="card-body">
                    <div class="input-group">
                        <form action="/search/" method="get" class="input-group">
                            <input type="text" name="query" class="form-control" placeholder="Enter search term..."
                                   required="" id="id_query">
                            <button class="btn btn-primary" id="button-search" type="submit">Go!</button>
                        </form>
                    </div>
                </div>
            </div>
            <!-- Latest posts widget-->
            <div class="card mb-4">
                <div class="list-group">
                    <a class="list-group-item list-group-item-action active" aria-current="true">
                        Latest posts
                    </a>
                    {% show_latest_posts 3 %}
                </div>
            </div>
            <div class="card mb-4">
                <div class="list-group">
                    <a class="list-group-item list-group-item-action active" aria-current="true">
                        Most commented posts
                    </a>
                    {% get_most_commented_posts as most_commented_posts %}
                    {% for post in most_commented_posts %}
                        <a class="list-group-item list-group-item-action"
                           href="{{ post.get_absolute_url }}">{{ post.title }}</a>
                    {% endfor %}
                </div>
            </div>
        </div>

И в самый низ страницы мы добавили небольшой простой футер:

<footer class="py-5 bg-dark mt-2">
    <div class="container"><p class="m-0 text-center text-white">Copyright &copy; Your Website 2023, Powered by
        Django</p></div>
</footer>

Далее давайте откроем наш latest_posts.html и изменим в нем код на следующий:

    {% for post in latest_posts %}
            <a class="list-group-item list-group-item-action" href="{{ post.get_absolute_url }}">{{ post.title }}</a>
    {% endfor %}

В данном коде, мы добавили к ссылкам необходимые классы стилей CSS.

Давайте проверим наш дизайн сайта:

Как мы видим, мы кардинально переработали макет нашего сайта. Продолжим доработку дизайна в следующем шаге.


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

Да, прикольно, довольно удобно и симпатично.

По-быстрому с помощью шпаргалки переделал постраничную навигацию в такое вот: 

pagination.html

<nav aria-label="Page navigation">
    <ul class="pagination">
        {% if page.has_previous %}
        <li class="page-item"><a class="page-link" href="?page={{ page.previous_page_number }}">Previous</a></li>
        {% else %}
        <li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
        {% endif %}

        {% for p_n in page.paginator.get_elided_page_range %}
            {% if p_n == page.number %}
        <li class="page-item active" aria-current="page"><a class="page-link" href="?page={{ p_n }}">{{ p_n }}</a></li>
            {% elif p_n == '…' %}
        <li class="page-item disabled" ><a class="page-link" href="#">{{ p_n }}</a></li>
            {% else %}
        <li class="page-item"><a class="page-link" href="?page={{ p_n }}">{{ p_n }}</a></li>
            {% endif %}
        {% endfor %}

        {% if page.has_next %}
        <li class="page-item"><a class="page-link" href="?page={{ page.next_page_number }}">Next</a></li>
        {% else %}
        <li class="page-item disabled"><a class="page-link" href="#">Next</a></li>
        {% endif %}
    </ul>
</nav>

При большом количестве страниц корректно делит их с помощью '…', установил пагинацию по 1 странице и добавил еще несколько статей, после добавления 11-й произошло разбиение:

Но при переходе на страницу 5 текущая перестает отображаться. Для корректной работы нужно передать методу Paginator.get_elided_page_range() номер текущей страницы, но я пока не понял, как это сделать в шаблоне. Так не работает, нужно разбираться:

{% for p_n in page.paginator.get_elided_page_range page.number %}

Пришлось добавить ключ 'paging' в контекст, и передать в него созданную во view post_list переменную paging:

 paging = paginator.get_elided_page_range(number=page_number, on_each_side=1, on_ends=1)
    return render(request, 'blog/post/list.html',
                  {'posts': posts, 'tag': tag, 'page_obj': paginator.get_page(page_number), 'paging': paging})

В шаблоне соответственно ее использовать:

{% for p_n in paging %}

Теперь отображается корректно (уменьшил все значения до 1, чтобы быстрее происходило "схлопывание"):

Изменен ilya kutaev

Ну блин, совсем другое дело))) Хоть на сайт стало похоже =)

@Александр_Павлов, это точно, как же все таки стиль влияет на ситуацию))

Такой вопрос, как лучше всего учиться работать с Bootstrap/CSS? Понятно, что все сразу выучить и запомнить невозможно. Есть ли какие-то рекомендации в данной случае?

Изменен Кислинский Роман

@Кислинский_Роман, Вообще сам Bootstrap не сложно понять, можно почитать документацию - https://bootstrap-5.ru/docs/5.3/getting-started/introduction/ Но я считаю что если никогда не работал с Bootstrap шаблонами, будет не совсем понятно. 

Для него существует довольно много примеров - https://getbootstrap.com/docs/5.0/examples/ Можно брать эти примеры, и смотреть уже в документации какие классы стилей что делают и пробовать их изменять, добавляя отступы, выравнивания и тд.

Также удобно пользоваться шпаргалками, типа этой https://getbootstrap.su/docs/5.0/examples/cheatsheet/ или https://cheatsheet.getbootstrap.su/

@Илья_Перминов, Спасибо, будем дерзать)

Подскажите, пожалуйста, почему у меня тэги выглядят так:

Изменен Valentin Sak

@Valentin_Sak, проверьте удалили ли вы строчку с базовым css(<link href="{% static "css/blog.css" %}" rel="stylesheet">) в base.html.

@Дмитрий_Селезнев, единственный "css" в base.html - {% bootstrap_css %}. Когда возникла ошибка весь base.html скопировал из проекта по итогам данного модуля (https://github.com/Permin0ff/Course_mysite_03/).

@Valentin_Sak, попробуйте выделить теги на странице и посмотреть исходный код выделенного фрагмента, должно быть так:

            Tags:
            
                <a class="badge bg-secondary text-decoration-none link-light" href="/tag/markdown/">
                    markdown
                </a>

@Valentin_Sak, попробуйте заменить всю папку шаблонов, папкой шаблонов с гитхаба, будет ли так работать?

@Дмитрий_Селезнев, Тег выглядел так:

<a class="badge bg-secondary text-decoration-none link-light" href="/tag/markdown/">
                    markdown
                </a>. 

Но заменил папку templates на папку с гитхаба и все ок.

Осталось только понять, где я накосячил :)

Спасибо!

Осталось только понять, где я накосячил :)

@Valentin_Sak, это можно выяснить сравнением файлов с помощью команды fc в командной строке.
fc templates\blog\post\list.html old_templates\blog\post\list.html > diff.log

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

@Дмитрий_Селезнев, Тоже столкнулся с этой проблемой, оказалось в следующем шаге надо убрать тег:

@Марат_Асылбаев, спасибо, поправил.

Latest posts как-то съехали вправо не понятно как, а как это настроить я честно говоря не понимаю(

@Maxim_Lapshin, проверьте код боковой панели(сайдбара), начиная с комментария <!-- Latest posts widget-->.

Изменен Дмитрий Селезнев

@Дмитрий_Селезнев, Спасибо) Не хватало:

<ul class="list-group">