В качестве хобби, я решил открыть для себя мир веб-разработки, попутно освещая свои успехи, а может и неудачи на пути к просветлению…

В настоящее время я публично описываю процесс создания веб-сайта с нуля на Django и Bootstrap, где задача разработать такие приложения как: аккаунты, вопросы, статьи и книги.

В проекте решаются только реальные задачи, которые почему-то так любят обходить стороной в учебной литературе.

Создание веб-сайта с нуля на Django и Bootstrap. Базовый шаблон

Сергей Серов

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

Шаблоны в Django это не просто набор статических данных, но и возможность использовать динамику через специальный синтаксис.

Настройки

Добавляем следующие параметры в файл settings.py:

SITE_AUTHOR = 'Имя и фамилия'
SITE_TITLE = SITE_AUTHOR
SITE_SUBTITLE = 'Ликбез по охране труда'

Заданные настройки найдут применение не только в базовом шаблоне, но и, например, в выводе ленты последних публикаций.

Контекстный процессор

Для передачи параметров в шаблон необходимо создать файл в каталоге core c названием context_processors.py со следующим содержимым:

from django.conf import settings


def site(request):
    return {
        'SITE_AUTHOR': settings.SITE_AUTHOR,
        'SITE_TITLE': settings.SITE_TITLE,
        'SITE_SUBTITLE': settings.SITE_SUBTITLE,
    }

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

Затем собираем настройки в словарь, где ключ словаря — это название которое будет использоваться в шаблоне, а значением выступает заданный параметр.

В файл настроек settings.py добавляем созданный процессор в список контекстных процессоров шаблонов:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'core.context_processors.site',
            ],
        },
    },
]

Шаблон

Переходим в каталог templates проекта и создаем файл base.html со следующей начинкой:

{% load static %}
<!DOCTYPE html>
<html lang="ru">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <meta name="author" content="{{ SITE_AUTHOR }}">

        <title>{% block title %}{% endblock %} | {{ SITE_TITLE }}</title>

        <!-- Bootstrap -->
        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
        <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">

        <!-- Style -->
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
        <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700%7CUbuntu:700&amp;subset=cyrillic" rel="stylesheet">
        <link rel="stylesheet" href="{% static 'style.css' %}">

        <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
        <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
        <!--[if lt IE 9]>
            <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
            <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
        <![endif]-->

        <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
        <!-- Include all compiled plugins (below), or include individual files as needed -->
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
    </head>
    <body>
        <header>
            <div class="container">
                <div class="about">
                    <div class="row">
                        <div class="col-sm-8">
                            <h2 class="title"><a href="/">{{ SITE_TITLE }}</a></h2>
                            <h3 class="subtitle">{{ SITE_SUBTITLE }}</h3>
                        </div>

                        <div class="col-sm-4">
                            <address class="contacts">
                                <div class="mail">
                                    <span class="sr-only">Адрес электронной почты</span>
                                    <i class="fa fa-envelope-o fa-fw" aria-hidden="true"></i>
                                    Почта
                                </div>

                                <div class="location">
                                    <span class="sr-only">Местоположение</span>
                                    <i class="fa fa-globe fa-fw" aria-hidden="true"></i>
                                    Местоположение
                                </div>
                            </address>
                        </div>
                    </div>

                    <div class="description">
                        <p>
                            В течение десяти лет работы на крупных производственных предприятиях заполучил опыт создания служб с нуля,
                            по направлениям: охрана труда, пожарная, промышленная и экологическая безопасность.
                        </p>

                        <p>
                            Проведу для Вас полный аудит или разработаю комплекты документов по всем вышеперечисленным направлениям.
                        </p>
                    </div>
                </div>
            </div>
        </header>

        <main>
            <div class="container">
                {% block main %}{% endblock %}
            </div>
        </main>
    </body>
</html>

В самом начале шаблона необходимо загрузить зависимости. Мы импортируем файл с шаблонными тегами стандартного приложения staticfiles.

Тег static генерирует URL по заданному относительному пути каталога статики. Это очень удобно, например, в настройках проекта можно сменить URL и перенести статические файлы на отдельный сервер без каких-либо изменений маршрутов в шаблонах.

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

И самое главное здесь это блоки title и main, которые мы будем переопределять в дочерних шаблонах.

Например, в шаблоне логина пользователя, в заголовке мы укажем "Вход", а в основном блоке — HTML форму для проведения процедуры авторизации.

Все остальное это вполне стандартный код HTML с классами от фреймворка Bootstrap.

Пожалуй, здесь можно выделить "колоночную верстку" которая будет приятно смотреться и мониторах, и на маленьких устройствах одновременно.

И класс sr-only. Скрывает содержимое тега, но призван помочь пользователям, которые используют программы для чтения с экрана, произнести эту метку вслух.

Стили

В базовом шаблоне мы уже подключили таблицу стилей, теперь нам необходимо ее создать в директории static проекта — файл style.css следующего вида:

body {
    font-family: 'Open Sans', sans-serif;
    font-size: 15px;
    line-height: 160%;
    color: black;
}

h1, h2, h3, h4, h5, h6 {
    font-family: 'Ubuntu', sans-serif;
    text-transform: uppercase;
    font-weight: bold;
}

.container {
    max-width: 750px;
}

header .about .title {
    font-size: 28px;
    margin: 0;
}

header .about .title a {
    color: black;
}

header .about .subtitle {
    margin-top: 0;
    font-family: 'Open Sans', sans-serif;
    font-size: 18px;
    font-weight: normal;
}

header .about .contacts {
    text-align: right;
    font-size: 18px;
    margin-top: 5px;
    line-height: 130%;
}

header .about .contacts .location {
    margin-right: 3px;
}

header .about .description {
    margin-top: 15px;
}

main {
    margin-top: 10px;
    margin-bottom: 40px;
}

main h1 {
    font-size: 22px;
    margin-bottom: 30px;
}

main a {
    font-weight: bold;
}

main form .errorlist {
    list-style-type: none;
    padding-left: 5px;
}

main form button {
    margin-top: 10px;
}

main blockquote {
    font-size: 14px;
    border-color: #e6e6e6;
    background-color: #f7f7f7;
}

main .django-ckeditor-widget {
    width: 100%;
}

main .accounts-login .links {
    margin-top: 25px;
}

main .questions-index .question .title {
    font-size: 18px;
    margin-top: 3px;
}

main .questions-index .pagination {
    margin-top: 25px;
    margin-bottom: 5px;
}

main .questions-index .your-question h2 {
    font-size: 20px;
    margin-top: 40px;
    margin-bottom: 20px;
}

main .questions-question .title {
    margin-bottom: -30px;
}

main .questions-question .article {
    margin-top: 50px;
}

main .questions-question .answer {
    color: darkgreen;
}

main .questions-question .article .expert {
    margin-right: 5px;
}

main .questions-question .article .author {
    font-weight: bold;
    margin-bottom: 5px;
}

main .questions-question .article .created {
    color: #666666;
    margin-right: 5px;
}

main .questions-question .article .text {
    margin-top: 20px;
}

main .questions-question .your-answer h2 {
    font-size: 20px;
    margin-top: 50px;
    margin-bottom: 20px;
}

main .questions-question .your-answer form {
    margin-top: -25px;
}

main .questions-question .your-answer label {
    visibility: hidden;
}

main .articles-index .article .title {
    font-size: 18px;
    margin-top: 3px;
}

main .articles-article .title {
    margin-bottom: 5px;
}

main .articles-article .published {
    color: #666666;
    margin-right: 5px;
}

main .articles-article .text {
    margin-top: 20px;
}

main .articles-article .text h2 {
    font-size: 20px;
    margin-top: 25px;
    margin-bottom: 15px;
}

main .articles-article .text h3 {
    font-size: 18px;
    margin-top: 25px;
    margin-bottom: 15px;
}

main .articles-article .text ul {
    margin-bottom: 20px;
}

main .articles-article .text p {
    margin-bottom: 15px;
}


/* Маленькие устройства (телефоны) (<768px) */
@media (max-width: 767px) {
    header .about .contacts {
        margin-top: 25px;
        text-align: left;
    }

    header .about .description {
        margin-top: 10px;
    }

    main {
        margin-bottom: 20px;
    }
}