Django для начинающих Научитесь веб-разработки с Django 2.0 William S. Vincent © 2018 William S. Vincent Группа в ВК "Django_Python" Содержание Введение Почему Django Почему эта книга Структура книги Макет книги Вывод 1 1 3 3 5 7 Глава 1: Первоначальная Настройка Командная строка Установка Python 3 на Mac OS X Установка Python 3 на Windows Установка Python 3 на Linux Установка Виртуальных Среды Джанго Установить Git Текстовой редактор Вывод 8 8 11 13 14 15 16 20 21 21 Глава 2: Hello World приложение Начальная настройка Создание приложения Views и URL конфигурация Привет мир! Git Bitbucket Вывод 22 22 25 27 30 31 33 37 Глава 3: Страницы приложения Начальная настройка Шаблоны Основа Классов Views URLs Добавление страницы 39 39 41 44 45 47 Группа в ВК "Django_Python" ОГЛАВЛЕНИЕ Расширение Шаблонов Тесты Git и Bitbucket Local vs Production Heroku(облачная PaaS-платформа) Дополнительный файл Deploy(развертывание) Вывод 49 52 54 56 57 58 61 64 Глава 4: Приложение Доска Объявлений Начальная настройка Создание модели базы данных Активация моделей Джанго Админ Views/Templates/URLs Добавление новых записей Тесты Bitbucket Heroku конфигурация Heroku развертывание Выводы 65 65 68 69 70 77 81 85 90 91 93 95 Глава 5: Приложение Блог Начальная настройка Модель базы данных Админ URLs Views Templates Static files Отдельные страницы блога Тесты Git Выводы 96 96 98 100 105 107 108 111 116 122 125 125 Глава 6: Формы Формы Обновление форм Удаление View Тесты Выводы 127 127 138 145 151 155 Глава 7: Учетные Записи Пользователей 156 Группа в ВК "Django_Python" ОГЛАВЛЕНИЕ Вход Обновление Домашней страницы Ссылка "выход" Регистрация Bitbucket Конфигурация Heroku Развертывание на Heroku Выводы 156 159 161 164 170 171 173 177 Глава 8: Пользовательская Модель Установка Пользовательская User Model Формы Суперпользователь Выводы 178 178 180 182 185 187 Глава 9: Аутентификация Пользователя Templates URLs Admin Выводы 188 188 192 197 203 Глава 10: Bootstrap Приложение Страницы Тесты Bootstrap Форма Регистрации Дальнейшие действия 204 204 208 211 219 224 Глава 11: смена и сброс пароля Изменение пароля Настройка изменения пароля Сброс пароля Пользовательские шаблоны Вывод 226 226 228 231 235 240 Глава 12: Электронная Почта SendGrid Пользовательская эл.почта Вывод 241 241 246 250 Глава 13: Приложение газета Приложение Статьи 251 251 Группа в ВК "Django_Python" ОГЛАВЛНИЕ URLs и Views Редактирование/Удаление Создание страницы Выводы 257 262 268 276 Глава 14: Права доступа и авторизация Улучшаем CreateView Авторизация Mixins Обновление views Вывод 277 277 279 281 283 285 Глава 15: Комментарии Model Admin Template Вывод 286 286 288 295 299 Вывод Ресурсы Джанго Питон книги Блоги Обратная связь 301 302 302 303 303 Группа в ВК "Django_Python" Введение Добро пожаловать в Django для начинающих, проектный подход к обучению веб-разработке с помощью веб-платформы Django. В этой книге вы создадите пять все более сложных вебприложений, начиная с простого приложения "Привет, мир", переходя к приложению блога с формами и учетными записями пользователей, и, наконец, приложение газеты, используя пользовательскую модель, интеграцию электронной почты, внешние ключи, авторизацию, права доступа и многое другое. К концу этой книги вы должны чувствовать себя уверенно, создавая свои собственные проекты Django с нуля, используя лучшую современную практику. Django-это бесплатный веб-фреймворк с открытым исходным кодом, написанный на языке программирования Python и используемый миллионами программистов каждый год. Его популярность обусловлена дружелюбием как к начинающим, так и продвинутым программистам: Django достаточно надежен для использования крупнейшими веб–сайтами в мире – Instagram, Pinterest, Bitbucket, Disqus, но также достаточно гибок, чтобы быть хорошим выбором для стартапов на ранней стадии и прототипирования личных проектов. Эта книга регулярно обновляется и содержит последние версии Django (2.0) и Python (3.6 x). Он также использует Pipenv, который теперь официально Рекомендуемый менеджер пакетов Python.org для управления пакетами Python и виртуальными средами. На всем протяжении мы будем использовать лучшие современные практики сообщества Django, Python и веб-разработки, особенно тщательное использование тестирования. Почему Django Веб-фреймворк - это набор модульных инструментов, которые абстрагируют большую часть трудностей и повторений, присущих веб-разработке. Например, большинству веб-сайтов Группа в ВК "Django_Python" 2 Introduction нужен такой же базовый функционал: возможность подключения к базе данных, установка URL маршрутов, отображение контента на странице, корректная защита и так далее. Вместо того, чтобы воссоздавать все это с нуля, программисты на протяжении многих лет создавали веб-фреймворки на всех основных языках программирования: Django и Flask в Python, Rails в Ruby и Express в JavaScript среди многих, многих других. Django унаследовал подход Python “batteries-included” и включает в себя поддержку из коробки для общих задач в веб-разработке: • Аутентификация пользователя • Шаблоны, маршруты и представления • Интерфейс администратора • Надежная безопасность • Поддержка нескольких серверных баз данных • И многое другое Такой подход значительно упрощает нашу работу как веб-разработчиков. Мы можем сосредоточиться на этом, что делает наше веб-приложение уникальным, а не изобретать колесо, когда дело доходит до стандартной функциональности веб-приложения. Напротив, несколько популярных фреймворков–в первую очередь Flask на Python и Express на JavaScript-используют подход “микропрограммирования”. Они предоставляют только минимум, необходимый для простой веб-страницы, и оставляют на усмотрение разработчика установку и настройку сторонних пакетов для репликации базовой функциональности веб-сайта. Такой подход обеспечивает большую гибкость для разработчика, но также дает больше возможностей для ошибок. По состоянию на 2018 Год Django находится в активной разработке более 13 лет, что делает его седым ветераном в программных годах. Миллионы программистов уже использовали Django для создания своих сайтов. И это, несомненно, хорошо. Веб-разработка трудна. Не имеет смысла повторять один и тот же код–и ошибки–когда, большое сообщество блестящих разработчиков уже решило эти проблемы для нас. Группа в ВК "Django_Python" 3 Introduction В то же время, Django остается в стадии активной разработки и имеет годовой график выпуска. Сообщество Django постоянно добавляет новые функции и улучшения безопасности. Если вы создаете сайт с нуля Джанго это отличный выбор. Почему эта книга Я написал эту книгу, не смотря на то, что Django очень хорошо документирован потому что существует серьезная нехватка доступных учебников для начинающих. Когда я впервые узнал Django несколько лет назад, я выбивался из сил пытаясь выполнять по официальному учебнику. По этому я так хорошо помню, что думал. С большим опытом сейчас я признаю, что авторы документации Django столкнулись с трудным выбором: они могли бы подчеркнуть простоту использования Django или его глубину, но не то и другое одновременно. Они выбирают последнее, и как профессиональный Разработчик я ценю этот выбор, но как новичок я нашел это как...разочарование! Моя цель состоит в том, что бы эта книга заполняла пробелы и демонстрировала насколько дружелюбен к новичкам Django. Вам не нужен прошлый опыт Python или веб-разработки для завершения этой книги. Она специально написан так, что даже новичок может следовать по ней и почувствовать магию написания собственных веб-приложений с нуля. Однако, если вы серьезно относитесь к карьере в веб-разработке, вам в конечном итоге нужно будет потратить время, чтобы изучить Python, HTML и CSS должным образом. В заключение приводится перечень рекомендуемых ресурсов для дальнейшего изучения. Структура Книги Мы начнем с правильного описания того, как настроить локальную среду разработки в главе 1. В этой книге мы используем кропотливые инструменты: самую последнюю версию Группа в ВК "Django_Python" 4 Introduction Django (2.0), Python (3.6) и Pipenv для управления нашими виртуальными средами. Мы также пройдем введение в командную строку и обсудим как работать с современным текстовым редактором. В Главе 2 мы создадим наш первый проект, минимальное Hello, World приложение, которое демонстрирует, как настроить новые проекты Django. Так как создание хорошего программного обеспечения важно, мы также сохраним нашу работу в git и загрузим копию в удаленный репозиторий кода на Bitbucket. В Главе 3 мы создадим, протестируем и развернем Приложение Pages, которое представляет шаблоны и view на основе классов. Шаблоны это то, как Django позволяет создавать (Не Повторяйтесь - принцип разработки программного обеспечения, нацеленный на снижение повторения информации различного рода) разработку с помощью HTML и CSS, в то время как view на основе классов требуют минимального количества кода для использования и расширения основной функциональности в Django. Они потрясающие, вы это скоро увидите. Мы также добавим наши первые тесты и развернем в Heroku, который имеет свободный уровень и который мы будем использовать на протяжении всей этой книги. Использование таких поставщиков услуг, как Heroku, превращает разработку из болезненного, трудоемкого процесса во что-то, что занимает всего несколько щелчков мыши. В Главе 4 мы создадим наш первый проект с базой данных, приложение для доски объявлений. Django предоставляет мощный ORM, который позволяет нам писать сжатый Python для наших таблиц базы данных. Мы рассмотрим встроенное приложение администратора, которое обеспечивает графический способ взаимодействия с нашими данными и может быть даже использовано в качестве системы управления контентом (CMS), аналогичной Wordpress. Также мы напишем тесты для всего нашего кода, сохраним удаленную копию на Bitbucket и развернем в Heroku. Наконец, в Главах 5-7 мы готовы к нашему финальному проекту: надежному приложению для блога, которое демонстрирует, как выполнять функциональность CRUD (Create-Read-Update-Delete)(Создание-ЧтениеОбновление-Удаление) в Django. Мы обнаружим, что общие view на основе классов Django означают, что для этого нам нужно написать лишь небольшое количество реального кода! Затем мы добавим формы и интегрируем встроенную систему аутентификации пользователей Django (login, logout, signup). В Главе 8 мы начнем сайт газеты и введем понятие пользовательских моделей, Django лучшая практика, которая редко рассматривается в учебных пособиях. Проще говоря все новые Группа в ВК "Django_Python" 5 Introduction проекты должны использовать пользовательскую модель, и в этой главе вы узнаете, как это сделать. Глава 9 охватывает аутентификацию пользователей, Глава 10 добавляет Bootstrap для стилизации, а главы 11-12 реализуют сброс пароля и изменение по электронной почте. В главах 13-15 мы добавляем статьи и комментарии к нашему проекту, а также соответствующие разрешения и полномочия. Мы даже узнаем некоторые приемы для настройки администратора, чтобы отобразить наши растущие данные. В заключении дается обзор основных понятий, введенных в книгу, и список рекомендуемых ресурсов для дальнейшего обучения. Хотя вы можете выбирать главы для чтения, структура книги очень продумана. Каждое приложение / глава вводит новую концепцию и усиливает прошлое обучение. Я настоятельно рекомендую прочитать его по порядку, даже если вы хотите пропустить перейдя вперед. Более поздние главы не будут охватывать предыдущий материал в той же глубине, что и предыдущие главы. К концу этой книги вы будете иметь представление о том, как работает Django, возможность создавать приложения самостоятельно, и основу необходимую, чтобы в полной мере воспользоваться дополнительными ресурсами для изучения промежуточных и передовых методов Django. Макет книги В этой книге есть много примеров кода, которые обозначаются следующим образом: Code # This is Python code print(Hello, World) Для краткости будем использовать точки ... для обозначения существующего кода, который остается неизменным, например, в функции, которую мы обновляем. Группа в ВК "Django_Python" 6 Introduction Code def make_my_website: ... print("All done!") Мы также часто будем использовать консоль командной строки (начиная с главы 1: Начальная настройка для выполнения команд, которые принимают форму префикса $ в традиционном стиле Unix. Command Line $ echo "hello, world" Результатом этой конкретной команды является следующая строка: Command Line "hello, world" Обычно мы объединяем команду и ее вывод. Команда всегда будет после символа $, а выходные данные-нет. Например, команда и результат выше будут представлены следующим образом: Command Line $ echo "hello, world" hello, world Полный исходный код всех примеров можно найти в официальном репозитории github. Группа в ВК "Django_Python" 7 Introduction Заключение Django-отличный выбор для любого разработчика, который хочет создавать современные, надежные веб-приложения с минимальным количеством кода. Он популярен, находится в стадии активной разработки и тщательно протестирован крупнейшими веб-сайтами в мире. В следующей главе мы узнаем, как настроить компьютер для разработки Django. Группа в ВК "Django_Python" Глава 1 : Начальная настройка В этой главе описывается, как правильно настроить компьютер для работы с Django проектом. Мы начнем с обзора командной строки и используем ее для установки последних версий Django (2.0) и Python (3.6 x). Затем мы обсудим виртуальные среды, git и работу с текстовым редактором. К концу этой главы вы будете готовы создавать и изменять новые проекты Джанго за несколько кликов. Командная строка Командная строка-это мощное текстовое представление компьютера. Как разработчики мы будем использовать его широко на протяжении всей этой книги, чтобы установить и настроить каждый проект Django. На Mac командная строка находится в программе Terminal, расположенной в каталоге / Applications / Utilities. Чтобы найти его, откройте новое окно Finder, откройте папку Applications, прокрутите вниз, чтобы открыть папку Utilities, и дважды щелкните приложение под названием Terminal. В Windows есть встроенная программа командной строки, но это трудно использовать. Я рекомендую вместо этого использовать Babun, бесплатную программу командной строки с открытым исходным кодом. На главной странице сайта Babun нажмите кнопку "Загрузить сейчас", дважды щелкните загруженный файл, чтобы установить Babun, и по завершении перетащите установщик в корзину. Чтобы использовать Babungo в меню Пуск, выберите программы, и нажмите на Babun. Забегая вперед, когда книга ссылается на” командную строку", это означает открыть новую консоль на вашем компьютере с помощью терминала или Babun. Группа в ВК "Django_Python" 9 Chapter 1: Initial Setup Хотя есть много возможных команд, которые мы можем использовать, на практике есть шесть наиболее часто используемых в разработке Django. • cd (изменить каталог) • cd .. (перейти в верх) • ls (список файлов в текущем каталоге) • pwd ( вывести текущий каталог) • mkdir (создать каталог) • touch (создать новый файл) Откройте командную строку и попробуйте их. Знак $ dollar-это наша командная строка: все команды в этой книге предназначены для ввода после $. Например, предположим, что вы находитесь на Mac, давайте перейдем в каталог рабочего стола. Command Line $ cd ~/Desktop Обратите внимание, что наше текущее местоположение / Desktop автоматически добавляется перед командной строкой. Чтобы убедиться, что мы находимся в правильном месте, мы можем использовать pwd, который распечатает путь к нашему текущему каталогу. Command Line ~/Desktop $ pwd /Users/wsv/desktop На моем компьютере Mac это показывает, что я использую пользователя wsv и нахожусь на рабочем столе для этой учетной записи. Давайте создадим новую папку каталога с mkdir, cd и добавим в нее новый файл index.html. Группа в ВК "Django_Python" 10 Chapter 1: Initial Setup Command Line ~/Desktop $ mkdir new_folder ~/Desktop $ cd new_folder ~/Desktop/new_folder $ touch index.html Теперь используйте ls для вывода списка всех текущих файлов в нашем каталоге. Вы увидите только что созданный index.html. Command Line ~/Desktop/new_folder $ ls index.html В качестве последнего шага вернитесь в каталог рабочего стола cd .. и используйте pwd для подтверждения местоположения. Command Line ~/Desktop/new_folder $ cd .. ~/Desktop $ pwd /Users/wsv/desktop Продвинутые разработчики могут с легкостью использовать клавиатуру и командную строку для навигации по компьютеру ; с практикой этот подход станет намного быстрее, чем с помощью мыши. В этой книге я дам вам точные инструкции для выполнения, вам не нужно быть экспертом в командной строке и со временем это будет хороший навык для любого профессионального разработчика программного обеспечения. Два хороших бесплатных ресурса для дальнейшего изучения-это Command Line Crash Course и CodeCademy’s Course on the Command Line. Группа в ВК "Django_Python" 11 Chapter 1: Initial Setup Ниже приведены инструкции для компьютеров Mac, Windows и Linux. Установка Python 3 на Mac OS X Хотя Python 2 установлен по умолчанию на компьютерах Mac, Python3 в нем нет. Вы можете подтвердить это, набрав python --version в консоли командной строки и нажав Enter: Command Line $ python --version Python . . Чтобы проверить, установлен ли Python 3, попробуйте выполнить ту же команду, используя python3 вместо python. Command Line $ python --version Если ваш компьютер выводит �.�.x ((любая версия 3.6 или выше), то у вас все в порядке, однако, скорее всего, вы увидите сообщение об ошибке, так как нам нужно установить Python 3 непосредственно. Наш первый шаг-установить пакет Xcode от Apple, поэтому выполните следующую команду, чтобы установить его: Группа в ВК "Django_Python" 12 Chapter 1: Initial Setup Command Line $ xcode-select --install Щелкните по всем командам подтверждения (Xcode - это большая программа, поэтому для установки может потребоваться некоторое время, в зависимости от вашего интернетсоединения). Затем установите менеджер пакетов Homebrew через длинную команду ниже: Command Line $ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/inst\ all/master/install)" Чтобы проверить правильность установки Homebrew, выполните следующую команду: Command Line $ brew doctor Your system is ready to brew. И что бы установить последнюю версию Python, выполните следующую команду: Command Line $ brew install python Сейчас мы проверим, какая версия была установлена: Группа в ВК "Django_Python" 13 Chapter 1: Initial Setup Command Line $ python Python --version . . Чтобы открыть интерактивную оболочку Python 3 (это позволит нам запускать команды Python прямо на нашем компьютере )просто введите python3 : Command Line $ python . . Python [GCC . . (default, Jan Compatible Apple LLVM , : : ) . . (clang- . . . )] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> Для выхода из интерактивной оболочки Python 3 в любое время нажмите Controld (клавиши” Control “и” d " одновременно). Вы все еще можете запускать оболочки Python с Python 2, просто набрав python: Command Line $ python Python [GCC . . . . (default, Dec , : : ) Compatible Apple LLVM . . (clang- . . . )] on darwin Type "help", "copyright", "credits" or "license" for more information. Установка Python 3 на Windows Python не включен по умолчанию в Windows, однако мы можем проверить, существует ли какая-либо версия в системе. Откройте консоль командной строки, введя cmd в меню Пуск (Поиск Windows)) Группа в ВК "Django_Python" 14 Chapter 1: Initial Setup Или вы можете удерживать клавишу SHIFT и щелкнуть правой кнопкой мыши на рабочем столе, а затем выбрать «Открыть командное окно» здесь или - "Открыть окно PowerShell здесь" в windows 10. Введите следующую команду и нажмите Enter: Command Line python --version Python . . Если вы видите такой вывод, Python уже установлен. Но скорее всего, этого не будет! Чтобы загрузить Python 3, перейдите в раздел загрузки официального сайта Python. Загрузите установщик и убедитесь, что вы выбрали опцию Add Python to PATH , которая позволит использовать python прямо из командной строки. В противном случае мы должны были бы ввести полный путь нашей системы и изменить наши переменные среды вручную. После установки Python выполните следующую команду в новой консоли командной строки: Command Line python --version Python . . Если это сработало, вы закончили! Установка Python 3 на Linux Добавление Python 3 в дистрибутив Linux требует немного больше работы. Здесь рекомендуются последние руководства для Centos и Debian. Если вам нужна Дополнительная помощь в добавлении Python к вашему пути, обратитесь к этому ответу StackOverflow. Группа в ВК "Django_Python" 15 Chapter 1: Initial Setup Виртуальная среда Виртуальные среды являются неотъемлемой частью программирования Python. Они представляют собой изолированный контейнер, содержащий все зависимости программного обеспечения для данного проекта. Это важно, потому что по умолчанию программное обеспечение, такое как Python и Django, установлено в одном каталоге. Это приводит к проблеме, когда требуется работать над несколькими проектами на одном компьютере. Что делать, если проект использует Django 2.0, но ProjectB с прошлого года все еще находится на Django 1.10? Без виртуальных сред это становится очень трудно; с виртуальными средами это вообще не проблема. Есть много областей разработки программного обеспечения, которые горячо обсуждаются, но использование виртуальных сред для разработки Python не является одним. Для каждого нового проекта Python следует использовать выделенную виртуальную среду. Исторически разработчики Python использовали virtualenv или pyenv для настройки виртуальных сред. Но в 2017 известных разработчиков Python KennethReitz Выпущено Pipenv который теперь официально Рекомендуемый инструмент упаковки питона. Pipenv похож на npm и yarn из экосистемы узлов: он создает файл канала, содержащий зависимости программного обеспечения и Pipfile. Блокировка для обеспечения детерминированных построений. “Детерминизм” означает, что каждый раз, когда вы загружаете программное обеспечение в новой виртуальной среде, у вас будет точно такой же конфигурации. Себастьян Маккензи, создатель Yarn, который впервые представил эту концепцию для упаковки JavaScript, имеет краткое сообщение в блоге, объясняющее, что такое детерминизм и почему это имеет значение. Конечным результатом является то, что мы создадим новую виртуальную среду с pipenv для каждого нового проекта Django. Для установки Pipenv мы можем использовать pip , который автоматически установился для нас вместе с Python 3. Группа в ВК "Django_Python" 16 Chapter 1: Initial Setup Command Line $ pip install pipenv Установка Django Чтобы увидеть Pipenv в действии, давайте создадим новый каталог и установим Django. Сначала перейдите на рабочий стол, создайте новый каталог django и введите его с cd. Command Line $ cd ~/Desktop $ mkdir django $ cd django Теперь используйте Pip env для установки Django. Command Line $ pipenv install django Если вы посмотрите в наш каталог, есть два новых файла: Popfile и Pipe-file.lock. У нас есть информация, необходимая для новой виртуальной среды, но мы ее еще не активировали. Давайте сделаем это с помощью оболочки pip env shell. Command Line $ pipenv shell Если вы находитесь на Mac, вы должны увидеть круглые скобки в командной строке с активированной средой. Он примет формат имени каталога и случайных символов. На моем компьютере я вижу нижеследующее, но вы увидите что-то немного другое: он начнется с django- но заканчится случайной серией символов. Группа в ВК "Django_Python" 17 Chapter 1: Initial Setup Обратите внимание, что из-за открытой ошибки пользователи Windows не будут видеть визуальную обратную связь виртуальной среды в это время. Но если вы сможете запустить Django-admin startproject в следующем разделе, то вы узнаете, что в вашей виртуальной среде установлен Django. Command Line (django-JmZ NTQw) $ Это означает, что все работает! Создайте новый проект Django с именем test с помощью следующей команды. Не забудьте поставить точку в конце через пробел. Command Line (django-JmZ NTQw) $ django-admin startproject test_project . Стоит остановиться здесь, чтобы объяснить, почему вы должны добавить точку через пробел к команде. Если вы просто запустите django-admin startproject test_project, то по умолчанию Django создаст эту структуру каталогов: └── test_project ├── manage.py └── test_project ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py Посмотрите, как он создает новый каталог test_project, а затем в нем manage.py файл и каталог test_project? Это кажется мне излишним, так как мы уже создали и перешли в папку django на нашем рабочем столе. Запустив django-admin startproject test_project . с точкой в конце-который говорит, установите в текущем каталоге Результат вместо этого будет: Группа в ВК "Django_Python" 18 Chapter 1: Initial Setup ├── manage.py └── test_project ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py Вывод заключается в том, что на самом деле не имеет большого значения, ставите ли вы точку или нет в конце команды, но я предпочитаю ставить ее, и именно так мы сделаем в этой книге. Теперь давайте проверим, что все работает, запустив локальный веб-сервер Django. Command Line (django-JmZ NTQw) $ python manage.py runserver Если вы перейдете в http://127.0.0.1:8000/ вы должны увидеть следующее изображение: Группа в ВК "Django_Python" 19 Chapter 1: Initial Setup Страница приветствия Django Чтобы остановить наш локальный сервер, введите Control-c. Затем выйдите из нашей виртуальной среды с помощью команды exit. Command Line (django-JmZ NTQw) $ exit Мы всегда можем снова активировать виртуальную среду с помощью pipenv shell в любое время. Мы получим много практики с виртуальными средами в этой книге, так что не волнуйтесь, если это Группа в ВК "Django_Python" 20 Chapter 1: Initial Setup немного сбивает вас с толку сейчас. Основной шаблон заключается в установке новых пакетов с pipenv, активируйте их с помощью pipenv shell, а затем выйти, когда закончите exit. Стоит отметить, что одновременно на вкладке командной строки может быть активна только одна виртуальная среда. В следующих главах мы будем создавать совершенно новую виртуальную среду для каждого нового проекта. Так что либо не забудьте выйти из текущей среды, либо откройте новую вкладку для новых проектов. Установка Git Git является неотъемлемой частью современной разработки программного обеспечения. Это система контроля версий, которая может рассматриваться как чрезвычайно мощная версия отслеживания изменений в Microsoft Word или Google Docs. С помощью git вы можете сотрудничать с другими разработчиками, отслеживать всю вашу работу с помощью фиксаций и возвращаться к любой предыдущей версии вашего кода, даже если вы случайно удалили что-то важное! На Mac, поскольку Homebrew уже установлен, мы можем просто ввести brew install git в командной строке: Command Line $ brew install git В Windows вы должны скачать Git из Git for Windows. Нажмите кнопку «Загрузить» и следуйте инструкциям по установке. Нажмите «Далее» на всех шагах, кроме пятого, “Adjusting your PATH environment.” Вместо этого выберите нижнюю опцию: “Use Git and optional Unix tools from the Windows Command Prompt.” После установки нам нужно выполнить одноразовую настройку системы, чтобы настроить ее, объявив имя и адрес электронной почты, которые вы хотите связать со всеми вашими обязательствами Git (подробнее об этом в ближайшее время). В консоли командной строки введите следующие две строки. Не забудьте обновить их имя и адрес электронной почты. Группа в ВК "Django_Python" 21 Chapter 1: Initial Setup Command Line $ git config --global user.name "Your Name" $ git config --global user.email "yourname@email.com" Вы всегда можете изменить эти настройки, если пожелаете, введя те же команды с новым именем или адресом электронной почты. Текстовой редактор Последний шаг это ваш текстовый редактор. В то время как командная строка, где мы выполняем команды для наших программ по сути текстовый редактор. Компьютеру все равно, какой текстовый редактор вы используете конечный результат просто код, но хороший текстовый редактор может предоставить полезные советы и поймать опечатки для вас. Опытные разработчики часто предпочитают использовать либо Vim, либо Emacs, оба десятилетних текстовых редактора с лояльными последователями. Однако каждый из них имеет крутую кривую обучения и требует запоминания множества различных комбинаций клавиш. Я не рекомендую их новичкам. Современные текстовые редакторы сочетают в себе те же мощные функции с привлекательным визуальным интерфейсом. Мой текущий фаворит это Visual Studio Code, который является бесплатным, простым в установке и пользуется широкой популярностью. Если вы еще не используете текстовый редактор, загрузите и установите Visual Studio Code. Вывод УФ! Никто на самом деле не любит настраивать локальную среду разработки, но, к счастью, это одноразовая боль. Теперь мы научились работать с виртуальными средами и установили последнюю версию Python и git. Все готово для вашего первого приложения на Django. Группа в ВК "Django_Python" Глава 2 : Hello World приложение В этой главе мы будем строить проект Django, который просто говорит: “Привет, мир” на главной странице. Это традиционный способ начать новый язык программирования или фреймворк. Мы также впервые будем работать с git и развертывать наш код в Bitbucket. Начальная настройка Для начала перейдите в новый каталог на компьютере. Например, мы можем создать папку helloworld на рабочем столе с помощью следующих команд. Command Line $ cd ~/Desktop $ mkdir helloworld $ cd helloworld Убедитесь, что вы еще не находитесь в существующей виртуальной среде и у вас отображается текст в скобках () перед знаком доллара $. Чтобы выйти из него, введите exit и нажмите ENTER. Скобки должны исчезнуть, что означает, что виртуальная среда больше не активна. Мы будем использовать pipenv для создания новой виртуальной среды, установить Django и затем активировать его. Группа в ВК "Django_Python" Chapter 2: Hello World app 23 Command Line $ pipenv install django $ pipenv shell Если Вы находитесь на Mac, вы должны увидеть скобки в начале командной строки в форме (helloworld-XXX) где XXX представляет случайные символы. На моем компьютере я вижу (helloworld-���ivvZC). Я покажу (helloworld) здесь в тексте, но вы увидите что-то немного другое на своем компьютере. Если Вы находитесь в Windows, Вы не увидите визуальную подсказку в это время. Создайте новый проект Django под названием helloworld_project включая пробел с точкой в конце команды, так что бы он установился в нашем текущем каталоге. Command Line (helloworld) $ django-admin startproject helloworld_project . Если сейчас вы используете команду tree вы можете увидеть, что наша структура проекта Django теперь выглядит так. (Примечание: если tree у вас не работает установите его с Homebrew:: brew install tree.) Command Line (helloworld) $ tree . ├── helloworld_project │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py Группа в ВК "Django_Python" Chapter 2: Hello World app directory, 24 files Файл settings.py управляет настройками нашего проекта, urls.py сообщает Django какие страницы для сборки выдать в ответ на запрос браузера или url, и wsgi.py который обозначает интерфейс шлюза веб-сервера и помогает Django обслуживать наши возможные веб-страницы. Последний файл manage.py используется для выполнения различных команд Django, таких как запуск локального веб-сервера или создание нового приложения. Django поставляется со встроенным веб-сервером для локальных целей разработки. Мы можем запустить его с помощью команды runerver. Command Line (helloworld) $ python manage.py runserver Если вы посетите http://127.0.0.1:8000/ вы должны увидеть следующее изображение: Группа в ВК "Django_Python" 25 Chapter 2: Hello World app Django страница приветствия Создание приложения Django использует концепцию проектов и приложений, чтобы сохранить код чистым и читаемым. Один проект Django содержит одно или несколько приложений, которые все вместе обеспечивают работу всего веб-приложения. Вот почему команда для нового проекта Django-startproject! Например, на реальном сайте электронной коммерции Django может быть одно приложение для проверки подлинности пользователя, другое приложение для платежей и третье приложение для получения сведений о списке элементов. Каждое фокусируется на изолированной части функциональности. Группа в ВК "Django_Python" Chapter 2: Hello World app 26 Нам нужно создать наше первое приложение, которое мы назовем pages. Из командной строки закройте сервер с помощью Control+c. Затем используйте команду startup. Command Line (helloworld) $ python manage.py startapp pages Если снова заглянуть в каталог дерева команд вы увидите, что Джанго создал страницы со следующими файлами: Command Line ├── pages │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ │ ├── models.py │ ├── tests.py │ └── views.py └── __init__.py Давайте рассмотрим, что делает каждый новый файл приложения pages: • admin.py файл конфигурации для встроенного приложения администратора Django • apps.py является конфигурационным файлом для самого приложения • migrations/ отслеживает любые изменения в файле models.py чтобы синхронизировать нашу базу данных с models.py • models.py тут мы определяем наши модели базы данных, которые Django автоматически переводит в таблицы базы данных • tests.py предназначен для тестирования приложений Группа в ВК "Django_Python" Chapter 2: Hello World app 27 • views.py тут мы обрабатываем логику запроса / ответа для нашего вебприложения Несмотря на то, что Наше новое приложение существует в проекте Django, Django не “знает” об этом, пока мы явно не добавим его. В текстовом редакторе откройте settings.py файл и прокрутите вниз до INSTALLED_APPS, где вы увидите шесть уже встроенных приложений Django. Добавьте наше новое приложение внизу: Code # helloworld_project/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'pages', # new ] Views и URLConfs В Django, Views определяет, какой контент выводится на данной странице, URLConfs определяет, где этот контент будет. Когда пользователь запрашивает определенную страницу, например домашнюю страницу, URLConf использует регулярное выражение для сопоставления этого запроса с соответствующей Views функцией , которая затем возвращает правильные данные. Другими словами, наша функция из Views выведет текст «Hello, World», в то время как наш URL будет гарантировать, что когда пользователь посещает главную страницу, он будет перенаправляется на правильную функцию Views. Начнем с обновления файла views.py в нашем pages приложении выглядит это следующим образом: Группа в ВК "Django_Python" Chapter 2: Hello World app 28 Code # pages/views.py from django.http import HttpResponse def homePageView(request): return HttpResponse('Hello, World!') В принципе, мы указываем, что всякий раз, когда вызывается функция homePageView вернется текст “Hello, World!” если более конкретно то мы импортировали встроенный HttpResponse метод, чтобы мы могли вернуть ответ объекта пользователю. Для этого мы создали функцию с именем homePageView которая принимает запрос объекта и возвращает ответ со строкой Hello, World!. Теперь нам нужно настроить наш URL. В приложении pages создайте новый urls.py файл. Command Line (helloworld) $ touch pages/urls.py Затем обновите его с помощью следующего кода: Группа в ВК "Django_Python" Chapter 2: Hello World app 29 Code # pages/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.homePageView, name='home') ] В верхней строке мы импортируем path из Django, чтобы использовать наш шаблон url, и на следующей строке мы импортируем наш файл views. Использованный пробел с точкой from . import views означает ссылку на текущий каталог, который является нашим приложением pages, содержащим как views.py, так и urls.py. Наш шаблон URL состоит из трех частей: • регулярное выражение Python для пустой строки '' • указывает функцию homePageView из файла views • добавляет не обязательное url имя'home' Другими словами, если пользователь запрашивает домашнюю страницу, представленную пустой строкой " , то используется функция файла views с именем homePageView. Мы уже почти закончили. Последний шаг это настройка на уровне проекта файла urls.py. Помните, что обычно в одном проекте Django есть несколько приложений, поэтому каждому из них нужен свой собственный маршрут. Обновите файл helloworld_project/urls.py следующим образом: Группа в ВК "Django_Python" Chapter 2: Hello World app 30 Code # helloworld_project/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('pages.urls')), ] Мы импортировали include во второй строке рядом с path, а затем создали новый шаблон url-адреса для нашего приложения pages. Теперь, когда пользователь посещает главную страницу ' ', он сначала будет перенаправлен на приложение pages, а затем на views домашней страницы. Это часто путает начинающих, то что нам здесь не нужно импортировать приложение pages, но мы ссылаемся на него в нашем шаблоне url как pages.urls. Причина, по которой мы делаем это, заключается в том, что метод django.urls.include () ожидает, что мы перейдем в модуль или приложение в качестве первого аргумента. Поэтому без использования include нам нужно будет импортировать наше приложение pages, но поскольку мы используем include, мы не должны это делать на уровне проекта! Hello, world! У нас есть весь код, который нам сейчас нужен ! Чтобы подтвердить, что все работает так, как и ожидалось, перезагрузите наш сервер Django: Группа в ВК "Django_Python" 31 Chapter 2: Hello World app Command Line (helloworld) $ python manage.py runserver Если вы обновите браузер для http://127.0.0.1:8000/ теперь он отображает текст “Hello, world!” Hello world homepage Git В предыдущей главе мы также установили git, который является системой контроля версий. Давайте используем его здесь. Первый шаг - инициализировать (или добавить) git в наш репозиторий. Command Line (helloworld) $ git init Если вы введете git status то увидите список изменений с момента последней фиксации(git commit). Поскольку это наша первая фиксация, это список всех наших изменений до сих пор. Группа в ВК "Django_Python" Chapter 2: Hello World app 32 Command Line (helloworld) $ git status On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) db.sqlite helloworld_project/ manage.py pages/ Pipfile Pipfile.lock ничего не добавлено к фиксации, но присутствуют неотслеживаемые файлы (используйте" git add " для отслеживания) Теперь мы хотим добавить все изменения с помощью команды add-A, а затем зафиксировать изменения вместе с сообщением, описывающим, что изменилось. Command Line (helloworld) $ git add -A (helloworld) $ git commit -m 'initial commit' Обратите внимание, что пользователи Windows могут получить сообщение об ошибке git commit error: pathspec ‘commit’ did not match any file(s) known to которая, повидимому, связана с использованием одинарных кавычек ' ' и отличается от двойных кавычек" ". Если вы видите эту ошибку, используйте двойные кавычки для всех сообщений фиксации в будущем. Группа в ВК "Django_Python" Chapter 2: Hello World app 33 Bitbucket Хорошей привычкой является создание удаленного хранилища кода для каждого проекта. Таким образом, у вас есть резервная копия на случай, если что-то случится с вашим компьютером, и, что более важно, она позволяет сотрудничать с другими разработчиками программного обеспечения. Два самых популярных варианта Bitbucket и Github. В этой книге мы будем использовать Bitbucket, потому что он позволяет частные репозитории бесплатно. Github взимает за это плату. Общедоступные репозитории доступны для всех пользователей интернета, а частные-нет. Когда вы изучаете вебразработку, лучше всего придерживаться частных репозиториев, чтобы случайно не опубликовать важную информацию, такую как пароли в интернете. Чтобы начать работу с Bitbucket, зарегистрируйте бесплатную учетную запись. После подтверждения аккаунта по электронной почте вы будете перенаправлены на страницу приветствия. Нажмите на ссылку “создать хранилище”. Группа в ВК "Django_Python" 34 Chapter 2: Hello World app Bitbucket страница приветствия Затем на странице” CreateRepo “введите имя вашего репозитория:”hello-world". Затем нажмите синюю кнопку “Create repository button”: Группа в ВК "Django_Python" 35 Chapter 2: Hello World app Bitbucket create repo На следующей странице внизу, нажмите на ссылку “ I have an existing project” в которой откроется выпадающий список: Группа в ВК "Django_Python" 36 Chapter 2: Hello World app Bitbucket существующий проект Мы уже в каталоге нашего repo, пропустим Шаг 1 (Step 1). В шаге 2(Step 2) мы используем две команды для добавления проекта в Bitbucket. Обратите внимание, что ваша команда будет отличаться от моей, так как у вас другое имя пользователя. Пример общего формата расположен ниже, где <USER> - Ваше имя пользователя Bitbucket. Command Line (helloworld) $ git remote add origin https://<USER>@bitbucket.org/<USER>/hello-w\ orld.git После выполнения этой команды для настройки git с этим репозиторием Bitbucket мы должны «вставить» наш код в него. Группа в ВК "Django_Python" 37 Chapter 2: Hello World app Command Line (helloworld) $ git push -u origin master Сейчас, если вы вернетесь на свою страницу Bitbucket и обновите ее, вы увидите, что код теперь находится в онлайн! Нажмите на вкладку “Source” слева, чтобы увидеть все это. Bitbucket обзор После этого выйдите из виртуальной среды с помощью команды exit. Command Line (helloworld) $ exit Сейчас в вашей командной строке не должно быть круглых скобок, это указывает на то, что виртуальная среда больше не активна. Вывод Поздравляю! Мы охватили много фундаментальных понятий в этой главе. Мы создали наше первое Приложение Django и узнали о структуре project/app (проект/приложение) в Django. Группа в ВК "Django_Python" Chapter 2: Hello World app 38 Мы начали изучать views, urls и внутренний веб-сервер. А так же мы работали с git, чтобы отслеживать наши изменения, и поместили наш код в частный репозиторий Bitbucket. Перейдем к главе 3: Простое приложение, где мы создадим и развернем более сложное приложение Django с использованием шаблонов(templates) и представлений(views) на основе классов. Группа в ВК "Django_Python" Глава �: Pages приложение В этой главе мы создадим, протестируем и развернем Приложение Pages с главной страницей home и страницей about. Мы также узнаем о классовых views и templates Django, которые являются строительными блоками для более сложных веб-приложений, построенных в книге позже . Начальная настройка Как и в главе 2: Hello World приложение, наша первоначальная настройка включает в себя следующие шаги: • создание нового каталога для нашего кода • установить Django в новой виртуальной среде • создание нового проекта Django • создание нового приложения pages • обновление(настройка) settings.py В командной строке, убедитесь, что вы не работаете в существующей виртуальной среде. Проверьте есть ли что-нибудь в скобках перед запросом командной строки. И если есть то просто наберите exit что бы выйти из него. Мы снова создадим новые страницы каталога для нашего проекта на рабочем столе, но вы можете поместить свой код в любом месте на вашем компьютере. Он просто должен быть в своем собственном каталоге. В новой консоли командной строки введите следующие команды: Группа в ВК "Django_Python" 40 Chapter 3: Pages app Command Line $ cd ~/Desktop $ mkdir pages $ cd pages $ pipenv install django $ pipenv shell (pages) $ django-admin startproject pages_project . (pages) $ python manage.py startapp pages Я использую здесь(pages) для представления виртуальной среды, но в реальности у меня выглядит так (pages-unOYeQ�e). Имя же вашей виртуальной среды будет уникальным и выглядеть будет примерно в таком формате (pages-XXX). Откройте текстовый редактор и перейдите к файлу settings.py. Добавьте приложение pages в наш проект в разделе INSTALLED_APPS: Code # pages_project/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'pages', # new ] Запустите локальный веб-сервер с помощью команды runserver. Группа в ВК "Django_Python" 41 Chapter 3: Pages app Command Line (pages) $ python manage.py runserver И затем перейдите на http://127.0.0.1:8000/. Django страница приветствия Templates(Шаблоны) Каждый веб-фреймворк нуждается в удобном способе создания HTML файлов. В Django, подход заключается в использовании шаблонов(templates), так что бы отдельные HTML файлы могли обслуживаться в view веб-страницы, указанным URL. Группа в ВК "Django_Python" 42 Chapter 3: Pages app Стоит повторить эту модель, так как вы увидите ее снова и снова в Django разработке : Templates, Views и URLs. Порядок, в котором вы их создаете, не имеет большого значения, поскольку все три необходимы и работают в тесном сотрудничестве. URLs управляют начальным маршрутом, точкой входа на страницу, например /about, views содержат логику, а шаблон имеет HTML. Для веб-страниц, использующих модели базы данных, views делает большую часть работы по определению какие данные доступны в шаблоне. И так: Templates, Views, URLs. Эта модель будет справедлива для каждой веб-страницы Django которую вы делаете. Однако потребуется некоторая практика, прежде чем вы усвоите это. Ок, двигаемся дальше. Вопрос о том, где разместить каталог шаблонов(templates), может смутить новичков. По умолчанию Django ищет шаблоны в каждом приложении. Наше pages приложение будет ожидать шаблон home.html который расположен в следующей директории: └── pages ├── templates ├── pages ├── home.html Это означает, что нам нужно будет создать новый каталог шаблонов(templates), новый каталог с именем приложения, pages, и наконец, сам наш шаблон home.html Общий вопрос: почему эта повторяющаяся структура? Короткий ответ заключается в том, что загрузчик шаблонов Django хочет быть действительно уверен, что он найдет правильный шаблон, и именно так он запрограммирован на их поиск. К счастью, есть еще один часто используемый подход построения шаблонов Djangoпроекта. Вместо этого необходимо создать единый каталог шаблонов на уровне проекта, доступный для всех приложений. Это тот подход, который мы и будем использовать. Группа в ВК "Django_Python" 43 Chapter 3: Pages app Сделав небольшую настройку в файле settings.py, мы можем указать Django также искать в папке на уровне проекта для шаблонов. Сначала остановите наш сервер с помощью Control-c. Затем создайте папку на уровне проекта с именем templates и HTML файл с именем home.html. Command Line (pages) $ mkdir templates (pages) $ touch templates/home.html Далее нам нужно обновить settings.py и указать Django, чтобы он искал шаблоны на уровне проекта. Для этого изменим строку 'DIRS' под TEMPLATES. Code # pages_project/settings.py TEMPLATES = [ { ... 'DIRS': [os.path.join(BASE_DIR, 'templates')], ... }, ] Затем мы можем добавить простой заголовок к нашему home.html файлу. Группа в ВК "Django_Python" 44 Chapter 3: Pages app Code <!-- templates/home.html --> <h1>Homepage.</h1> Ок, наш шаблон готов! Следующий шаг это настройка url и views. Class-Based Views Ранние версии Django поставлялись только с функциональными views, но вскоре разработчики обнаружили что снова и снова повторяли одни и те же действия. Напишите view со списком всех объектов в модели. Напишите view, в котором отображается только один подробный элемент модели и так далее. Основанные на функциях view, были представлены, что бы абстрагировать эти закономерности и оптимизировать разработку общими моделями. Однако не было простого способа расширить или настроить их. В результате в Django появились универсальные view на основе классов, которые упрощают использование, а также расширяют, и охватывают распространенные варианты использования. Классы это фундаментальная часть Python, но полное обсуждение их выходит за рамки этой книги. Если Вы нуждаетесь в ведении или напоминании, я предлагаю рассмотреть официальные документы Python, у которых есть превосходное учебное руководство по классам и их использованию. В нашем view мы будем использовать встроенный TemplateView . Обновить pages/ views.py файл. Группа в ВК "Django_Python" 45 Chapter 3: Pages app Code # pages/views.py from django.views.generic import TemplateView class HomePageView(TemplateView): template_name = 'home.html' Обратите внимание, что мы капитализировали наш view, так как теперь это класс Python. Классы, в отличие от функций, всегда должны быть капитализированы. TemplateView уже содержит всю логику, необходимую для отображения нашего шаблона, нам просто нужно указать имя шаблона. URLs Последним шагом мы обновим наш URLConfs. Вспомним из главы 2, Что нам нужно внести изменения в двух местах. Сначала мы обновляем на уровене проекта urls.py файл, чтобы указать на наше pages приложение, а затем в urls.py файле pages приложения мы сопоставим с маршрутами views . Давайте начнем с уровня проекта urls.py файл. Группа в ВК "Django_Python" 46 Chapter 3: Pages app Code # pages_project/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('pages.urls')), ] Давайте рассмотрим код на этом этапе. Мы добавили include во второй строке, чтобы указать существующий URL-адрес приложения pages. Далее создайте на уровне приложения urls.py файл. Command Line (pages) $ touch pages/urls.py И добавьте следующий код. Code # pages/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.HomePageView.as_view(), name='home'), ] Группа в ВК "Django_Python" 47 Chapter 3: Pages app Этот шаблон почти идентичен тому, что мы сделали в главе 2 с одним существенным отличием. При использовании Views на основе класса мы всегда добавляем as_view() в конце имени view. Мы закончили! Если сейчас вы запустите сервер командой python manage.py runserver и перейдете к http://127.0.0.1:8000/ вы увидете нашу новую домашнюю страницу. Домашняя страница Добавить About страницу Процесс добавления страницы очень похож на то, что мы только что сделали. Мы создадим новый файл шаблона, новый view и новый url. Закройте сервер с помощью Control-c и создайте новый шаблон с именем about.html. Command Line (pages) $ touch templates/about.html Затем введите в него короткий HTML заголовок. Группа в ВК "Django_Python" 48 Chapter 3: Pages app Code <!-- templates/about.html --> <h1>About page.</h1> Создайте новый view для страницы.(подразумевается создать новый класс или функцию в данном случае классAboutPageView) Code # pages/views.py from django.views.generic import TemplateView class HomePageView(TemplateView): template_name = 'home.html' class AboutPageView(TemplateView): template_name = 'about.html' А затем подключите его к url-адресу about/. Группа в ВК "Django_Python" 49 Chapter 3: Pages app Code # pages/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.HomePageView.as_view(), name='home'), path('about/', views.AboutPageView.as_view(), name='about'), ] Запустите веб сервер командой python manage.py runserver. Перейдите к http://127.0.0.1:8000/about и вы увидите нашу новую страницу “About page”. About page Расширение Шаблонов Реальная сила шаблонов заключается в их способности расширяться. Если вы сейчас вспомните то у большинства веб сайтов, которые повторяются на каждой странице (заголовок, нижний колонтитул, и т.д.). И было бы неплохо, если бы у нас, как разработчиков, могло быть одно каноническое место для нашего кода заголовка, который бы наследовался всеми другими шаблонами. Группа в ВК "Django_Python" 50 Chapter 3: Pages app И это возможно! Давайте создадим файл base.html содержащий заголовок с ссылками на две наши страницы. Сначала Control-c а затем введите следующую команду. Command Line (pages) $ touch templates/base.html Django имеет минимальный язык шаблонов для добавления ссылок и базовой логики в наши шаблоны. Вы можете увидеть полный список встроенных шаблонов здесь в официальной документации. Теги шаблонов имеют вот такую форму{% что-то %} где “чтото” - это сам тег шаблона. Вы даже можете создать свои собственные теги шаблонов, хотя мы не будем делать этого в этой книге. Чтобы добавить URL-ссылки в наш проект, мы можем использовать встроенный тег шаблона url, который принимает имя шаблона URL в качестве аргумента. Помните, как мы добавили необязательные имена URL в наши URL-маршруты? Вот для чего. Тег url автоматически использует эти имена для того чтобы создать соединения для нас. URL-Адрес нашей домашней страницы называется home, поэтому для настройки ссылки на нее мы будем использовать следующее: {% url 'home' %}. Code <!-- templates/base.html --> <header> <a href="{% url 'home' %}">Home</a> | <a href="{% url 'about' %}">About</a> </header> {% block content %} {% endblock %} В нижней части мы добавили тег блока content. Блоки могут быть перезаписаны дочерними шаблонами с помощью наследования. Группа в ВК "Django_Python" 51 Chapter 3: Pages app Давайте обновим наш home.html и about.html для расширения base.html шаблона. Это означает, что мы можем повторно использовать код из одного шаблона в другой шаблон. Язык шаблонов Django поставляется с методом extends, который мы можем использовать для этого. Code <!-- templates/home.html --> {% extends 'base.html' %} {% block content %} <h1>Homepage.</h1> {% endblock %} Code <!-- templates/about.html --> {% extends 'base.html' %} {% block content %} <h1>About page.</h1> {% endblock %} Запустите сервер командой python manage.py runserver и снова откройте наш сайт http://127.0.0.1:8000/ и http://127.0.0.1:8000/about вы увидите, что заголовок волшебным образом включен в обоих местах. Правда приятно? Группа в ВК "Django_Python" 52 Chapter 3: Pages app Домашняя страница с заголовком About страница с заголовком С шаблонами мы можем сделать гораздо больше, на практике мы обычно создаем base.html файл, а затем добавляем дополнительные шаблоны поверх него в надежном проекте Django. Мы сделаем это позже в книге. Tests Наконец мы подошли к тестам. Даже в этом базовом приложении важно добавлять тесты и иметь привычку всегда добавлять их в наши проекты Django. По словам Джейкоба КапланМосса, одиного из создателей Джанго, “код без тестов нарушен полностью.” Написание тестов важно, поскольку оно автоматизирует процесс подтверждения того, что код работает должным образом. В таком приложении, как это, мы можем вручную посмотреть и увидеть, что страница homepage и страница about page существуют и содержат предполагаемое содержание. Но как только Django Группа в ВК "Django_Python" 53 Chapter 3: Pages app проект начинает расти в размерах и в нем могут быть сотни, если не тысячи отдельных вебстраниц и идея вручную посетить каждую страницу становится невозможной. Далее, каждый раз, когда мы вносим изменения в код и добавляем новые функции, обновляем существующие, или удаляем неиспользуемые области сайта мы хотим быть уверены, что случайно не сломали какую-то другую часть сайта. Автоматизированные тесты позволяют нам написать один раз, как мы ожидаем будет работать определенная часть нашего проекта, а затем пусть компьютер делает проверку за нас. К счастью, Django поставляется с надежными, встроенными инструментами тестирования для написания и запуска тестов. Если вы посмотрите на наши страницы приложения, Django уже снабжен, tests.py файлом, который мы можем использовать. Откройте его и добавьте следующий код: Code # pages/tests.py from django.test import SimpleTestCase class SimpleTests(SimpleTestCase): def test_home_page_status_code(self): response = self.client.get('/') self.assertEqual(response.status_code, ) def test_about_page_status_code(self): response = self.client.get('/about/') self.assertEqual(response.status_code, ) Мы используем здесь SimpleTestCase поскольку мы не используем базу данных. Если бы мы использовали базу данных, мы бы использовали TestCase. Затем мы выполняем проверку состояния кода Группа в ВК "Django_Python" 54 Chapter 3: Pages app для 200 страниц, это стандартный ответ для успешного запроса HTTP. И это причудливый способ сказать, что гарантированно данная веб-страница реально существует, но ничего не говорит о содержании указанной страницы. Для запуска тестов закройте server Control-c и введите python manage.py test в командной строке: Command Line (pages) $ python manage.py test Creating test database for alias 'default'... .. ---------------------------------------------------------------------Ran tests in . s OK Destroying test database for alias 'default'... Получилось! В будущем мы сделаем гораздо больше с тестированием, особенно когда начнем работать с базами данных. На данный момент важно видеть, как легко добавлять тесты каждый раз, когда мы добавляем новую функциональность в наш Django проект. Git и Bitbucket Пришло время отслеживать наши изменения с git и выводить в Bitbucket. Начнем с инициализации каталога. Группа в ВК "Django_Python" 55 Chapter 3: Pages app Command Line (pages) $ git init Используйте git status, чтобы увидеть все наши изменения кода, а затем git add -A, чтобы добавить их все. Наконец, мы добавим наше первое сообщение о фиксации. Command Line (pages) $ git status (pages) $ git add -A (pages) $ git commit -m 'initial commit' На Bitbucket создайте новый репозиторий, который мы назовем pages-app. Bitbucket создание страницы На следующей странице нажмите на ссылку с низу “I have an existing project”. Скопируйте две команды для подключения, а затем нажмите repository to Bitbucket. Группа в ВК "Django_Python" 56 Chapter 3: Pages app Bitbucket Existing Project(существующий проект) Он должен выглядеть следующим образом, замените wsvincent вашим именем пользователя Bitbucket: Command Line (pages) $ git remote add origin git@bitbucket.org:wsvincent/pages-app.git (pages) $ git push -u origin master Local vs Production До этого момента мы использовали собственный внутренний веб-сервер Django для работы нашего приложения Pages на нашем локальном компьютере. Но Вы не можете поделиться адресом localhost с кем-то еще. Чтобы сделать наш сайт доступным в интернете, где его может видеть каждый, нам нужно развернуть наш код на внешнем сервере, что бы каждый мог просматривать наш сайт. Группа в ВК "Django_Python" 57 Chapter 3: Pages app Это называется вводом кода в производство (production). Локальный код работает только на нашем компьютере; производственный код на внешнем сервере. Есть много доступных поставщиков серверов, но мы будем использовать Heroku, потому что он бесплатен для небольших проектов, широко используется и имеет относительно простой процесс развертывания. Heroku Вы можете зарегистрировать бесплатный аккаунт на сайте Heroku и после того, как вы подтвердите свою электронную почту Heroku перенаправит вас в раздел панели инструментов сайта. Heroku панель инструментов Теперь нам необходимо установить Heroku’s Command Line Interface (CLI), так что мы можем развернуть его из командной строки. Нам нужно установить Heroku глобально, чтобы он был доступен на всем нашем компьютере, поэтому откройте новую вкладку командной строки: Command + t на Mac, Control+ t на Windows. Если бы мы установили Heroku в нашей виртуальной среде, она была бы доступна только там. В новой вкладке на Mac используйте Homebrew для установки Heroku: Группа в ВК "Django_Python" 58 Chapter 3: Pages app Command Line $ brew install heroku Для правильной установки 32-разрядной или 64-разрядной версии Windows см. страницу Heroku CLI. После завершения установки вы можете закрыть нашу новую вкладку командной строки и вернуться на начальную вкладку с активной виртуальной средой pages. Введите команду heroku login и использованную электронную почту и пароль которые вы указали при регистрации на Heroku. Command Line (pages) $ heroku login Enter your Heroku credentials: Email: will@wsvincent.com Password: ********************************* Logged in as will@wsvincent.com Дополнительный файл Нам необходимо внести следующие четыре изменения в наш проект Pages, чтобы он был готов к развертыванию онлайн на Heroku: • обновить Pipfile.lock • создать новый файл профиля (Procfile file) • установить gunicorn как наш веб-сервер • внести однострочное изменение в settings.py файл В существующем Pipfile укажите версию Python которую мы используем, то есть �.�. Добавьте эти две строки в конец файла. Группа в ВК "Django_Python" 59 Chapter 3: Pages app Code # Pipfile [requires] python_version = " . " Затем запустите pipenv lock для создания соответствующего файла Pipfile.lock. Command Line (pages) $ pipenv lock Фактически Heroku ищет в нашем Pipfile.lock информацию о нашей виртуальной среде, поэтому мы здесь добавляем настройки языка. Затем создайте Procfile который является специфическим для Heroku. Command Line (pages) $ touch Procfile Откройте файл Procfile в текстовом редакторе и добавьте следующее: Code web: gunicorn pages_project.wsgi --log-file - Это указывает на то что используется наш существующий файл pages_project.wsgi но с gunicorn, который является веб сервером и подходит для производства (production), вместо Django сервера который используется только для локальной разработки. Группа в ВК "Django_Python" 60 Chapter 3: Pages app Command Line (pages) $ pipenv install gunicorn Последний шаг это однострочное изменение в settings.py. Прокрутите вниз до раздела ALLOWED_HOSTS и добавьте '*', чтобы он выглядело как в примере ниже: Code # pages_project/settings.py ALLOWED_HOSTS = ['*'] Параметр ALLOWED_HOSTS представляет имена хостов/доменов, которые может обслуживать наш Django сайт. Это мера безопасности, предотвращающая атаки на HTTP Host header, которые возможны даже при многих, казалось бы, безопасных конфигурациях веб-сервера. Однако мы использовали подстановочный знак звездочку *, который означает, что все домены приемлемы, для простоты. На сайте Django уже на уровне производства(production) вы должны явно указать, какие домены разрешены. Используйте git status, чтобы проверить наши изменения, добавить новые файлы, а затем зафиксировать их: Command Line (pages) $ git status (pages) $ git add -A (pages) $ git commit -m "New updates for Heroku deployment" И наконец отправим на Bitbucket чтобы у нас была онлайн резервная копия наших изменений кода. Группа в ВК "Django_Python" 61 Chapter 3: Pages app Command Line (pages) $ git push -u origin master Deploy (Развертывание) Заключительный шаг фактически разворачивает на Heroku. Если вы в прошлом настраивали сервер самостоятельно, то вы будете поражены тем, насколько простой процесс с провайдером сервис платформы таким как Heroku. Наш процесс будет следующим: • Создадим новое приложение на Heroku и отправляем на него наш код. • Добавим удаленный git “hook” для Heroku • Настроим приложение, чтобы оно игнорировало статические файлы, которые мы рассмотрим в последующих главах. • Запустим сервер Heroku, чтобы приложение заработало. • Посетим приложение на Heroku с предоставленным URL Мы можем сделать первый шаг, создав новое приложение Heroku, из командной строки heroku create. Heroku создаст случайное имя для нашего приложения, в моем случае это cryptic-oasis-�����. у вас имя будет другим. Command Line (pages) $ heroku create Creating app... done, https://cryptic-oasiss- cryptic-oasis.herokuapp.com/ | https://git.heroku.com/cryptic-oasi\ .git Теперь нам нужно добавить “hook” для Heroku в git. Это означает, что git сохранит обе наши настройки для отправки кода на Bitbucket и Heroku. Мое Приложение Heroku называется cryptic-oasis-����� поэтому моя команда выглядит следующим образом. Группа в ВК "Django_Python" 62 Chapter 3: Pages app Command Line (pages) $ heroku git:remote -a cryptic-oasis- Вы должны заменить cryptic-oasis-40349 на имя приложения которое предоставил Heroku. На данный момент нам нужно сделать только одину настройку конфигурации Heroku, которая должна указать Heroku игнорировать статические файлы, такие как CSS и JavaScript, которые Django по умолчанию пытается оптимизировать для нас. Мы рассмотрим это в последующих главах, так что пока просто выполните следующую команду. Command Line (pages) $ heroku config:set DISABLE_COLLECTSTATIC= Теперь мы можем отправить наш код на Heroku. Потому что ранее мы установили наш “hook” и он уйдет на Heroku. Command Line (pages) $ git push heroku master Если мы просто наберем git push origin master, то код будет перенесен в Bitbucket, а не в Heroku. Добавив heroku к команде пошлет код на Heroku. Это немного сбивает с толку первое время. Наконец, нам необходимо сделать ваше приложение Heroku рабочим. Поскольку веб-сайты растут в трафике, им нужны дополнительные услуги Heroku, но для нашего основного примера мы можем использовать самый низкий уровень, web=�, который также является бесплатным! Введите следующую команду. Группа в ВК "Django_Python" 63 Chapter 3: Pages app Command Line (pages) $ heroku ps:scale web= Мы закончили! Последний шаг, это подтвердить что наше приложение работает в интернете. Если вы используете команду heroku open веб-браузер откроет новую вкладку с URL вашего приложения: Command Line (pages) $ heroku open У меня это https://cryptic-oasis-40349.herokuapp.com/about/. Вы сможете увидеть страницу homepage и страницу about page . Страница Homepage на Heroku Страница About page на Heroku Группа в ВК "Django_Python" 64 Chapter 3: Pages app Вывод Поздравляем с созданием и развертыванием второго проекта Django! На этот раз мы использовали шаблоны, классовые vews, более полно исследовали URLConfs, добавили базовые тесты и использовали Heroku. Далее мы перейдем к нашему первому проекту с базой данных и посмотрим, где действительно Django блистает. Группа в ВК "Django_Python" Глава �: Message Board app (Приложение доска объявлений) В этой главе мы будем использовать базу данных в первый раз, чтобы построить базовое приложение доска объявлений, где пользователи могут отправлять и читать короткие сообщения. Мы рассмотрим мощный встроенный интерфейс администратора Django, который обеспечивает визуальный способ внесения изменений в наши данные. И после добавления тестов мы будем отправлять наш код в Bitbucket и развернем приложение на Heroku. Django предоставляет встроенную поддержку для нескольких типов баз данных. Имея всего несколько строк в нашем файле settings.py, он может поддерживать PostgreSQL, MySQL, Oracle или SQLite. Но самым простым на сегодняшний день-является SQLite, потому что он работает с одним файлом и не требует сложной установки. В отличие от других, опции которых требуют, чтобы процессы работали в фоновом режиме и могут быть довольно сложными для настройки. Django использует SQLite по умолчанию и это идеальный выбор для небольших проектов. Начальная настройка Так как мы уже создали несколько проектов Django в этой книге, мы можем быстро выполнить наши команды, чтобы начать новый проект. Нам необходимо выполнить следующее: • создать новый каталог для нашего кода на рабочем столе под названием mb • установить Django в новой виртуальной среде • создать новый проект с именем mb_project • создать новое приложение posts • обновить settings.py Группа в ВК "Django_Python" Chapter 4: Message Board app 66 В новой консоли командной строки введите следующие команды. Обратите внимание, что я использую здесь (mb) для представления имени виртуальной среды, хотя на самом деле (mb-XXX), где XXX случайные символы. Command Line $ cd ~/Desktop $ mkdir mb $ cd mb $ pipenv install django $ pipenv shell (mb) $ django-admin startproject mb_project . (mb) $ python manage.py startapp posts Укажите Django о новом приложении posts, добавив его в нижнюю часть раздела INSTALLED_APPS вашего settings.py файла открыв его в текстовом редакторе. Code # mb_project/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'posts', # new ] Затем выполните команду migrate, чтобы создать исходную базу данных на основе настроек Django по умолчанию. Группа в ВК "Django_Python" Chapter 4: Message Board app 67 Command Line (mb) $ python manage.py migrate Если вы посмотрите в наш каталог с помощью команды ls, вы увидите, что у нас появился файл db.sqlite� представляющий нашу базу данных SQLite. Command Line (mb) $ ls db.sqlite mb_project manage.py Отступление: Технически файл db.sqlite� создается при первом запуске migrate или runserver. Использование runserver формирует базу данных, используя настройки Django по умолчанию, однако migrate будет синхронизировать базу данных с текущим состоянием любых моделей баз данных, находящихся в проекте и внесенных в INSTALLED_APPS. Другими словами, чтобы убедиться, что база данных отражает текущее состояние проекта, необходимо использовать migrate (а также makemigrations) при каждом обновлении модели. Более подробно об этом чуть позже. Чтобы убедиться, что все работает правильно, запустите наш локальный сервер. Command Line (mb) $ python manage.py runserver И перейдите к http://127.0.0.1:8000/ что бы увидеть хорошо знакомую страницу, Django установлен правильно. Группа в ВК "Django_Python" 68 Chapter 4: Message Board app Django welcome page Создание модели базы данных Наша первая задача создать модель базы данных, где мы можем хранить и отображать сообщения от наших пользователей. Для нас Django превратит эту модель в таблицу базы данных. В реальных проектах Django часто бывает так, что будет много сложных взаимосвязанных моделей баз данных, но в нашем простом приложении для доски объявлений нам нужна только одна. Я не буду рассматривать конструкцию базы данных в этой книге, но я написал краткое руководство, которое вы можете найти здесь, если это новое для вас. Группа в ВК "Django_Python" Chapter 4: Message Board app 69 Откройте файл posts/models.py и посмотрите на код по умолчанию, который предоставляет Django: Code # posts/models.py from django.db import models # Create your models here Django импортирует модуль models, чтобы помочь нам построить новые модели баз данных, которые будут “моделировать” характеристики данных в нашей базе данных. Мы хотим создать модель для хранения текстового содержимого сообщений в доске объявлений, которую мы можем выполнить следующим образом: Code # posts/models.py from django.db import models class Post(models.Model): text = models.TextField() Обратите внимание, что мы создали новую модель базы данных, которая имеет текстовое поле данных. Мы также определили тип контента, который он будет содержать, TextField (). Django предоставляет множество полей модели, поддерживающих общие типы контента, такие как символы, даты, целые числа, электронные письма и так далее. Активация моделей Теперь, когда наша новая модель создана, нам нужно ее активировать. В будущем, когда мы создаем или изменяем существующую модель необходимо обновлять Django в два этапа. Группа в ВК "Django_Python" 70 Chapter 4: Message Board app 1. Сначала мы создаем файл миграции с помощью команды makemigrations, которая генерирует команды SQL для предустановленных приложений в нашем параметре INSTALLED_APPS. Файлы миграции не выполняют эти команды в файле базы данных, а являются ссылкой на все новые изменения в наших моделях. Этот подход означает, что у нас есть запись изменений в наших моделях . 2. Во-вторых, мы создаем фактическую базу данных с помощью migrate, которая выполняет инструкции в нашем файле migrations. Command Line (mb) $ python manage.py makemigrations posts (mb) $ python manage.py migrate posts Обратите внимание, что вам не нужно включать имя после makemigrations или migrate. Если вы просто запустите команды, то они будут применены ко всем доступным изменениям. Это хорошая привычка, чтобы быть точным. Если бы у нас было два отдельных приложения в нашем проекте, и мы обновили модели в обоих, а затем запустили makemigrations, это создало бы файл миграции, содержащий данные об обоих изменениях. Это усложняет отладку в будущем. Вам потребуется, чтобы каждый файл миграции был как можно меньше и возможно отдельным. Таким образом, если вам нужно просмотреть прошлые миграции, есть только одно изменение на миграцию, а не одно, которое применяется к нескольким приложениям. Django Admin Django предоставляет нам мощный интерфейс администратора для взаимодействия с нашей базой данных. Это поистине потрясающий функционал, который предлагают немногие веб-фреймворки. Он имеет свои маршруты как в Django проекте newspaper(газета - в этой книге ниже). Разработчики хотели CMS (систему управления контентом), чтобы журналисты могли писать и редактировать свои истории без необходимости затрагивать "код.” Пстепенно встроенное приложение администратора превратилось в фантастический, из коробки инструмент для управления всеми аспектами проекта Django. Группа в ВК "Django_Python" Chapter 4: Message Board app 71 Чтобы использовать Django админ, нам сначала нужно создать суперпользователя, который может войти в систему. В консоли командной строки введите python manage.py createsuperuser и заполните ответы на запросы для имени пользователя, электронной почты и пароля: Command Line (mb) $ python manage.py createsuperuser Username (leave blank to use 'wsv'): wsv Email: Password: Password (again): Superuser created successfully. Примечание: При вводе пароля он не отображается в консоли командной строки по соображениям безопасности. Перезапустите сервер Django с помощью python manage.py runserver и в браузере перейдите к http://127.0.0.1:8000/admin/. Вы должны увидеть экран входа администратора: Группа в ВК "Django_Python" 72 Chapter 4: Message Board app Admin login page Войдите в систему, введя имя пользователя и пароль, которые вы только что создали. Далее вы увидите домашнюю страницу администратора Django: Группа в ВК "Django_Python" 73 Chapter 4: Message Board app Admin homepage Но где наше приложение posts ? Оно не отображается на главной странице администратора! Нам необходимо явно указать Django, что отображать в админке. К счастью, мы можем легко изменять это, открыв файл posts/admin.py и отредактировать его следующим образом: Группа в ВК "Django_Python" 74 Chapter 4: Message Board app Code # posts/admin.py from django.contrib import admin from .models import Post admin.site.register(Post) Django теперь знает, что он должен отображать наше posts приложение и его модель базы данных Post на странице администратора. Если вы обновите свой браузер, вы увидите, что теперь оно появилось: Admin homepage updated Теперь давайте создадим наш первый пост в доске объявлений для нашей базы данных. Нажмите на кнопку + Add напротив Posts. Введите свой текст в текстовое поле. Группа в ВК "Django_Python" 75 Chapter 4: Message Board app Admin new entry Затем нажмите кнопку "Save", которая перенаправит вас на главную страницу Post. Однако, если вы посмотрите внимательно, то есть проблема: Наша новая запись называется “Post object”, что не очень полезно. Группа в ВК "Django_Python" 76 Chapter 4: Message Board app Admin new entry Давайте изменим эту ситуацию. В файле posts/models.py добавьте новую функцию __str__ как в примере: Code # posts/models.py from django.db import models class Post(models.Model): text = models.TextField() def __str__(self): """Строковое отображение модели """ return self.text[:��] Группа в ВК "Django_Python" 77 Chapter 4: Message Board app Если вы обновите страницу администратора в браузере, вы увидите, что она изменена на гораздо более наглядное и полезное отображение нашей записи базы данных. Admin new entry Гораздо лучше! Рекомендуется добавлять методы str () ко всем моделям, чтобы улучшить их читаемость. Views/Templates/URLs Для того, чтобы отобразить содержимое нашей базы данных на нашей домашней странице, мы должны подключить наши views, templates и URLConfs. Сейчас этот образец должен начать казаться знакомым. Начнем с view. Ранее в книге мы использовали встроенный универсальный TemplateView для отображения файла шаблона на нашей домашней странице. Теперь мы хотим перечислить содержимое нашей модели базы данных. К счастью, это общая задача в вебразработке и Django поставляется с универсальным основным-классом ListView. В posts/views.py файле введите код Python ниже: Группа в ВК "Django_Python" Chapter 4: Message Board app 78 Code # posts/views.py from django.views.generic import ListView from .models import Post class HomePageView(ListView): model = Post template_name = 'home.html' В первой строке мы импортируем ListView, а во второй строке нам нужно явно определить, какую модель мы используем. В view мы создаем подкласс ListView, указываем название модели и указываем ссылку на шаблон. Внутри ListView возвращает объект с именем object_list, который мы хотим отобразить в нашем шаблоне (template). Наш view готов, что означает, что нам все еще нужно настроить наши URL-адреса и сделать наш шаблон(template). Начнем с шаблона. Создадим каталог(папку) на уровне проекта с именем templates и файл шаблона home.html. Command Line (mb) $ mkdir templates (mb) $ touch templates/home.html Затем обновите поле DIRS в settings.py файле, чтобы Django знал, что нужно искать в этой папке шаблонов. Группа в ВК "Django_Python" Chapter 4: Message Board app 79 Code # settings.py TEMPLATES = [ { ... 'DIRS': [os.path.join(BASE_DIR, 'templates')], ... }, ] В нашем файле шаблона home.html мы можем использовать Django Templating Language’s(Язык шаблонов Django) для цикла, чтобы перечислить все объекты в object_list. Помните, что object_list-это то, что ListView возвращает нам. Code <!-- templates/home.html --> <h1>Message board homepage</h1> <ul> {% for post in object_list %} <li>{{ post }}</li> {% endfor %} </ul> Последний шаг заключается в том, чтобы настроить наш URLConfs. Давайте начнем с файла urls.py на уровне проекта где мы просто подключаем include нашего posts и добавляем include во вторую строку. Группа в ВК "Django_Python" Chapter 4: Message Board app Code # mb_project/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('posts.urls')), ] Затем создайте файл urls.py на уровне приложения. Command Line (mb) $ touch posts/urls.py И обновите его таким образом: Code # posts/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.HomePageView.as_view(), name='home'), ] Перезапустите сервер с python manage.py runserver и перейдите на наш сайт http://127.0.0.1:8000/ в котором теперь перечислены наши сообщения на доске объявлений. Группа в ВК "Django_Python" 80 81 Chapter 4: Message Board app Homepage with posts В основном мы закончили, но давайте создадим еще несколько сообщений на доске объявлений в администраторе Django, чтобы убедиться, что они будут отображаться правильно на главной странице. Добавление новых записей Чтобы добавить новые сообщения в нашу доску объявлений, вернитесь в Admin: http://127.0.0.1:8000/admin/ И создайте еще два поста. Вот как выглядит мой: Группа в ВК "Django_Python" 82 Chapter 4: Message Board app Admin entry Группа в ВК "Django_Python" 83 Chapter 4: Message Board app Admin entry Группа в ВК "Django_Python" 84 Chapter 4: Message Board app Updated admin entries section Если вы вернетесь на домашнюю страницу, вы увидите, что она автоматически отображает наши отформатированные сообщения. Ура! Homepage with three entries Все работает так, что самое время инициализировать наш каталог, добавить новый код и Группа в ВК "Django_Python" Chapter 4: Message Board app 85 и включить наш первый git commit. Command Line (mb) $ git init (mb) $ git add -A (mb) $ git commit -m 'initial commit' Tests Раньше мы тестировали только статические страницы, поэтому мы использовали SimpleTestCase(простой тест). Но теперь, когда наша домашняя страница работает с базой данных, нам нужно использовать TestCase, которая позволит нам создать «тестовую» базу данных, которую мы можем проверить. Другими словами, нам не нужно запускать тесты в нашей реальной базе данных, но вместо этого можно создать отдельную тестовую базу данных, заполнить ее образцами данных, а затем протестировать ее. Начнем с добавления образца записи в текстовое поле базы данных, а затем проверим, правильно ли она хранится в базе данных. Важно, чтобы все наши методы тестирования начинались с test_, чтобы Django знал что тестировать! Код будет выглядеть следующим образом: Code # posts/tests.py from django.test import TestCase from .models import Post class PostModelTest(TestCase): def setUp(self): Post.objects.create(text='just a test') Группа в ВК "Django_Python" 86 Chapter 4: Message Board app def test_text_content(self): post=Post.objects.get(id= ) expected_object_name = f'{post.text}' self.assertEqual(expected_object_name, 'just a test') В верхней части мы импортируем модуль TestCase, который позволяет нам создать образец базы данных, а затем импортировать нашу Post модель. Мы создаем новый класс PostModelTest и добавляем метод setUp для создания новой базы данных, которая имеет только одну запись: сообщение с текстовым полем, содержащим строку 'just a test'. Затем мы запускаем наш первый тест, test_text_content, чтобы проверить, что поле базы данных на самом деле содержит только тест. Мы создаем переменную post, которая представляет первый идентификатор (id) в нашей модели Post. Запомните, что Django автоматически устанавливает этот id для нас. Если бы мы создали еще одну запись, у нее был бы идентификатор 2, следующий был бы 3 и так далее. Следующая строка использует f strings которая является очень классным дополнением к Python 3.6. Она позволяет помещать переменные непосредственно в строки, если переменные окружены фигурными скобками {}. Здесь мы устанавливаем expected_object_name как строку значения в post.text, который должен быть просто тест. В последней строке мы используем assertEqual, чтобы проверить, что наша недавно созданная запись действительно соответствует тому, что мы вводим наверху. Продолжайте и запустите тест в командной строке python manage.py test. Группа в ВК "Django_Python" 87 Chapter 4: Message Board app Command Line (mb) $ python manage.py test Creating test database for alias 'default'... System check identified no issues ( silenced). . ---------------------------------------------------------------------Ran test in . s OK Destroying test database for alias 'default'... Прошло! Не волнуйтесь, если предыдущее объяснение было похоже на информационную перегрузку. Это естественно, когда вы впервые начинаете писать тесты, но вскоре вы обнаружите, что большинство тестов, которые вы пишете, на самом деле довольно повторяющиеся. Время для нашего второго теста. Первый тест был для модели, но теперь мы хотим проверить нашу единственную страницу: домашнюю страницу (homepage). В частности, мы хотим проверить, что она существует (throws an HTTP 200 response), использует view home и использует home.html шаблон(template). Нам нужно добавить еще один импорт наверху для reverse и совершенно новый класс HomePageViewTest для нашего теста. Группа в ВК "Django_Python" 88 Chapter 4: Message Board app Code from django.test import TestCase from django.urls import reverse from .models import Post class PostModelTest(TestCase): def setUp(self): Post.objects.create(text='just a test') def test_text_content(self): post=Post.objects.get(id= ) expected_object_name = f'{post.text}' self.assertEqual(expected_object_name, 'just a test') class HomePageViewTest(TestCase): def setUp(self): Post.objects.create(text='this is another test') def test_view_url_exists_at_proper_location(self): resp = self.client.get('/') self.assertEqual(resp.status_code, ) def test_view_url_by_name(self): resp = self.client.get(reverse('home')) self.assertEqual(resp.status_code, ) Группа в ВК "Django_Python" 89 Chapter 4: Message Board app def test_view_uses_correct_template(self): resp = self.client.get(reverse('home')) self.assertEqual(resp.status_code, ) self.assertTemplateUsed(resp, 'home.html') Если вы запустите наши тесты снова, вы должны увидеть, что они проходят. Command Line (mb) $ python manage.py test Creating test database for alias 'default'... System check identified no issues ( silenced). . ---------------------------------------------------------------------Ran tests in . s OK Destroying test database for alias 'default'... Почему там написано четыре теста? Запомните, что наш setUp метод на самом деле не тест, он просто позволяет нам запускать последующие тесты. Наши четыре реальных теста это test_test_content, test_view_url_exists_at_proper_location, test_view_url_by_name, и test_view_-uses_correct_template. Любая функция, имеющая в начале слово test* в существующем файле tests.py будет запущена при выполнении команды python manage.py test. Мы закончили добавление кода для нашего тестирования, пришло время внести изменения в git. Группа в ВК "Django_Python" 90 Chapter 4: Message Board app Command Line (mb) $ git add -A (mb) $ git commit -m 'added tests' Bitbucket Нам также нужно сохранить наш код на Bitbucket. Это хорошая привычка, чтобы сохранить код в случае, если что-нибудь случится с вашим локальным компьютером, и это также позволяет вам делиться и сотрудничать с другими разработчиками. У Вас уже должна быть учетная запись Bitbucket из главы 3, поэтому создайте новый репозиторий, который мы назовем mb-app. Bitbucket create app На следующей странице нажмите на нижнюю ссылку “I have an existing project”. Скопируйте две команды для подключения, а затем нажмите repository to Bitbucket. Группа в ВК "Django_Python" Chapter 4: Message Board app 91 Это должно выглядеть так, замените wsvincent (мое имя пользователя)на ваш логин в системе bitbucket : Command Line (mb) $ git remote add origin git@bitbucket.org:wsvincent/mb-app.git (mb) $ git push -u origin master Heroku configuration Вы также должны уже иметь настройки учетной записи Heroku установленные из главы 3. Нам необходимо внести следующие изменения в наш проект доски объявлений, чтобы развернуть его в интернете: • обновить Pipfile.lock • новый Procfile • установить gunicorn • обновить settings.py В вашем Pipfile укажите версию Python мы используем �.�. Добавьте эти две строки в конец файла. Code # Pipfile [requires] python_version = " . " Запустите pipenv lock для создания соответствующего Pipfile.lock. Группа в ВК "Django_Python" Chapter 4: Message Board app Command Line (mb) $ pipenv lock Затем создайте Procfile, который указывает Heroku, как запустить удаленный сервер, где наш код будет работать. Command Line (mb) $ touch Procfile Теперь мы укажем Heroku использовать gunicorn в качестве нашего рабочего сервера и посмотреть в наш mb_project.wsgi файл для получения дальнейших инструкций. Command Line web: gunicorn mb_project.wsgi --log-file - Далее установим gunicorn который мы будем использовать для production но при этом используя внутренний сервер Django для локальной разработки. Command Line (mb) $ pipenv install gunicorn Наконец, обновите ALLOWED_HOSTS в нашем файле settings.py file. Code # mb_project/settings.py ALLOWED_HOSTS = ['*'] Все мы закончили! Добавьте и зафиксируйте наши новые изменения в git, а затем отправьте их в Bitbucket. Группа в ВК "Django_Python" 92 93 Chapter 4: Message Board app Command Line (mb) $ git status (mb) $ git add -A (mb) $ git commit -m 'New updates for Heroku deployment' (mb) $ git push -u origin master Heroku развертывание Убедитесь, что вы вошли в свою учетную запись Heroku. Command Line (mb) $ heroku login Затем запустите команду heroku create и Heroku случайным образом сгенерирует для вас имя приложения. Вы сможете настроить это позже, если будет необходимо. Command Line (mb) $ heroku create Creating app... done, https://agile-inlet- agile-inlet.herokuapp.com/ | https://git.heroku.com/agile-inlet- .git Настройте git на использование имени вашего нового приложения, когда вы отправляете код на Heroku. Мое сгенерированное имя Heroku это agile-inlet-����� поэтому команда выглядит следующим образом. Группа в ВК "Django_Python" \ 94 Chapter 4: Message Board app Command Line (mb) $ heroku git:remote -a agile-inlet- Укажите Heroku игнорировать статические файлы, которые мы подробно рассмотрим при развертывании нашего приложения блога в книге позже. Command Line (mb) $ heroku config:set DISABLE_COLLECTSTATIC= Отправьте код в Heroku и добавьте бесплатное масштабирование, чтобы он действительно работал в интернете, иначе код просто сидит там. Command Line (mb) $ git push heroku master (mb) $ heroku ps:scale web= Если вы откроете новый проект heroku open, он автоматически запустит новое окно браузера с URL вашего приложения. Мой находится по адресу: https://agile-inlet-25811.herokuapp.com/. Live site Группа в ВК "Django_Python" Chapter 4: Message Board app 95 Вывод На данный момент мы создали, протестировали и развернули наше первое приложение на основе базы данных. Хотя это намеренно довольно просто, теперь мы знаем, как создать модель базы данных, обновить ее с помощью панели администратора, а затем отобразить содержимое на веб-странице. Но чего-то не хватает, не так ли? В реальном мире пользователям нужны формы для взаимодействия с нашим сайтом. Ведь не у всех должен быть доступ к админ панели. В следующей главе мы создадим приложение блога, которое использует формы, чтобы пользователи могли создавать, редактировать и удалять записи. Мы также добавим стили CSS. Группа в ВК "Django_Python" ϏҵҪҬҪ�:Blogapp ҹҺҲҵҸҰүҷҲүύҵҸҭ ώ ӇҼҸҳ ҭҵҪҬү ҶӅ ҹҸһҼҺҸҲҶ ҹҺҲҵҸҰүҷҲү ҴҸҼҸҺҸү ҹҸұҬҸҵӉүҼ ҹҸҵӆұҸҬҪҼүҵӉҶ һҸұҮҪҬҪҼӆ ҺүҮҪҴҼҲҺҸҬҪҼӆ Ҳ ҽҮҪҵӉҼӆ һҸҸҫӃүҷҲӉ ϙҪ ҭҵҪҬҷҸҳ һҼҺҪҷҲӀү ҫҽҮҽҼ ҹүҺүӁҲһҵүҷӅ Ҭһү һҸҸҫӃүҷҲӉ Ҭ ҫҵҸҭү Ҳ ҮҵӉ ҴҪҰҮҸҭҸ ҸҼҮүҵӆҷҸҭҸ һҸҸҫӃүҷҲӉ ҫҽҮүҼ ҬӅҮүҵүҷҪ ҸҼҮүҵӆҷҪӉ һҼҺҪҷҲӀҪϘӅҼҪҴҰүҹҸұҷҪҴҸҶҲҶһӉһ$44ҮҵӉһҼҲҵҲұҪӀҲҲҲҽұҷҪүҶ ҴҪҴ%KBOHPҺҪҫҸҼҪүҼһҸ һҼҪҼҲӁүһҴҲҶҲҾҪҳҵҪҶҲ ϙҪӁҪҵӆҷҪӉҷҪһҼҺҸҳҴҪ Как описано в предыдущих главах, наши шаги для создания нового проекта Django будут выглядеть следующим образом: • Создать новую папку для нашего проекта на рабочем столе с именем blog • Установить Django в новой виртуальной среде • Создать новый проект Django с именем blog_project • Создать новое приложение blog • Выполнить миграцию для установки базы данных • Обновить settings.py Выполните следующие команды в новой консоли командной строки. Обратите внимание, что фактическое имя виртуальной среды будет (blog-XXX), где XXX представляет случайные символы. Я использую здесь (blog) , чтобы было проще, так как мое имя будет отличаться от вашего. И не забудьте указать пробел с точкой ". " в конце команды для создания нашего нового blog_project. (прм. переводчика. Создает проект Django в существующей директории без создания новой папки, не обязательно) Группа в ВК "Django_Python" 97 Chapter 5: Blog app Command Line $ cd ~/Desktop $ mkdir blog $ cd blog $ pipenv install django $ pipenv shell (blog) $ django-admin startproject blog_project . (blog) $ python manage.py startapp blog (blog) $ python manage.py migrate (blog) $ python manage.py runserver Чтобы убедиться, что Django знает о нашем новом приложении, откройте текстовый редактор и добавьте новое приложение в INSTALLED_APPS в нашем settings.py файле: Code # blog_project/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog', # new ] Если вы перейдете по http://127.0.0.1:8000/ должны увидеть следующую страницу. Группа в ВК "Django_Python" 98 Chapter 5: Blog app Django welcome page Хорошо, первичная настройка закончена! Далее мы создадим модель базы данных для записей блога. Database Models(модели базы данных) Каковы характеристики типичного приложения для блога? В нашем случае давайте придерживаться простоты и предположим, что у каждого сообщения есть название, автор и тело. Мы можем превратить это в модель базы данных, откроем файл blog/models.py и введем показанный ниже код : Группа в ВК "Django_Python" 99 Chapter 5: Blog app Code # blog/models.py from django.db import models class Post(models.Model): title = models.CharField(max_length= ) author = models.ForeignKey( 'auth.User', on_delete=models.CASCADE, ) body = models.TextField() def __str__(self): return self.title В верхней части мы импортируем класс models а затем создаем подкласс models.Model с именем Post. Используя функционал подкласса, мы автоматически имеем доступ ко всему в django.db.models.Models и можем добавлять дополнительные поля и методы по своему желанию. Для title мы ограничиваем длину до 200 символов, а для body мы используем текстовое поле, которое будет автоматически расширяться по мере необходимости, чтобы соответствовать тексту пользователя. В Django доступно много типов полей; вы можете увидеть полный список здесь. Для поля author мы используем ForeignKey это предоставляет связь многие-к-одному . Это означает, что данный пользователь может быть автором многих различных постов в блоге, но не наоборот. Ссылка на встроенную модель User которой Django обеспечивает аутентификацию. Для всех связей многие-к-одному таких как ForeignKey нам также необходимо указать параметр on_delete . Группа в ВК "Django_Python" 100 Chapter 5: Blog app Теперь, когда наша новая модель базы данных создана, нам нужно создать для нее новую запись миграции и перенести изменения в нашу базу данных. Это двухэтапный процесс и может быть выполнен следующими командами: Command Line (blog) $ python manage.py makemigrations blog (blog) $ python manage.py migrate blog Наша база настроена! Что дальше? Admin Нам нужен способ доступа к нашим данным. Войдем в Django админ! Но сначала создадим учетную запись суперпользователя, введя приведенную ниже команду и после отвечая на запросы введем адрес электронной почты и пароль. Обратите внимание, что при вводе пароля он не будет отображаться на экране по соображениям безопасности. Command Line (blog) $ python manage.py createsuperuser Username (leave blank to use 'wsv'): wsv Email: Password: Password (again): Superuser created successfully. Теперь снова запустите сервер Django с помощью команды python manage.py runserver и откройте Django админ http://127.0.0.1:8000/admin/. Войдите с новой учетной записью суперпользователя. Упс! А где наша новая Post модель? Группа в ВК "Django_Python" 101 Chapter 5: Blog app Admin homepage Мы забыли обновить blog/admin.py так что давайте сейчас сделаем это. Code # blog/admin.py from django.contrib import admin from .models import Post admin.site.register(Post) Если вы обновите страницу, вы увидите это обновление. Группа в ВК "Django_Python" 102 Chapter 5: Blog app Admin homepage Давайте добавим два сообщения в блог, чтобы у нас были примеры данных для работы. Нажмите кнопку + Add рядом с Posts чтобы создать новую запись. Не забудьте добавить “author” к каждому сообщению, так как по умолчанию все поля модели обязательны для заполнения. Если вы попытаетесь ввести сообщение без автора вы увидите ошибку. Если мы захотим изменить это, мы можем добавить field options(параметры поля) в нашу модель, чтобы сделать это поле необязательным или заполнить его значением по умолчанию Группа в ВК "Django_Python" 103 Chapter 5: Blog app Первый пост Группа в ВК "Django_Python" 104 Chapter 5: Blog app Второй пост Группа в ВК "Django_Python" 105 Chapter 5: Blog app Админка с двумя постами Теперь, когда наша модель базы данных завершена, нам нужно создать необходимые view, URL-адреса и шаблоны, чтобы мы могли отображать информацию в нашем вебприложении. URLs Мы хотим, чтобы сообщения в блоге отображались на главной странице, поэтому, как и в предыдущих главах, мы сначала настроим URLConfs уровня проекта, а затем URLConfs уровня приложения, что бы достичь этого. Обратите внимание на то, что “уровень проекта” означает в той же родительской папке где папка blog_project и папка приложения blog. В командной строке остановите существующий сервер с помощью Control-c и создайте новый urls.py файл в вашем blog: Группа в ВК "Django_Python" 106 Chapter 5: Blog app Command Line (blog) $ touch blog/urls.py Теперь обновите его с помощью кода ниже. Code # blog/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.BlogListView.as_view(), name='home'), ] Мы импортируем views (которые скоро создадим) в верхней части. Пустая строка '' говорит Python что соответствует всем значениям, далее мы делаем URL именованным home, к которому мы можем обратиться в наших views позже. Хотя это не обязательно но добавление имени к URL это хорошая практика которую необходимо принять так как это помогает сохранять организованность когда количество ваших URL растет. Также мы должны обновить файл urls.py на уровне проекта, чтобы он мог пересылать все запросы непосредственно на blog приложение. Группа в ВК "Django_Python" 107 Chapter 5: Blog app Code # blog_project/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('blog.urls')), ] Мы добавили include во второй строке и url образец использовав пустую строку регулярного выражения '' указывающее, что URL запросы должны быть перенаправлены на URL-адреса блога(blog) для дальнейших инструкций. Views Мы собираемся использовать views на основе классов, но если вы хотите увидеть функциональный способ создания приложения для блога, я настоятельно рекомендую Django Girls Tutorial. Это отличный учебник. В нашем файле views добавьте код ниже, чтобы отобразить содержимое нашей модели Post с помощью ListView. Группа в ВК "Django_Python" 108 Chapter 5: Blog app Code # blog/views.py from django.views.generic import ListView from . models import Post class BlogListView(ListView): model = Post template_name = 'home.html' ώҮҬҽҿҬүҺҿҷҲҿһҼҺҸҴҪҿҶӅҲҶҹҸҺҼҲҺҽүҶ-JTU7JFXҲҷҪӂҽҶҸҮүҵӆҫҪұӅҮҪҷҷӅҿ1PTUϓҪҼүҶ ҶӅһҸұҮҪүҶҹҸҮҴҵҪһһ-JTU7JFXҲҮҸҫҪҬҵӉүҶһһӅҵҴҲҷҪҷҪӂҽҶҸҮүҵӆҲӂҪҫҵҸҷҧҼҸӇҴҸҷҸҶҲҼ ҷҪҶҶҷҸҭҸҴҸҮҪҹҸһҺҪҬҷүҷҲӈһҺүҪҵҲұҪӀҲүҳҬһүҭҸӇҼҸҭҸһҷҽҵӉ Templates ӂҪҫҵҸҷӅ ϞүҹүҺӆ һ ҷҪӂҲҶ 63-$POGT Ҳ WJFXT ұҪҴҸҷӁҲҵҲ ҷҪҶ ҷү ҿҬҪҼҪүҼ ҼҸҵӆҴҸ ҼҺүҼӆүҳ ӁҪһҼҲ ҭҸҵҸҬҸҵҸҶҴҲӂҪҫҵҸҷҸҬ UFNQMBUFT ϖҪҴҶӅҽҰүузнали изҭҵҪҬы ҶӅҶҸҰүҶҷҪһҵүҮҸҬҪҼӆҸҼ ҮҺҽҭҲҿ ӂҪҫҵҸҷҸҬ ӁҼҸҫӅ ҮүҺҰҪҼӆ наш ҴҸҮ Ҭ ӁҲһҼҸҼү ϛҸӇҼҸҶҽ ҷҪӁҷүҶ һ ҾҪҳҵҪ CBTFIUNM Ҳ ҾҪҳҵҪ IPNFIUNM ҴҸҼҸҺӅҳ ҷҪһҵүҮҽүҼһӉ ҸҼ ҷүҭҸ ϛҸұҰү ҴҸҭҮҪ ҶӅ ҮҸҫҪҬҲҶ ӂҪҫҵҸҷӅ ҮҵӉ һҸұҮҪҷҲӉҲҺүҮҪҴҼҲҺҸҬҪҷҲӉһҸҸҫӃүҷҲҳҫҵҸҭҪ ҸҷҲҼҪҴҰүҶҸҭҽҼҷҪһҵүҮҸҬҪҼӆһӉҸҼbase.html. Начнем с создания каталога шаблонов(templates) на уровне проекта с двумя файлами шаблонов. Группа в ВК "Django_Python" 109 Chapter 5: Blog app Command Line (blog) $ mkdir templates (blog) $ touch templates/base.html (blog) $ touch templates/home.html Затем обновите settings.py чтобы Django знал, где искать наши шаблоны. Code # blog_project/settings.py TEMPLATES = [ { ... 'DIRS': [os.path.join(BASE_DIR, 'templates')], ... }, ] Теперь обновите шаблон base.html следующим образом. Code <!-- templates/base.html --> <html> <head> <title>Django blog</title> </head> <body> <header> <h1><a href="/">Django blog</a></h1> </header> Группа в ВК "Django_Python" 110 Chapter 5: Blog app <div class="container"> {% block content %} {% endblock content %} </div> </body> </html> Обратите внимание на то, что код между {% block content %} и {% endblock content %} может быть заполнен другими шаблонами. Говоря об этом, вот код для home.html. Code <!-- templates/home.html --> {% extends 'base.html' %} {% block content %} {% for post in object_list %} <div class="post-entry"> <h2><a href="">{{ post.title }}</a></h2> <p>{{ post.body }}</p> </div> {% endfor %} {% endblock content %} В верхней части отметим, что этот шаблон расширяет(extends) base.html и обертывает нужный вам код с помощью контент-блоков. Мы используем язык шаблонов Django Templating Language, чтобы создать простой цикл for для каждой записи блога. Обратите внимание, что object_list происходит из ListView и содержит все объекты в нашем view. Если вы снова запустите сервер Django: python manage.py runserver. Группа в ВК "Django_Python" 111 Chapter 5: Blog app И обновите http://127.0.0.1:8000/ мы видим, что это работает. Главная страница блога с двумя постами Но выглядит это ужасно. Давайте это исправим! Static files(статичные файлы) Нам нужно добавить CSS, который называется статическим файлом, потому что, в отличие от нашего динамического контента базы данных, он не изменяется. К счастью добавлять, статические файлы, такие как CSS, JavaScript и изображения в наш проект Django очень просто. В готовом к производству Django проекте вы, как правило, храните его в сети доставки контента (CDN) для лучшей производительности, но для наших целей хранение файлов локально это нормально. Сначала закройте наш локальный сервер с Control-c. Затем создайте папку на уровне проекта с именем static. Группа в ВК "Django_Python" 112 Chapter 5: Blog app Command Line (blog) $ mkdir static Как и в нашей папке с шаблонами, нам нужно обновить settings.py, чтобы сообщить Django, где искать эти статические файлы. Мы можем обновить settings.py с изменением строки для STATICFILES_DIRS. Добавьте его в нижнюю часть файла под заголовком STATIC_URL. Code # blog_project/settings.py STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] Теперь создайте папку css в static и добавьте в нее новый файл base.css. Command Line (blog) $ mkdir static/css (blog) $ touch static/css/base.css Что мы должны поместить в ваш файл? Как насчет изменения title на красный? Code /* static/css/base.css */ header h1 a { color: red; } Последний шаг. Нам нужно добавить статические файлы в ваши шаблоны, добавив {% load staticfiles %} в начало base.html. Потому что другие наши шаблоны наследуются от base.html мы должны добавить это только один раз. Добавьте новую строку в нижней части кода <head>< / head>, которая явно ссылается на наш новый файл base.css. Группа в ВК "Django_Python" 113 Chapter 5: Blog app Code <!-- templates/base.html --> {% load static %} <html> <head> <title>Django blog</title> <link rel="stylesheet" href="{% static 'css/base.css' %}"> </head> ... УФ! Это было немного больно, но это одноразовая боль. Теперь мы можем добавить статические файлы в нашу папку static, и они автоматически появятся во всех наших шаблонах. Запустите сервер снова с python manage.py runserver и посмотрите на нашем обновленном сайте http://127.0.0.1:8000/. Домашняя страница блога с красным заголовком Мы можем сделать немного лучше. Как насчет того, чтобы добавить пользовательский шрифт и еще немного CSS? Поскольку эта книга не является учебником по CSS, просто вставьте следующее между тегами <head>< / head>, чтобы добавить Source Sans Pro, бесплатный шрифт от Google. Группа в ВК "Django_Python" 114 Chapter 5: Blog app Code <!-- templates/base.html --> {% load static %} <html> <head> <title>Django blog</title> <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro: stylesheet"> <link rel="stylesheet" href="{% static 'css/base.css' %}"> </head> ... Затем обновите наш css файл, скопировав и вставив следующий код: Code /* static/css/base.css */ body { font-family: 'Source Sans Pro', sans-serif; font-size: px; } header { border-bottom: px solid # margin-bottom: rem; ; display: flex; } header h1 a { color: red; Группа в ВК "Django_Python" " rel="\ 115 Chapter 5: Blog app text-decoration: none; } .nav-left { margin-right: auto; } .nav-right { display: flex; padding-top: rem; } .post-entry { margin-bottom: rem; } .post-entry h2 { margin: . rem ; } .post-entry h2 a, .post-entry h2 a:visited { color: blue; text-decoration: none; } .post-entry p { margin: ; Группа в ВК "Django_Python" 116 Chapter 5: Blog app font-weight: ; } .post-entry h2 a:hover { color: red; } Обновить домашнюю страницу http://127.0.0.1:8000/ и вы должны увидеть следующее. Домашняя страница блога с CSS Отдельные страницы блога Теперь мы можем добавить функциональность для отдельных страниц блога. Как мы можем это сделать?Мы должны создать новое представление, URL и шаблон. Я надеюсь, что вы заметили шаблон в разработке с Django сейчас! Начните с представления. Для упрощения можно использовать общее представление DetailView на основе классов. В верхней части файла добавьте DetailView в список импорта, а затем создайте наше новое представление под названием BlogDetailView. Группа в ВК "Django_Python" 117 Chapter 5: Blog app view called BlogDetailView. Code # blog/views.py from django.views.generic import ListView, DetailView from . models import Post class BlogListView(ListView): model = Post template_name = 'home.html' class BlogDetailView(DetailView): model = Post template_name = 'post_detail.html' В этом новом представлении мы определяем модель, которую мы используем, Post и шаблон, с которым мы хотим ее связать, post_detail.html. По умолчанию DetailView предоставит контекстный объект, который мы можем использовать в нашем шаблоне, называемый либо объектом, либо строчным именем нашей модели post. Кроме того, DetailView ожидает либо первичный ключ, либо slug, переданный ему в качестве идентификатора. Подробнее об этом вкратце. Теперь выйдите из локального сервера Control-c и создайте наш новый шаблон для деталей поста следующим образом: Группа в ВК "Django_Python" 118 Chapter 5: Blog app Command Line (blog) $ touch templates/post_detail.html Затем введите следующий код: Code <!-- templates/post_detail.html --> {% extends 'base.html' %} {% block content %} <div class="post-entry"> <h2>{{ post.title }}</h2> <p>{{ post.body }}</p> </div> {% endblock content %} В верхней части мы указываем, что этот шаблон наследуется от base.html. Затем отобразите title и body из нашего объекта контекста, который DetailView делает доступным как post. Лично я нашел именование объектов контекста в общих представлениях чрезвычайно запутанным при первом изучении Django. Поскольку наш объект контекста из подробного представления является либо вашим именем модели post, либо объектом, мы также можем обновить наш шаблон следующим образом, и он будет работать точно так же. Группа в ВК "Django_Python" 119 Chapter 5: Blog app Code <!-- templates/post_detail.html --> {% extends 'base.html' %} {% block content %} <div class="post-entry"> <h2>{{ object.title }}</h2> <p>{{ object.body }}</p> </div> {% endblock content %} Если вы обнаружите, что использование post или object сбивает с толку, мы также можем явно задать имя объекта контекста в нашем представлении. Поэтому, если бы мы хотели назвать его anything_you_want, а затем использовать его в шаблоне, код выглядел бы следующим образом, и он работал бы так же. Code # blog/views.py ... class BlogDetailView(DetailView): model = Post template_name = 'post_detail.html' context_object_name = 'anything_you_want' Группа в ВК "Django_Python" 120 Chapter 5: Blog app Code <!-- templates/post_detail.html --> {% extends 'base.html' %} {% block content %} <div class="post-entry"> <h2>{{ anything_you_want.title }}</h2> <p>{{ anything_you_want.body }}</p> </div> {% endblock content %} "Волшебное" именование объекта контекста-это цена, которую вы платите за простоту и удобство использования общих представлений. Они великолепны, если вы знаете, что они делают, но их трудно настроить, если вы хотите другого поведения. Хорошо, что будет дальше? Как насчет добавления нового URLConf для нашего представления, который мы можем сделать следующим образом. Code # blog/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.BlogListView.as_view(), name='home'), path('post/<int:pk>/', views.BlogDetailView.as_view(), name='post_detail'), ] Группа в ВК "Django_Python" 121 Chapter 5: Blog app Все записи в блоге будут начинаться с post/. Далее идет первичный ключ для нашей записи post, которая будет представлена в виде целого числа <int: pk>. Вероятно вы спросите что это за первичный ключ? Django автоматически добавляет автоматически увеличивающийся первичный ключ к нашим моделям баз данных. Таким образом, как только мы объявили название поля, author и body в нашей модели Post, Django под капотом также добавил еще одно поле под названием id, который является нашим первичным ключом. Мы можем получить к нему доступ как id или pk. Этот pk для нашего первого поста "Привет, Мир" равен 1. Для второго поста это 2. и так далее. Поэтому, когда мы переходим на страницу отдельной записи для нашего первого сообщения, мы можем ожидать, что его url шаблон будет post/1. Примечание: понимание того, как первичные ключи работают с DetailView является очень распространенной путаницей для начинающих. Стоит перечитать предыдущие два абзаца несколько раз, если не понимаете. С практикой это станет второй натурой. Если теперь запустить сервер с python manage.py runserver и перейти непосредственно к http://127.0.0.1:8000/post/1/ вы увидите специальную страницу для нашего первого поста в блоге. Blog post one detail Ура! Вы также можете перейти к http://127.0.0.1:8000/post/2 посмотреть вторую запись. Чтобы сделать нашу жизнь проще, мы должны обновить ссылку на главной странице, чтобы мы могли напрямую получить доступ к отдельным сообщениям в блоге оттуда. В настоящее время в home.html наша ссылка пуста: <a href="">. Обновите его до <a href="{% url 'post_detail' post.pk %}">. Группа в ВК "Django_Python" 122 Chapter 5: Blog app Code <!-- templates/home.html --> {% extends 'base.html' %} {% block content %} {% for post in object_list %} <div class="post-entry"> <h2><a href="{% url 'post_detail' post.pk %}">{{ post.title }}</a></h2> <p>{{ post.body }}</p> </div> {% endfor %} {% endblock content %} Мы начинаем с того, что говорим нашему шаблону Django, что мы хотим ссылаться на URLConf, используя код {%url ... %}. Какой URL? Тот, который называется post_detail, который мы назвали BlogDetailView в нашей url-конфигурации всего минуту назад. Если мы посмотрим на post_detail в нашем URLConf, мы увидим, что он ожидает, что будет передан аргумент pk, представляющий первичный ключ для записи блога. К счастью, Django уже создал и включил это поле pk в наш объект post. Мы передаем его в URLConf, добавляя его в шаблон как post.pk. Чтобы убедиться, что все работает, обновите главную страницу http://127.0.0.1:8000/ и нажмите на название каждого поста в блоге, чтобы подтвердить работу новых ссылок. Тесты Сейчас нам нужно протестировать нашу модель и представления. Мы хотим убедиться, что модель Post работает должным образом, включая ее представление. И мы хотим протестировать как ListView, так и DetailView. Группа в ВК "Django_Python" 123 Chapter 5: Blog app Вот как выглядят примеры тестов в файле blog/tests.py. Code # blog/tests.py from django.contrib.auth import get_user_model from django.test import Client, TestCase from django.urls import reverse from .models import Post class BlogTests(TestCase): def setUp(self): self.user = get_user_model().objects.create_user( username='testuser', email='test@email.com', password='secret' ) self.post = Post.objects.create( title='A good title', body='Nice body content', author=self.user, ) def test_string_representation(self): post = Post(title='A sample title') self.assertEqual(str(post), post.title) Группа в ВК "Django_Python" 124 Chapter 5: Blog app def test_post_content(self): self.assertEqual(f'{self.post.title}', 'A good title') self.assertEqual(f'{self.post.author}', 'testuser') self.assertEqual(f'{self.post.body}', 'Nice body content') def test_post_list_view(self): response = self.client.get(reverse('home')) self.assertEqual(response.status_code, ) self.assertContains(response, 'Nice body content') self.assertTemplateUsed(response, 'home.html') def test_post_detail_view(self): response = self.client.get('/post/ /') no_response = self.client.get('/post/ /') self.assertEqual(response.status_code, ) self.assertEqual(no_response.status_code, ) self.assertContains(response, 'A good title') self.assertTemplateUsed(response, 'post_detail.html') В этих тестах много нового, поэтому мы будем проходить их медленно. Вверху импортируем get_user_model для ссылки на активного пользователя. Мы импортируем TestCase, который мы видели раньше, а также Client (), который является новым и используется в качестве фиктивного веб-браузера для имитации GET и POST запросов на URL. Другими словами, всякий раз, когда вы тестируете представления, вы должны использовать Client(). В нашем методе setUp мы добавляем образец поста в блоге для тестирования, а затем подтверждаем, что его строковое представление и содержимое верны. Затем мы используем test_post_list_view, чтобы подтвердить, что наш сайт возвращает код статуса HTTP 200, содержит наш текст, и использует правильный шаблон home.html. Группа в ВК "Django_Python" 125 Chapter 5: Blog app Наконец test_post_detail_view проверяет, что наша страница работает должным образом и что неправильная страница возвращает 404. Всегда хорошо проверить, что что-то существует, и что что-то неправильное не существует в ваших тестах. Продолжайте и выполните эти тесты сейчас. Они все должны быть пройдены Command Line (testy) $ python manage.py test Git Сейчас самое время для нашего первого git commit. Сначала инициализируйте наш каталог.Command Line (testy) $ git init Затем просмотрите все содержимое, которое мы добавили, проверив состояние. Добавьте все новые файлы. И сделайте наш первый commit. Command Line (testy) $ git status (testy) $ git add -A (testy) $ git commit -m 'initial commit' Conclusion Мы создаем простое приложение блог с нуля! С помощью администратора Django мы можем создавать, редактировать или удалять контент. И мы использовали DetailView впервые создав детальное отдельное представление каждого поста блога. Группа в ВК "Django_Python" 126 Chapter 5: Blog app В следующем разделе Глава 6: Приложение блога с формами мы добавим формы, чтобы нам вообще не пришлось использовать администратора Django для этих изменений. Группа в ВК "Django_Python" Глава 6: Формы В этой главе мы продолжим работу над нашим приложением блога из главы 5, добавив формы, чтобы пользователь мог создавать, редактировать или удалять любую из своих записей блога. Формы Формы очень распространены и очень сложны для правильной реализации. Каждый раз, когда вы принимаете пользовательский ввод, возникают проблемы безопасности (XSS Атаки), требуется правильная обработка ошибок, а также вопросы пользовательского интерфейса о том, как предупредить пользователя о проблемах с формой. Не говоря уже о необходимости перенаправления при успехе. К счастью для нас, встроенные формы Django абстрагируют большую часть сложности и предоставляют богатый набор инструментов для обработки распространенных случаев использования, работающих с формами. Чтобы начать, обновите наш базовый шаблон, чтобы отобразить ссылку на страницу для ввода новых сообщений в блоге. Он примет фрму <a href= "{% url 'post_new'%} " ></a>, где post_new-это имя нашего URL. Обновленный файл должен выглядеть следующим образом: Группа в ВК "Django_Python" 128 Chapter 6: Forms Code <!-- templates/base.html --> {% load staticfiles %} <html> <head> <title>Django blog</title> <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro: ="stylesheet"> <link rel="stylesheet" href="{% static 'css/base.css' %}"> </head> <body> <div class="container"> <header> <div class="nav-left"> <h1><a href="/">Django blog</a></h1> </div> <div class="nav-right"> <a href="{% url 'post_new' %}">+ New Blog Post</a> </div> </header> {% block content %} {% endblock content %} </div> </body> </html> Давайте добавим новый URLConf для post_new: Группа в ВК "Django_Python" " rel\ 129 Chapter 6: Forms Code # blog/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.BlogListView.as_view(), name='home'), path('post/<int:pk>/', views.BlogDetailView.as_view(), name='post_detail'), path('post/new/', views.BlogCreateView.as_view(), name='post_new'), ] Ваш url будет начинаться с post / new/, представление называется Blog Create View, и url будет называться post_new. Просто, правда? Теперь давайте создадим наше представление, импортировав новый универсальный класс под названием Create View, а затем создадим его подкласс для создания нового представления с именем BlogCreateView. Code # blog/views.py from django.views.generic import ListView, DetailView from django.views.generic.edit import CreateView from . models import Post class BlogListView(ListView): model = Post template_name = 'home.html' Группа в ВК "Django_Python" 130 Chapter 6: Forms class BlogDetailView(DetailView): model = Post template_name = 'post_detail.html' class BlogCreateView(CreateView): model = Post template_name = 'post_new.html' fields = '__all__' В BlogCreateView мы указываем нашу модель базы данных Post, имя нашего шаблона post_new.html и все поля с '__all__', так как у нас только два: title и author. Последний шаг создать наш шаблон, который мы назовем post_new.html. Command Line (blog) $ touch templates/post_new.html А затем добавьте следующий код: Группа в ВК "Django_Python" 131 Chapter 6: Forms Code <!-- templates/post_new.html --> {% extends 'base.html' %} {% block content %} <h1>New post</h1> <form action="" method="post">{% csrf_token %} {{ form.as_p }} <input type="submit" value="Save" /> </form> {% endblock %} Давайте разберемся с тем, что мы сделали: • В верхней строке мы наследуем от нашего базового шаблона. • Используются HTML-теги <form> с методом POST, так как мы отправляем данные. Если бы мы получали данные из формы, например, в окне поиска, мы бы использовали GET. • Добавлен {% csrf_token %}, который предоставляется Django для защиты нашей формы от атак межсайтового скриптинга. Вы должны использовать его для всех ваших форм в Django. • Затем для вывода данных формы используется {{ form.as_p }}, который отображает его в тегах параграфа <p>. • Наконец, указываем тип ввода submit и присваиваем ему значение "Save". Для просмотра нашей работы запустите сервер с python manage.py runserver и перейдите на домашнюю страницу по адресу http://127.0.0.1:8000/. Группа в ВК "Django_Python" 132 Chapter 6: Forms Homepage with New button Нажмите на нашу ссылку "+ New Blog Post", которое перенаправит вас на: http://127.0.0.1:8000/post/new/. Группа в ВК "Django_Python" 133 Chapter 6: Forms Blog new page Попробуйте создать новую запись в блоге и отправить ее. Группа в ВК "Django_Python" 134 Chapter 6: Forms Blog new page Упс! что случилось? Группа в ВК "Django_Python" 135 Chapter 6: Forms Blog new page Сообщение об ошибке Django является весьма полезным. Он жалуется, что мы не указали, куда отправить пользователя после успешной отправки формы. Давайте отправим пользователя на страницу сведений после успешной отправки; таким образом они смогут увидеть свою завершенную публикацию. Мы можем следовать предложению Django и добавить get_absolute_url в нашу модель. Это лучшая практика, которую вы всегда должны делать. Он устанавливает канонический URLадрес для объекта, поэтому, даже если структура ваших URL-адресов изменится в будущем, ссылка на конкретный объект будет одинаковой. Короче говоря, вы должны добавить get_absolute_url() и __ str_ _ () метод к каждой модели, которую вы пишете. Группа в ВК "Django_Python" 136 Chapter 6: Forms Откройте models.py файл. Добавьте импорт во второй строке для reverse и новый метод get_-absolute_url. Command Line # blog/models.py from django.db import models from django.urls import reverse class Post(models.Model): title = models.CharField(max_length= ) author = models.ForeignKey( 'auth.User', on_delete=models.CASCADE, ) body = models.TextField() def __str__(self): return self.title def get_absolute_url(self): return reverse('post_detail', args=[str(self.id)]) Reverse - очень удобная утилита Django, позволяющая ссылаться на объект по имени шаблона URL, в данном случае post_detail. Если вы всмните наш шаблон URL, то он выглядит следующим образом: Группа в ВК "Django_Python" 137 Chapter 6: Forms Code path('post/<int:pk>/', views.BlogDetailView.as_view(), name='post_detail'), Это означает, что для того, чтобы этот маршрут работал, мы должны также передать аргумент с ключом pk или первичным ключом объекта. Запутанно что pk и id взаимозаменяемы в Django, хотя документация Django рекомендуют использовать self.id с параметром get_absolute_url. Поэтому мы сообщаем Django, что конечным местоположением записи Post является ее представление post_detail, которое представляет собой posts / <int: pk> /, поэтому маршрут для первой созданной нами записи будет находиться в posts / 1. Попробуйте создать новую запись в блоге еще раз по адресу http://127.0.0.1:8000/post/new/, и в случае успеха вы будете перенаправлены на страницу подробного просмотра, где размещена запись. Новая страница блога с вводом Группа в ВК "Django_Python" 138 Chapter 6: Forms Вы также заметите, что наш предыдущий пост в блоге тоже есть. Он был успешно отправлен в базу данных, но Django не знал, как перенаправить нас после этого. Blog homepage with four posts Хотя мы можем обратиться к администратору Django, чтобы удалить нежелательные сообщения, но лучше добавить формы, чтобы пользователь мог обновлять и удалять существующие сообщения непосредственно с сайта. Форма обновления Процесс создания формы обновления, чтобы пользователи могли редактировать сообщения в блоге, должен быть знакомым. Мы снова будем использовать встроенное общее представление на основе классов Django UpdateView и создадим необходимый шаблон, URL и представление. Группа в ВК "Django_Python" 139 Chapter 6: Forms Для начала добавим новую ссылку на post_detail.html, чтобы возможность редактирования сообщения блога появилась на отдельной странице блога. Code <!-- templates/post_detail.html --> {% extends 'base.html' %} {% block content %} <div class="post-entry"> <h2>{{ object.title }}</h2> <p>{{ object.body }}</p> </div> <a href="{% url 'post_edit' post.pk %}">+ Edit Blog Post</a> {% endblock content %} Мы добавили ссылку, используя <a href> ... </a> и тег движка шаблонов Django {% url ...%}. В нем мы указали целевое имя нашего URL, которое будет называться post_edit, а также передали необходимый параметр, который является первичным ключом поста post.pk. Затем мы создаем шаблон для нашей страницы редактирования с именем post_edit.html. Command Line (blog) $ touch templates/post_edit.html И добавьте следующий код: Группа в ВК "Django_Python" 140 Chapter 6: Forms Code <!-- templates/post_edit.html --> {% extends 'base.html' %} {% block content %} <h1>Edit post</h1> <form action="" method="post">{% csrf_token %} {{ form.as_p }} <input type="submit" value="Update" /> </form> {% endblock %} Мы снова используем теги HTML <form></form>, csrf_token от Django для безопасности, form.as_p, чтобы отобразить наши поля формы с тегами абзаца, и, наконец, дать ему значение “Update” на кнопке отправки. Теперь к нашему представлению нужно импортировать UpdateView на второй строке с верху, а затем создать его подкласс в нашем новом представлении BlogUpdateView. Code # blog/views.py from django.views.generic import ListView, DetailView from django.views.generic.edit import CreateView, UpdateView from . models import Post class BlogListView(ListView): model = Post template_name = 'home.html' Группа в ВК "Django_Python" 141 Chapter 6: Forms class BlogDetailView(DetailView): model = Post template_name = 'post_detail.html' class BlogCreateView(CreateView): model = Post template_name = 'post_new.html' fields = '__all__' class BlogUpdateView(UpdateView): model = Post fields = ['title', 'body'] template_name = 'post_edit.html' Обратите внимание, что в представлении BlogUpdateView мы явно перечисляем поля, которые мы хотим использовать ['title', 'body'], а не " __all__". Это потому, что мы предполагаем, что автор сообщения не меняется; мы хотим, чтобы только заголовок и текст были редактируемыми. Заключительный этап-это обновление вашего файла urls.py как указано: Группа в ВК "Django_Python" 142 Chapter 6: Forms Code # blog/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.BlogListView.as_view(), name='home'), path('post/<int:pk>/', views.BlogDetailView.as_view(), name='post_detail'), path('post/new/', views.BlogCreateView.as_view(), name='post_new'), path('post/<int:pk>/edit/', views.BlogUpdateView.as_view(), name='post_edit'), ] В верхней части мы добавляем наше представление BlogUpdateView в список импортированных представлений, затем создали новый шаблон url для /post/pk / edit и дали ему имя post_edit. Теперь, если вы нажмете на запись в блоге, вы увидите нашу новую кнопку редактирования. Страница блога с кнопкой редактировать Группа в ВК "Django_Python" 143 Chapter 6: Forms Если вы нажмете на "+ Edit Blog Pos", вы будете перенаправлены на http://127.0.0.1:8000/post/1/ edit/if это ваш первый пост в блоге. Страница редактирования Обратите внимание, что форма предварительно заполняется существующими данными нашей базы данных для публикации. Давайте внесем изменения… Группа в ВК "Django_Python" 144 Chapter 6: Forms Blog edit page И после нажатия кнопки "Update" мы перенаправляемся в подробный вид поста, где вы можете увидеть изменения. Это из-за нашей установки get_absolute_url. Перейдите на главную страницу, и вы увидите изменения рядом со всеми остальными записями. Группа в ВК "Django_Python" 145 Chapter 6: Forms Домашняя страница блога с отредактированным сообщением Удаление View Процесс создания формы для удаления записей блога очень похож на процесс обновления записи. Мы будем использовать еще одно общее представление на основе классов, DeleteView, для создания представления, url-адреса и шаблона функциональности. Давайте начнем с добавления ссылки для удаления записей блога на Вашей странице блога,post_-detail.html. Группа в ВК "Django_Python" 146 Chapter 6: Forms Code <!-- templates/post_detail.html --> {% extends 'base.html' %} {% block content %} <div class="post-entry"> <h2>{{ object.title }}</h2> <p>{{ object.body }}</p> </div> <p><a href="{% url 'post_edit' post.pk %}">+ Edit Blog Post</a></p> <p><a href="{% url 'post_delete' post.pk %}">+ Delete Blog Post</a></p> {% endblock content %} Затем создайте новый файл для нашего шаблона удаления страницы. Сначала выйдите из локального сервера Control-c, а затем введите следующую команду: Command Line (blog) $ touch templates/post_delete.html И заполните его этим кодом: Группа в ВК "Django_Python" 147 Chapter 6: Forms Code <!-- templates/post_delete.html --> {% extends 'base.html' %} {% block content %} <h1>Delete post</h1> <form action="" method="post">{% csrf_token %} <p>Are you sure you want to delete "{{ post.title }}"?</p> <input type="submit" value="Confirm" /> </form> {% endblock %} Обратите внимание, что мы используем post.title для отображения заголовка нашего блога. Мы также могли бы просто использовать object.title, поскольку он также предоставляется DetailView. Теперь обновите наш файл views.py, импортировав DeleteView и reverse_lazy вверху, затем создайте новое представление, которое подкласс DeleteView. Code # blog/views.py from django.views.generic import ListView, DetailView from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.urls import reverse_lazy from . models import Post class BlogListView(ListView): model = Post Группа в ВК "Django_Python" 148 Chapter 6: Forms template_name = 'home.html' class BlogDetailView(DetailView): model = Post template_name = 'post_detail.html' class BlogCreateView(CreateView): model = Post template_name = 'post_new.html' fields = '__all__' class BlogUpdateView(UpdateView): model = Post fields = ['title', 'body'] template_name = 'post_edit.html' class BlogDeleteView(DeleteView): model = Post template_name = 'post_delete.html' success_url = reverse_lazy('home') Мы используем reverse_lazy вместо просто reverse, чтобы он не выполнял перенаправление URL, пока наше представление не завершило удаление сообщения в блоге. Наконец, добавьте URL, импортировав наш BlogDeleteView и добавив новый шаблон: Группа в ВК "Django_Python" 149 Chapter 6: Forms Code # blog/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.BlogListView.as_view(), name='home'), path('post/<int:pk>/', views.BlogDetailView.as_view(), name='post_detail'), path('post/new/', views.BlogCreateView.as_view(), name='post_new'), path('post/<int:pk>/edit/', views.BlogUpdateView.as_view(), name='post_edit'), path('post/<int:pk>/delete/', views.BlogDeleteView.as_view(), name='post_delete'), ] Если вы снова запустите сервер python manage.py runserver и обновите страницу отдельного сообщения, вы увидите ссылку «Удалить сообщение в блоге». Группа в ВК "Django_Python" 150 Chapter 6: Forms Blog delete post При нажатии на ссылку мы попадаем на страницу удаления записи блога, где отображается название записи блога. страница удалить запись Если вы нажмете на кнопку “Confirm”, то будуте перенаправлены на домашнию страницу, где пост был удален! Группа в ВК "Django_Python" 151 Chapter 6: Forms Домашняя страница с удаленным сообщением Так это работает! Tests Время для тестов, чтобы убедиться, что все работает сейчас–и в будущем-как и ожидалось. Мы добавили метод get_absolute_url в нашу модель и новые представления для создания, обновления и редактирования записей. Это означает, что нам нужно четыре новых теста: • def test_get_absolute_url • def test_post_create_view • def test_post_update_view • def test_post_delete_view Обновите существующий tests.py файл следующим образом. Группа в ВК "Django_Python" 152 Chapter 6: Forms Code # blog/tests.py from django.contrib.auth import get_user_model from django.test import Client, TestCase from django.urls import reverse from .models import Post class BlogTests(TestCase): def setUp(self): self.user = get_user_model().objects.create_user( username='testuser', email='test@email.com', password='secret' ) self.post = Post.objects.create( title='A good title', body='Nice body content', author=self.user, ) def test_string_representation(self): post = Post(title='A sample title') self.assertEqual(str(post), post.title) Группа в ВК "Django_Python" 153 Chapter 6: Forms def test_get_absolute_url(self): self.assertEquals(self.post.get_absolute_url(), '/post/ /') def test_post_content(self): self.assertEqual(f'{self.post.title}', 'A good title') self.assertEqual(f'{self.post.author}', 'testuser') self.assertEqual(f'{self.post.body}', 'Nice body content') def test_post_list_view(self): response = self.client.get(reverse('home')) self.assertEqual(response.status_code, ) self.assertContains(response, 'Nice body content') self.assertTemplateUsed(response, 'home.html') def test_post_detail_view(self): response = self.client.get('/post/ /') no_response = self.client.get('/post/ /') self.assertEqual(response.status_code, ) self.assertEqual(no_response.status_code, ) self.assertContains(response, 'A good title') self.assertTemplateUsed(response, 'post_detail.html') def test_post_create_view(self): response = self.client.post(reverse('post_new'), { 'title': 'New title', 'body': 'New text', 'author': self.user, }) Группа в ВК "Django_Python" 154 Chapter 6: Forms self.assertEqual(response.status_code, ) self.assertContains(response, 'New title') self.assertContains(response, 'New text') def test_post_update_view(self): response = self.client.post(reverse('post_edit', args=' '), { 'title': 'Updated title', 'body': 'Updated text', }) self.assertEqual(response.status_code, ) def test_post_delete_view(self): response = self.client.get( reverse('post_delete', args=' ')) self.assertEqual(response.status_code, ) Мы ожидаем, что URL нашего теста будет в post / 1 /, поскольку там только один пост, а 1 его первичный ключ, который Django автоматически добавляет для нас. Чтобы протестировать создание представления, мы создаем новый ответ и затем проверяем, что ответ проходит (код состояния 200) и содержит наш новый заголовок и основной текст. Для просмотра обновлений мы обращаемся к первому сообщению, в котором в качестве единственного аргумента указан pk, равный 1, и подтверждаем, что оно приводит к перенаправлению 302. Наконец, мы проверяем наше представление удаления, подтверждая, что если мы удаляем сообщение, код состояния 200 успех. Всегда можно добавить больше тестов, но это, по крайней мере, охватывает все наши новые функциональные возможности. Группа в ВК "Django_Python" 155 Chapter 6: Forms Заключение В небольшом количестве кода мы создали приложение blog которое позволяет создавать, читать, обновлять и удалять сообщения в блоге. Эта основная функциональность известна под аббревиатурой CRUD: Create-Read-Update-Delete. Хотя есть несколько способов достичь этой же функциональности-мы могли бы использовать представления на основе функций или написать наши собственные представления на основе классов-мы продемонстрировали, как мало кода требуется в Django, чтобы это произошло. В следующей главе мы добавим учетные записи пользователей, а также функции входа, выхода и регистрации. Группа в ВК "Django_Python" Глава 7: Учетные Записи Пользователей На данный момент мы создали работающее приложение блога, которое использует формы, но нам не хватает основной части большинства веб-приложений: аутентификации пользователей.Реализация правильной аутентификации пользователя очень сложна; на этом пути есть много проблем с безопасностью, так что вы действительно не захотите реализовывать это самостоятельно. К счастью, Django поставляется с мощной встроенной системой аутентификации пользователей, которую мы можем использовать.Каждый раз, когда вы создаете новый проект, по умолчанию Django устанавливает приложение auth, которое предоставляет нам объект User, содержащий: • username • password • email • first_name • last_name Мы будем использовать этот объект User для реализации входа в систему, выхода из системы и регистрации в нашем приложении блога. Login Django предоставляет нам представление по умолчанию для страницы входа через LoginView. Все, что нам нужно добавить, это urlpattern на уровне проекта для системы аутентификации, шаблон входа в систему и небольшое обновление нашего файла settings.py. Сначала обновите файл urls.py уровня проекта. Мы разместим наши страницы входа и выхода в accounts/ URL. Это однострочное дополнение в предпоследней строке. Группа в ВК "Django_Python" Chapter 7: User Accounts 157 Code # blog_project/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('accounts/', include('django.contrib.auth.urls')), path('', include('blog.urls')), ] Как отмечается в документации к LoginView, по умолчанию Django будет искать в папке с шаблонами, с именем registration , файл с именем login.html для формы входа. Таким образом, нам нужно создать новый каталог с именем Registration и необходимым файлом внутри него. В командной строке введите Control-C, чтобы выйти из нашего локального сервера. Затем введите следующее: Command Line (blog) $ mkdir templates/registration (blog) $ touch templates/registration/login.html Теперь введите следующий код шаблона для нашего вновь созданного файла. Группа в ВК "Django_Python" Chapter 7: User Accounts 158 Code <!-- templates/registration/login.html --> {% extends 'base.html' %} {% block content %} <h2>Login</h2> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Login</button> </form> {% endblock content %} Мы используем HTML теги <form> </ form> и указываем метод POST, так как мы отправляем данные на сервер (мы будем использовать GET, если запрашиваем данные, например, в форме поисковой системы). Мы добавляем {% csrf_token%} из соображений безопасности, а именно для предотвращения атаки XSS. Содержимое формы выводится между тегами абзаца благодаря {{form.as_p}}, а затем мы добавляем кнопку «submit».На последнем шаге нам нужно указать, куда перенаправить пользователя при успешном входе в систему. Мы можем установить это с помощью параметра LOGIN_REDIRECT_URL. Внизу файла settings.py добавьте следующее: Code # settings.py LOGIN_REDIRECT_URL = 'home' Теперь пользователь будет перенаправлен на шаблон "home", который является нашей домашней страницей. На данный момент мы закончили! Если теперь снова запустить сервер Django с python manage.py runserver и перейдите на нашу страницу входа: Группа в ВК "Django_Python" 159 Chapter 7: User Accounts http://127.0.0.1:8000/accounts/login/ You’ll see the following: Login page После ввода регистрационных данных для нашей учетной записи суперпользователя, мы перенаправлены на главную страницу. Обратите внимание, что мы не добавили никакой логики представления и не создавали модель базы данных, потому что система Django auth предоставляется для нас автоматически. Спасибо Джанго! Обновленние домашней страницы Давайте обновим наш шаблон base.html, чтобы мы могли отображать сообщение пользователям независимо от того, вошли они в систему или нет. Мы можем использовать для этого атрибут is_authenticated.На данный момент мы можем просто разместить этот код на видном месте. Позже мы можем стилизовать его более подходящим образом. Обновите файл base.html, добавив новый код, начинающийся с закрывающего тега </ header>. Группа в ВК "Django_Python" Chapter 7: User Accounts Code <!-- templates/base.html --> ... </header> {% if user.is_authenticated %} <p>Hi {{ user.username }}!</p> {% else %} <p>You are not logged in.</p> <a href="{% url 'login' %}">login</a> {% endif %} {% block content %} {% endblock content %} Если пользователь вошел в систему, мы передаем ему привет по имени, если нет, мы предоставляем ссылку на нашу недавно созданную страницу входа. Группа в ВК "Django_Python" 160 161 Chapter 7: User Accounts Homepage logged in Это сработало! Моего суперпользователя зовут wsv, вот что я вижу на странице. Ссылка на выход Мы добавили логику страницы шаблона для пользователей, вышедших из системы, но ... как нам выйти сейчас? Мы могли бы зайти в панель администратора и сделать это вручную, но есть и лучший способ. Давайте вместо этого добавим ссылку выхода из системы, которая перенаправляет на домашнюю страницу. Благодаря системе аутентификации Django этого достичь очень просто. В нашем файле base.html добавьте однострочную ссылку {% url 'logout'%} для выхода из системы. Группа в ВК "Django_Python" Chapter 7: User Accounts 162 Command Line <!-- templates/base.html--> ... {% if user.is_authenticated %} <p>Hi {{ user.username }}!</p> <p><a href="{% url 'logout' %}">logout</a></p> {% else %} ... Это все, что нам нужно сделать, так как необходимое представление предоставляется нам приложением Django auth. Нам нужно указать, куда перенаправить пользователя при выходе из системы. Обновите файл settings.py, чтобы предоставить ссылку для перенаправления, которая соответственно называется LOGOUT_-REDIRECT_URL. Мы можем добавить его прямо рядом с нашим перенаправлением входа в систему, чтобы нижняя часть файла выглядела следующим образом: Code # blog_project/settings.py LOGIN_REDIRECT_URL = 'home' LOGOUT_REDIRECT_URL = 'home' Если вы обновите домашнюю страницу, то увидите, что на ней теперь есть ссылка «Выйти» для зарегистрированных пользователей. Группа в ВК "Django_Python" 163 Chapter 7: User Accounts Homepage logout link И нажав на нее, вы вернетесь на домашнюю страницу с ссылкой для входа. Группа в ВК "Django_Python" 164 Chapter 7: User Accounts Homepage logged out Попробуйте несколько раз войти в систему и выйти из нее под своей учетной записью. Регистрация Нам нужно написать собственное представление для страницы регистрации для регистрации новых пользователей, но Django предоставляет нам класс формы UserCreationForm, чтобы упростить задачу. По умолчанию он имеет три поля: имя пользователя, пароль1, и пароль 2. Есть много способов организовать ваш код и структуру URL для надежной системы аутентификации пользователей. Здесь мы создадим новое приложение, accounts, для нашей страницы регистрации. Группа в ВК "Django_Python" Chapter 7: User Accounts Command Line (blog) $ python manage.py startapp accounts Добавьте новое приложение в параметр INSTALLED_APPS в settings.py файл. Code # blog_project/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog', 'accounts', ] Затем добавьте URL уровня проекта, указывающий на это новое приложение, ниже включаемого встроенного приложения auth . Группа в ВК "Django_Python" 165 Chapter 7: User Accounts 166 Code # blog_project/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('accounts/', include('django.contrib.auth.urls')), path('accounts/', include('accounts.urls')), path('', include('blog.urls')), ] Здесь важен порядок наших URL, потому что Django читает этот файл сверху вниз. Поэтому, когда мы запрашиваем их /accounts/signup url, Django сначала смотрит в auth, не находит его, а затем переходит к приложению account. Давайте продолжим и создадим наш файл account / urls.py. Command Line (blog) $ touch accounts/urls.py И добавьте следующий код: Группа в ВК "Django_Python" Chapter 7: User Accounts 167 Code # acounts/urls.py from django.urls import path from . import views urlpatterns = [ path('signup/', views.SignUpView.as_view(), name='signup'), ] Мы используем еще не созданное представление с именем RegistrationView, которое, как мы уже знаем, основано на классе, так как оно написано заглавными буквами и имеет суффикс as_view (). Его путь просто signup/ поэтому общим путем будет accounts/ signup/.Сейчас представление, которое использует встроенную UserCreationForm и универсальный CreateView. Code # accounts/views.py from django.contrib.auth.forms import UserCreationForm from django.urls import reverse_lazy from django.views import generic class SignUpView(generic.CreateView): form_class = UserCreationForm success_url = reverse_lazy('login') template_name = 'signup.html' Мы создаем подкласс общего представления на основе классов CreateView в нашем классе SignUpView. Мы указываем использование встроенного UserCreationForm и еще не созданного шаблона в signup.html. И мы используем reverse_lazy, чтобы перенаправить пользователя на страницу входа в систему после успешной регистрации. Группа в ВК "Django_Python" Chapter 7: User Accounts 168 Зачем здесь использовать reverse_lazy вместо reverse? Причина в том, что для всех общих представлений на основе классов URL не загружаются при импорте файла, поэтому мы должны использовать ленивую форму реверса, чтобы загрузить их позже, когда они станут доступны. Теперь давайте добавим signup.html в нашу папку шаблонов уровня проекта: Command Line (blog) $ touch templates/signup.html Добавьте, а затем заполните его кодом ниже. Code <!-- templates/signup.html --> {% extends 'base.html' %} {% block content %} <h2>Sign up</h2> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Sign up</button> </form> {% endblock %} Этот формат очень похож на то, что мы делали раньше. Мы расширяем наш базовый шаблон сверху, размещаем нашу логику между тегами <form> </ form>, используем csrf_token для безопасности, отображаем содержимое формы в тегах абзаца с помощью form.as_p и добавляем кнопку отправки. Группа в ВК "Django_Python" 169 Chapter 7: User Accounts Теперь мы закончили! Чтобы проверить это, перейдите на нашу новую страницу: http://127.0.0.1:8000/accounts/signup/ Django signup page Обратите внимание, что по умолчанию в Django есть много лишнего текста. Мы можем настроить это, используя что-то вроде встроенной платформы сообщений, но пока попробуйте форму. Я создал нового пользователя с именем «william» и после отправки был перенаправлен на страницу входа. Затем, после успешного входа в систему с использованием моего нового пользователя и пароля, я был перенаправлен на домашнюю страницу с нашим персонализированным приветствием «Привет, имя пользователя». Группа в ВК "Django_Python" 170 Chapter 7: User Accounts Homepage for user wsvincent Таким образом, наш конечный поток: Signup -> Login -> Homepage. И, конечно, мы можем настроить это, как мы хотим. RegistrationView перенаправляет на вход в систему, потому что мы установили success_url = reverse_lazy ('login'). Страница входа перенаправляется на домашнюю страницу, потому что в нашем файле settings.py мы установили LOGIN_REDIRECT_URL = 'home'. На первый взгляд может показаться сложным отслеживать все различные части проекта Django. Это нормально. Но я обещаю, что со временем они станут более понятными. Bitbucket Прошло много времени с тех пор, как мы сделали git commit. Давайте сделаем это, а затем вставим копию нашего кода в Bitbucket. Группа в ВК "Django_Python" Chapter 7: User Accounts 171 Сначала проверьте всю новую работу, которую мы сделали с git status. Command Line (blog) $ git status Затем добавьте новый контент. Command Line (blog) $ git commit -m 'forms and user accounts' Создайте новый репозиторий на Bitbucket, который вы можете назвать как угодно. Я выберу название blog-app. Поэтому после создания нового репо на сайте Bitbucket я могу ввести следующие две команды. Обязательно замените мое имя пользователя wsvincent вашим именем пользователя из Bitbucket. Command Line (blog) $ git remote add origin git@bitbucket.org:wsvincent/blog-app.git (blog) $ git push -u origin master Все сделано! Теперь мы можем развернуть наше новое приложение на Heroku. Heroku конфигурация Это наше третье развертывание приложения. Как и в нашем приложении Message Board (доска сообщений), есть четыре изменения, которые нам нужно сделать, чтобы его можно было развернуть на Heroku. • обновить Pipfile.lock • новый Procfile • установить gunicorn Группа в ВК "Django_Python" Chapter 7: User Accounts 172 • обновить settings.py Мы укажем версию Python в нашем Pipfile, а затем запустим pipenv, чтобы применить ее к Pipfile.lock. Мы добавим Procfile, который является файлом конфигурации, специфичным для Heroku, установим gunicorn для запуска в качестве нашего рабочего веб-сервера вместо локального сервера Django и, наконец, обновим ALLOWED_HOSTS, чтобы любой мог просматривать наше приложение. Откройте Pipfile в текстовом редакторе и в нижней части добавьте следующие две строки. Code # Pipfile [requires] python_version = " . " Мы используем здесь «3.6», А не более конкретный «3.6.4», так что наше приложение автоматически обновляется до самой последней версии Python 3.6x для Heroku.Теперь запустите pipenv lock, чтобы обновить наш Pipfile.lock, поскольку Heroku будет использовать его для создания новой среды на серверах Heroku для нашего приложения. Command Line (blog) $ pipenv lock Создайте новый файл Procfile . Command Line (blog) $ touch Procfile В вашем текстовом редакторе добавьте следующую строку в Procfile. Это говорит Heroku использовать gunicorn, а не локальный сервер, который не подходит для производства. Группа в ВК "Django_Python" Chapter 7: User Accounts 173 Code web: gunicorn blog_project.wsgi --log-file - Сейчас установите gunicorn. Command Line (blog) $ pipenv install gunicorn Наконец, обновите ALLOWED_HOSTS, чтобы принять все домены, которые представлены звездочкой *. Code # blog_project/settings.py ALLOWED_HOSTS = ['*'] Мы можем зафиксировать(commit) наши новые изменения и отправить их в Bitbucket. Command Line (blog) $ git status (blog) $ git add -A (blog) $ git commit -m 'Heroku config files and updates' (blog) $ git push -u origin master Развертывание на Heroku Для развертывания в Heroku сначала убедитесь, что вы вошли в свою существующую учетную запись Heroku. Группа в ВК "Django_Python" Chapter 7: User Accounts 174 Command Line (blog) $ heroku login Затем выполните команду create, которая скажет Heroku создать новый контейнер для нашего приложения. Если вы просто запустите heroku create, Heroku назначит вам произвольное имя, однако вы можете указать произвольное имя, но оно должно быть уникальным в Heroku. Другими словами, так как я выбираю название dfb-blog, вы не сможете дать такое же имя. Вам нужна другая комбинация букв и цифр. Command Line (blog) $ heroku create dfb-blog Теперь настройте git таким образом, чтобы при переходе на Heroku он переходил к новому имени приложения (заменив dfb-blog своим произвольным именем). Command Line (blog) $ heroku git:remote -a dfb-blog Теперь мы должны сделать еще один шаг, когда у нас есть статические файлы, в нашем случае это CSS. Django не поддерживает обслуживание статических файлов в производстве, однако проект WhiteNoise поддерживает. Итак, давайте установим его. Command Line (blog) $ pipenv install whitenoise Затем нам нужно обновить наши static настройки, чтобы они были использованы в производстве. В вашем текстовом редакторе откройте settings.py. Добавьте whitenoise в INSTALLED_APPS над встроенным приложением staticfiles, а также в MIDDLEWARE в третьей строке. Порядок имеет значение как для INSTALLED_APPS, так и для MIDDLEWARE. Внизу файла добавьте новые строки для STATIC_ROOT и STATICFILES_STORAGE. Это должно выглядеть следующим образом. Группа в ВК "Django_Python" Chapter 7: User Accounts Code # blog_project/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'whitenoise.runserver_nostatic', # new! 'django.contrib.staticfiles', 'blog', 'accounts', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', # new! 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ... STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') # new! Группа в ВК "Django_Python" 175 176 Chapter 7: User Accounts STATIC_URL = '/static/' STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' \ # new! Не забудьте добавить и зафиксировать новые изменения. Затем нажмите на Bitbucket. Command Line (blog) $ git add -A (blog) $ git commit -m 'Heroku config' (blog) $ git push origin master Наконец, мы можем перенести наш код в Heroku и добавить веб-процесс, чтобы dyno работал. Command Line (blog) $ git push heroku master (blog) $ heroku ps:scale web= URL вашего нового приложения будет в выходных данных командной строки, или вы можете запустить heroku open, чтобы найти его. Мой находится по адресу https://df bblog.herokuapp.com/. Heroku site Группа в ВК "Django_Python" Chapter 7: User Accounts 177 Заключение С минимальным количеством кода, фреймворк Django позволил нам создать поток аутентификации пользователя для входа в систему, выхода из системы и регистрации. Он позаботился о многих проблемах безопасности, которые могут возникнуть, если вы попытаетесь создать собственный поток аутентификации пользователя с нуля. Группа в ВК "Django_Python" Глава 8: Custom User Model Встроенная в Django модель User позволяет нам сразу начать работать с пользователями, как мы только что делали с нашим приложением блога в предыдущих главах. Однако официальная документация Django настоятельно рекомендует использовать пользовательскую модель для новых проектов. Причина в том, что если вы хотите внести какие-либо изменения в пользовательскую модель в будущем например, добавив поле возраста с помощью custom user model с самого начала это довольно просто. Но если вы не создадите custom user model, обновление модели пользователя по умолчанию в существующем проекте Django будет очень и очень сложным. Поэтому всегда используйте пользовательскую модель для всех новых проектов Django. Однако пример официальной документации не совсем то, что рекомендуют многие эксперты Django. Он использует довольно сложный AbstractBaseUser, чем если мы просто используем AbstractUser, все гораздо проще и все еще настраиваемо. Это подход, который мы будем использовать в этой главе, где мы правильно запускаем новое приложение Newspaper(Газета) с пользовательской моделью. Выбор приложения газеты отдает дань уважения корням Django в качестве веб фреймворка, созданного для редакторов и журналистов в Lawrence Journal-World. Настройка Первым шагом является создание нового проекта Django из командной строки. Нам нужно сделать несколько вещей: • создать и перейти в новый каталог для нашего кода • создать новую виртуальную среду news • установка Django • создать новый проект Django newspaper_project Группа в ВК "Django_Python" Chapter 8: Custom User Model 179 • сделать новое приложение users Мы называем наше приложение для управления пользователями users, но вы также увидите его часто называемыми учетными записями в открытом исходном коде. Фактическое имя не имеет значения, если вы последовательны в обращении к нему на протяжении всего проекта. Вот команды для запуска: Command Line $ cd ~/Desktop $ mkdir news $ cd news $ pipenv install django $ pipenv shell (news) $ django-admin startproject newspaper_project . (news) $ python manage.py startapp users (news) $ python manage.py runserver Обратите внимание, что мы не выполняли миграцию для настройки базы данных. Важно подождать, пока мы не создадим нашу новую пользовательскую модель, прежде чем делать это, учитывая, насколько тесно связана пользовательская модель с остальной частью Django. Если вы перейдете к http://127.0.0.1:8000 вы увидите знакомый экран приветствия Django. Группа в ВК "Django_Python" 180 Chapter 8: Custom User Model Welcome page Custom User Model Создание нашей собственной модели пользователя требует четыре шага: • обновить settings.py • создание новой CustomUser модели • создание новой формы для UserCreation и UserChangeForm • обновить админку сайта Группа в ВК "Django_Python" Chapter 8: Custom User Model 181 В settings.py мы добавим приложение users в наш INSTALLED_APPS. Затем в нижней части файла используйте конфигурацию AUTH_USER_MODEL, чтобы указать Django использовать нашу новую пользовательскую модель вместо встроенной модели User. Мы будем называть нашу пользовательскую пользовательскую модель CustomUser так, поскольку она существует в нашем users приложении, мы называем ее users.CustomUser. Code # newspaper_project/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'users', # new ] ... AUTH_USER_MODEL = 'users.CustomUser' Теперь обновите users / models.py новой моделью User, которую мы будем называть CustomUser. Мы также добавим наше первое дополнительное поле для «age»(возраста) наших пользователей. Мы можем использовать PositiveIntegerField от Django, что означает, что целое число должно быть либо положительным, либо нулевым. Группа в ВК "Django_Python" Chapter 8: Custom User Model 182 Code # users/models.py from django.contrib.auth.models import AbstractUser from django.db import models class CustomUser(AbstractUser): age = models.PositiveIntegerField(default= ) Это действительно весь код, который нам нужен! Поскольку мы расширяем AbstractUser, наш CustomUser является в основном копией модели User по умолчанию. Единственное обновление - наше поле age. Формы Если мы на шаг отступим назад, как мы будем взаимодействовать с нашей новой моделью CustomUser? Первый случай, когда пользователь регистрирует новую учетную запись на нашем сайте. Другой находится в приложении администратора, которое позволяет нам, как суперпользователям, изменять существующих пользователей. Поэтому нам нужно обновить две встроенные формы для этой функции: UserCreationForm и UserChangeForm. Остановите локальный сервер с помощью Control + c и создайте новый файл в приложении users с именем forms.py. Command Line (news) $ touch users/forms.py Мы обновим его следующим кодом, чтобы расширить существующие формы UserCreationForm и UserChangeForm. Группа в ВК "Django_Python" Chapter 8: Custom User Model 183 Code # users/forms.py from django import forms from django.contrib.auth.forms import UserCreationForm, UserChangeForm from .models import CustomUser class CustomUserCreationForm(UserCreationForm): class Meta(UserCreationForm.Meta): model = CustomUser fields = UserCreationForm.Meta.fields class CustomUserChangeForm(UserChangeForm): class Meta: model = CustomUser fields = UserChangeForm.Meta.fields Для обеих новых форм мы устанавливаем модель CustomUser и используем поля по умолчанию с помощью Meta.fields. TПоначалу понятие полей в форме может сбивать с толку, поэтому давайте уделим немного времени, чтобы изучить его дальше. Наша модель CustomUser содержит все поля модели пользователя по умолчанию и наше дополнительное поле возраста, которое мы установили. Но что это за поля по умолчанию? Оказывается, есть много, включая имя пользователя, имя, фамилия, адрес электронной почты, пароль, группы и многое другое. Тем не менее, когда пользователь регистрирует новую учетную запись в Django, форма по умолчанию запрашивает только имя пользователя, адрес электронной почты и пароль. Группа в ВК "Django_Python" Chapter 8: Custom User Model 184 Это говорит нам о том, что настройка по умолчанию для полей в UserCreationForm - это просто имя пользователя, адрес электронной почты и пароль, хотя доступно еще много полей. Это может быть не понятно, так как для правильного понимания форм и моделей требуется некоторое время. В следующей главе мы создадим наши собственные страницы регистрации, входа и выхода, которые будут более четко связывать нашу модель CustomUser и формы. Так что держитесь! Последний шаг это обновить наш файл admin.py, так как Admin тесно связан с моделью пользователя по умолчанию. Мы расширим существующий класс UserAdmin для использования нашей новой модели CustomUser и двух новых форм. Code # users/admin.py from django.contrib import admin from django.contrib.auth.admin import UserAdmin from .forms import CustomUserCreationForm, CustomUserChangeForm from .models import CustomUser class CustomUserAdmin(UserAdmin): add_form = CustomUserCreationForm form = CustomUserChangeForm list_display = ['email', 'username', 'age'] model = CustomUser admin.site.register(CustomUser, CustomUserAdmin) Обратите внимание, что наш CustomUserAdmin также имеет параметр list_display, чтобы он отображал только поля email, username и age, даже если на данный момент в модели CustomUser их гораздо больше. Группа в ВК "Django_Python" Chapter 8: Custom User Model 185 Хорошо, мы закончили! Запустите makemigrations и выполните миграцию в первый раз, чтобы создать новую базу данных, использующую пользовательскую модель. Command Line (news) $ python manage.py makemigrations (news) $ python manage.py migrate Superuser Давайте создадим учетную запись суперпользователя, чтобы подтвердить, что все работает, как ожидалось. В командной строке введите следующую команду и выполните запросы. Command Line (news) $ python manage.py createsuperuser Тот факт, что это работает, является первым доказательством того, что наша пользовательская модель работает, как и ожидалось. Давайте посмотрим на админку, чтобы быть более уверенным. Запустите веб-сервер. Command Line (news) $ python manage.py runserver Затем перейдите в админку по адресу http://127.0.0.1:8000/admin и войдите в систему. Группа в ВК "Django_Python" 186 Chapter 8: Custom User Model Admin page Если вы нажмете на ссылку "Users", вы увидите свою учетную запись суперпользователя. Обратите внимание, что три поля, которые мы видим, электронная почта, имя пользователя и возраст, так как мы установили, как list_-display в нашем CustomUserAdmin. Группа в ВК "Django_Python" 187 Chapter 8: Custom User Model Admin one user Заключение Теперь, когда наша пользовательская модель готова, мы можем сосредоточиться на создании остальной части нашего приложения Newspaper . В следующей главе мы настроим страницы регистрации, входа и выхода. Группа в ВК "Django_Python" Глава 9: Аутентификация пользователя Теперь, когда у нас есть рабочая пользовательская модель, мы можем добавить функциональность, которая нужна каждому веб-сайту: возможность регистрироваться, входить и выходить из системы. Django предоставляет все необходимое для входа и выхода, но нам нужно будет создать собственную форму для регистрации новых пользователей. Мы также создадим базовую домашнюю страницу со ссылками на все три функции, поэтому нам не придется каждый раз вводить URL вручную. Templates(Шаблоны) По умолчанию загрузчик шаблонов Django ищет шаблоны в вложенной структуре в каждом приложении. Таким образом, шаблон home.html для пользователей должен быть расположен по адресу users / templates / users / home.html. Но подход к папке с шаблонами на уровне проекта более понятен и лучше масштабируется, поэтому мы будем использовать его.Давайте создадим новый каталог шаблонов и внутри него папку registration, в которой Django будет искать шаблон login. Command Line (news) $ mkdir templates (news) $ mkdir templates/registration Теперь нам нужно сообщить Django об этом новом каталоге, обновив конфигурацию для 'DIRS' в settings.py. Это изменение в одну строку. Группа в ВК "Django_Python" Chapter 9: User Authentication 189 Code # newspaper_project/settings.py TEMPLATES = [ { ... 'DIRS': [os.path.join(BASE_DIR, 'templates')], ... } ] Если вы думаете о том, что происходит, когда вы входите или выходите из сайта, вы сразу же перенаправляетесь на следующую страницу. Нам нужно указать Django, куда отправлять пользователей в каждом конкретном случае. Делают это настройки LOGIN_REDIRECT_URL и LOGOUT_REDIRECT_URL. Мы настроим оба, чтобы перенаправить на нашу домашнюю страницу, которая будет иметь названный URL «home».Помните, что когда мы создаем наши маршруты URL, у нас есть возможность добавить имя для каждого из них. Поэтому, когда мы создадим URL домашней страницы, мы обязательно назовем его «home».Добавьте эти две строки внизу файла settings.py. Code # newspaper_project/settings.py LOGIN_REDIRECT_URL = 'home' LOGOUT_REDIRECT_URL = 'home' Now we can create four new templates: Группа в ВК "Django_Python" Chapter 9: User Authentication 190 Command Line (news) $ touch templates/registration/login.html (news) $ touch templates/base.html (news) $ touch templates/home.html (news) $ touch templates/signup.html Вот HTML код для каждого используемого файла. base.html будет унаследован каждым другим шаблоном в нашем проекте. Используя блок типа {% block content %}, мы можем позже переопределить содержимое только в этом месте в других шаблонах. Code <!-- templates/base.html --> <!DOCTYPE html> <html> <head> <meta charset="utf- "> <title>Newspaper App</title> </head> <body> <main> {% block content %} {% endblock %} </main> </body> </html> Группа в ВК "Django_Python" Chapter 9: User Authentication Code <!-- templates/home.html --> {% extends 'base.html' %} {% block title %}Home{% endblock %} {% block content %} {% if user.is_authenticated %} Hi {{ user.username }}! <p><a href="{% url 'logout' %}">logout</a></p> {% else %} <p>You are not logged in</p> <a href="{% url 'login' %}">login</a> | <a href="{% url 'signup' %}">signup</a> {% endif %} {% endblock %} Code <!-- templates/registration/login.html --> {% extends 'base.html' %} {% block title %}Login{% endblock %} {% block content %} <h2>Login</h2> <form method="post"> {% csrf_token %} {{ form.as_p }} Группа в ВК "Django_Python" 191 Chapter 9: User Authentication 192 <button type="submit">Login</button> </form> {% endblock %} Code <!-- templates/signup.html --> {% extends 'base.html' %} {% block title %}Sign Up{% endblock %} {% block content %} <h2>Sign up</h2> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Sign up</button> </form> {% endblock %} Наши шаблоны готовы. Тем не менее перейдем к нашим URL и представлениям. URLs Давайте начнем с URL-маршрутов. В нашем файле urls.py уровня проекта мы хотим, чтобы наш шаблон home.html отображался в качестве домашней страницы. Но мы пока не хотим создавать приложение для выделенных страниц, поэтому мы можем использовать быстрый доступ импортируя TemplateView и установки имени шаблона прямо в нашем шаблоне URL. Группа в ВК "Django_Python" Chapter 9: User Authentication 193 Далее мы хотим «включить» и приложение пользователя, и встроенное приложение аутентификации. Причина в том, что встроенное приложение авторизации уже предоставляет представления и URL для входа и выхода из системы. Но для регистрации нам нужно создать наш собственное представление и URL. Чтобы обеспечить согласованность наших URL маршрутов, мы размещаем в users/, поэтому возможными URL будут / users / login, / users / logout и / users / signup. Code # newspaper_project/urls.py from django.contrib import admin from django.urls import path, include from django.views.generic.base import TemplateView urlpatterns = [ path('', TemplateView.as_view(template_name='home.html'), name='home'), path('admin/', admin.site.urls), path('users/', include('users.urls')), path('users/', include('django.contrib.auth.urls')), ] Создайте файл urls.py в приложении users. Command Line (news) $ touch users/urls.py Обновите users / urls.py следующим кодом: Группа в ВК "Django_Python" Chapter 9: User Authentication 194 Code # users/urls.py from django.urls import path from . import views urlpatterns = [ path('signup/', views.SignUp.as_view(), name='signup'), ] Последний шаг это наш файл views.py, который будет содержать логику для нашей формы регистрации. Здесь мы используем общий CreateView от Django и говорим ему использовать нашу CustomUserCreation-Form и перенаправлять на вход в систему после успешной регистрации пользователя, и что наш шаблон называется signup.html. Code # users/views.py from django.urls import reverse_lazy from django.views import generic from .forms import CustomUserCreationForm class SignUp(generic.CreateView): form_class = CustomUserCreationForm success_url = reverse_lazy('login') template_name = 'signup.html' Отлично! Мы это сделали. Давайте все проверим.Запустите сервер с помощью python manage.py runserver и перейдите на домашнюю страницу по адресу http://127.0.0.1:8000/. Группа в ВК "Django_Python" 195 Chapter 9: User Authentication Homepage logged in Мы вошли в систему администратора в предыдущей главе, поэтому вы должны увидеть персональное приветствие. Нажмите на ссылку выхода. Homepage logged out Теперь мы на главной странице. Идем вперед и нажимаем на ссылку входа в систему и используем свои учетные данные суперпользователя. Login После успешного входа в систему вы будете перенаправлены обратно на главную страницу и увидите то же персонализированное приветствие. Работает! Группа в ВК "Django_Python" 196 Chapter 9: User Authentication Homepage logged in Теперь используйте ссылку выхода, чтобы вернуться на главную страницу, и на этот раз нажмите на ссылку регистрации. Homepage logged out Вы будете перенаправлены на страницу регистрации(signup ). Signup page Создайте нового пользователя. Мой называется testuser. После успешной отправки формы вы будете перенаправлены на страницу входа(login ). Войдите в систему с новым пользователем, и вы снова будете Группа в ВК "Django_Python" 197 Chapter 9: User Authentication перенаправлены на домашнюю страницу с персонализированным приветствием для нового пользователя. Homepage for testuser Все работает, как и ожидалось. Admin Давайте также войдем в admin для просмотра наших двух учетных записей пользователей. Перейдите к: http://127.0.0.1:8000/admin и … Группа в ВК "Django_Python" 198 Chapter 9: User Authentication Admin login wrong Что это такое? Почему мы не можем войти? Потому что мы вошли в систему с нашей новой учетной записью тестового пользователя, а не с учетной записью суперпользователя. Только учетная запись суперпользователя имеет разрешения для входа в систему администратора! Поэтому используйте свою учетную запись суперпользователя для входа. После этого вы увидите обычную домашнюю страницу администратора. Нажмите на Users и вы увидите наших двух пользователей: того, которого мы только что создали, и ваше предыдущее имя суперпользователя (мое-wsv). Группа в ВК "Django_Python" 199 Chapter 9: User Authentication Users in the Admin Все работает, но вы можете заметить, что для нашего тестового пользователя нет поля электронной почты. Почему так? Ну, оглянитесь на страницу регистрации в users/signup/, и вы увидите, что она запрашивает только имя пользователя и пароль, а не электронную почту! Именно так работает настройка Django по умолчанию. Однако мы можем легко изменить это. Вернемся к нашему файлу users/forms.py. Сейчас это выглядит так: Группа в ВК "Django_Python" Chapter 9: User Authentication 200 Code # users/forms.py from django import forms from django.contrib.auth.forms import UserCreationForm, UserChangeForm from .models import CustomUser class CustomUserCreationForm(UserCreationForm): class Meta(UserCreationForm.Meta): model = CustomUser fields = UserCreationForm.Meta.fields class CustomUserChangeForm(UserChangeForm): class Meta: model = CustomUser fields = UserChangeForm.Meta.fields Мы используем Meta.поля, которые просто отображают настройки по умолчанию имя пользователя/пароль. Но мы также можем явно установить, какие поля мы хотим отобразить, поэтому давайте обновим его, чтобы запросить имя пользователя/адрес электронной почты/пароль, установив его в ('username', 'email',). Нам не нужно включать поле пароля, потому что оно необходимо! Но все остальные поля могут быть настроены, как мы выберем. Группа в ВК "Django_Python" Chapter 9: User Authentication Code # users/forms.py ... class CustomUserCreationForm(UserCreationForm): class Meta(UserCreationForm.Meta): model = CustomUser fields = ('username', 'email', ) # new class CustomUserChangeForm(UserChangeForm): class Meta: model = CustomUser fields = ('username', 'email', ) # new Теперь, если вы попробуете страницу регистрации снова на http://127.0.0.1:8000/users/ signup/ вы можете увидеть дополнительное поле “Email address”. Группа в ВК "Django_Python" 201 202 Chapter 9: User Authentication New sign up page Зарегистрируйтесь с новой учетной записью пользователя. Я назвал свою testuser2 с адресом электронной почты testuser2@email.com. Если мы затем переключимся обратно на страницу администратора и войдем с помощью учетной записи суперпользователя, мы обнаружим теперь три пользователя. Группа в ВК "Django_Python" 203 Chapter 9: User Authentication Three users in the Admin Аутентификации пользователя Django требует немного настройки, но вы должны начать видеть, что он также предоставляет нам невероятную гибкость для настройки регистрации и входа в систему именно так, как мы хотим. Заключение До сих пор наше приложение Newspaper имеет кастомную модель пользователя и рабочие страницы регистрации, входа и выхода. Но вы, возможно, заметили, что наш сайт выглядит не очень хорошо. В следующей главе мы добавим Bootstrap для стилизации и создадим приложение Pages. Группа в ВК "Django_Python" Глава ��: Bootstrap Веб-разработка требует много навыков. Вам нужно не только программировать сайт для корректной работы, но так же пользователи ожидают, что он будет хорошо выглядеть. Когда вы создаете все с нуля, может быть крайне тяжело добавить все необходимые HTML / CSS для красивого сайта. К счастью, есть Bootstrap, самая популярная платформа для создания адаптивных проектов, ориентированных на мобильные устройства. Вместо того, чтобы писать все наши собственные CSS и JavaScript для общих функций макета веб-сайта, мы можем вместо этого положиться на Bootstrap, чтобы сделать тяжелую работу. Это означает, что с небольшим количеством кода с нашей стороны мы сможем быстро создать великолепно выглядящие сайты. И если мы хотим вносить изменения по мере развития проекта, то при необходимости Bootstrap легко переопределить. Если вы хотите сосредоточиться на функциональности проекта, а не на дизайне, Bootstrap отличный выбор. Вот почему мы будем использовать его здесь. Приложение Pages В предыдущей главе мы отображали нашу домашнюю страницу, включив логику просмотра в наш файл urls.py. Хотя этот подход работает, он кажется мне несколько хакерским, и, конечно, он не масштабируется, поскольку веб-сайт со временем растет. Это также, вероятно, несколько сбивает с толку новичков в Django. Вместо этого мы можем и должны создать специальное приложение для всех наших статических страниц. Это сделает наш код красивым и организованным далее. В командной строке используйте команду startapp для создания нашего нового приложения pages. Если сервер все еще работает, вам может потребоваться сначала нажать Control + c, чтобы выйти из него. Группа в ВК "Django_Python" 205 Chapter 10: Bootstrap Command Line (news) $ python manage.py startapp pages Затем сразу обновите наш файл settings.py. Я часто забываю делать это, поэтому рекомендуется думать о создании нового приложения как о двухэтапном процессе: запустите команду startapp, а затем обновите INSTALLED_APPS. Code # newspaper_project/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'users', 'pages', # new ] Теперь мы можем обновить наш файл urls.py на уровне проекта. Идем дальше и удалим импорт TemplateView. Мы также обновим маршрут ' ', чтобы включить приложение pages. Группа в ВК "Django_Python" 206 Chapter 10: Bootstrap Code # newspaper_project/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('', include('pages.urls')), path('admin/', admin.site.urls), path('users/', include('users.urls')), path('users/', include('django.contrib.auth.urls')), ] Пришло время добавить нашу домашнюю страницу, что означает стандартные urls/views/ templates. Начнем с файла pages/urls.py . Сначала создайте его. Command Line (news) $ touch pages/urls.py Затем импортируйте наши еще не созданные представления, задайте пути маршрута и убедитесь, что каждый url так же имеет имя(name). Группа в ВК "Django_Python" 207 Chapter 10: Bootstrap Code # pages/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.HomePageView.as_view(), name='home'), ] На данном этапе код views.py должен выглядеть знакомым. Мы используем представление шаблонов на основе классов Django, что означает, что нам нужно только указать template_name, чтобы использовать его. Code # pages/views.py from django.views.generic import TemplateView class HomePageView(TemplateView): template_name = 'home.html' У нас уже есть существующий шаблон home.html . Давайте проверим, что он по прежнему работает должным образом с нашим новым url и представлением. Перейдите к странице http://127.0.0.1:8000/ что бы проверить что все остается неизменным. Группа в ВК "Django_Python" 208 Chapter 10: Bootstrap Homepage logged in Он должен показывать имя вашей учетной записи суперпользователя, которую мы использовали в конце последней главы. Тесты Мы добавили новый код и функциональность, что означает, что пришло время для тестов. Вы никогда не сможете иметь достаточно тестов в своих проектах. Даже если они заранее время, чтобы написать, они всегда экономят время и дают уверенность по мере возрастания сложности проекта. Есть два идеальных момента для добавления тестов: либо перед тем, как писать какой-либо код (разработка на основе тестов), либо сразу после того, как вы добавили новую функциональность, и вам должно это быть понятно. Сейчас наш проект имеет четыре страницы: • home • signup • login • logout Однако нам нужно только проверить первые два. Вход и выход из системы являются частью Django и зависят от внутренних представлений и url маршрутов. Поэтому они уже имеют тестовое покрытие. Если мы внесем существенные изменения в них в будущем, то мы захотели бы добавить тесты и для этого. Но, как правило, вам не нужно добавлять тесты для основной функциональности Django. Группа в ВК "Django_Python" 209 Chapter 10: Bootstrap Поскольку у нас есть URL, шаблоны и представления для каждой из наших двух новых страниц, мы добавим тесты для каждой. SimpleTestCase будет достаточно для тестирования домашней страницы, но страница регистрации использует базу данных, поэтому нам также нужно использовать TestCase. Вот как должен выглядеть код в вашем файле pages/tests.py. Code # pages/tests.py from django.contrib.auth import get_user_model from django.test import SimpleTestCase, TestCase from django.urls import reverse class HomePageTests(SimpleTestCase): def test_home_page_status_code(self): response = self.client.get('/') self.assertEqual(response.status_code, ) def test_view_url_by_name(self): response = self.client.get(reverse('home')) self.assertEqual(response.status_code, ) def test_view_uses_correct_template(self): response = self.client.get(reverse('home')) self.assertEqual(response.status_code, ) self.assertTemplateUsed(response, 'home.html') class SignupPageTests(TestCase): Группа в ВК "Django_Python" 210 Chapter 10: Bootstrap username = 'newuser' email = 'newuser@email.com' def test_signup_page_status_code(self): response = self.client.get('/users/signup/') self.assertEqual(response.status_code, ) def test_view_url_by_name(self): response = self.client.get(reverse('signup')) self.assertEqual(response.status_code, ) def test_view_uses_correct_template(self): response = self.client.get(reverse('signup')) self.assertEqual(response.status_code, ) self.assertTemplateUsed(response, 'signup.html') def test_signup_form(self): new_user = get_user_model().objects.create_user( self.username, self.email) self.assertEqual(get_user_model().objects.all().count(), ) self.assertEqual(get_user_model().objects.all() [ ].username, self.username) self.assertEqual(get_user_model().objects.all() [ ].email, self.email) В верхней строке мы используем get_user_model() для ссылки на нашу пользовательскую модель. Затем для обеих страниц мы тестируем три вещи: Группа в ВК "Django_Python" 211 Chapter 10: Bootstrap • страница существует и возвращает код состояния HTTP 200 • страница использует правильное имя URL в представлении • используется правильный шаблон Наша страница регистрации также имеет форму, поэтому мы должны проверить и это. В тестовой форме test_sign up_ мы проверяем, что когда имя пользователя и адрес электронной почты публикуются (отправляются в базу данных), они соответствуют тому, что хранится в модели CustomUser. Выйдите из локального сервера с Control+c, а затем запустите наши тесты, чтобы подтвердить, что все проходит. Command Line (news) $ python manage.py test Bootstrap Если вы никогда не использовали Bootstrap, вы получите реальное удовольствие от его использования. Он так много делает в таком маленьком коде. Существует два способа добавить Bootstrap в проект: можно загрузить все файлы и обслуживать их локально или использовать Сеть Доставки Контента (CDN). Второй подход проще реализовать, если у вас есть стабильное подключение к интернету, это мы и будем использовать здесь. Bootstrap поставляется со стартовым шаблоном, который включает в себя основные необходимые файлы. В частности, есть четыре, которые мы подключаем: • Bootstrap.css • jQuery.js • Popper.js • Bootstrap.js Группа в ВК "Django_Python" 212 Chapter 10: Bootstrap Вот как должен выглядеть обновленный файл base.html. Как правило, вы должны набирать все примеры кода самостоятельно, но так как это довольно длинный код, здесь его можно скопировать и вставить. Code <!-- templates/base.html --> <!doctype html> <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf- "> <meta name="viewport" content="width=device-width, initial-scale= , shrink-t\ o-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/ . . \ /css/bootstrap.min.css" integrity="sha -Gn xqQ aoWXA+ RXPxPg fy IWvTNh E \ XmFcJlSAwiGgFAW/dAiS JXm" crossorigin="anonymous"> <title>Hello, world!</title> </head> <body> <h1>Hello, world!</h1> <!-- Optional JavaScript --> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <script src="https://code.jquery.com/jquery- . . .slim.min.js" integrity="sh\ a -KJ o DKtIkvYIK UENzmM KCkRr/rE /Qpg aAZGJwFDMVNA/GpGFF hXpG KkN" crossorig\ in="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/ . Группа в ВК "Django_Python" . /umd/pop\ 213 Chapter 10: Bootstrap per.min.js" integrity="sha -ApNbgh B+Y QKtv Rn W mgPxhU K/ScQsAP hUibX j fakF\ PskvXusvfa b Q" crossorigin="anonymous"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/ . . /js/bootstrap.mi\ n.js" integrity="sha -JZR Spejh U d jOt vLEHfe/JQGiRRSQQxSfFWpi MquVdAyjUar +\ PVCmYl" crossorigin="anonymous"></script> </body> </html> Если вы снова запустите сервер с помощью python manage.py runserver и обновите домашнюю страницу по адресу http://127.0.0.1:8000/, вы увидите, что в данный момент изменился только размер шрифта. Давайте добавим панель навигации вверху страницы, которая содержит наши ссылки для домашней страницы, входа в систему, выхода из системы и регистрации. В частности, мы можем использовать теги if / else в шаблонизаторе Django для добавления некоторой базовой логики. Мы хотим показать кнопки «войти» и «зарегистрироваться» пользователям, которые вышли из системы, а кнопки «выйти» и «изменить пароль» пользователям, которые вошли в систему. Вот как выглядит код. Опять же, здесь можно копировать / вставлять, поскольку эта книга посвящена изучению Django, а не HTML, CSS и Bootstrap. Code <!-- templates/base.html --> <!doctype html> <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf- "> <meta name="viewport" content="width=device-width, initial-scale= , shrink-t\ o-fit=no"> Группа в ВК "Django_Python" 214 Chapter 10: Bootstrap <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/ . . \ /css/bootstrap.min.css" integrity="sha -Gn xqQ aoWXA+ RXPxPg fy IWvTNh E \ XmFcJlSAwiGgFAW/dAiS JXm" crossorigin="anonymous"> <title>{% block title %}Newspaper App{% endblock title %}</title> </head> <body> <nav class="navbar navbar-expand-md navbar-dark bg-dark mb- "> <a class="navbar-brand" href="{% url 'home' %}">Newspaper</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-t\ arget="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" ari\ a-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarCollapse"> {% if user.is_authenticated %} <ul class="navbar-nav ml-auto"> <li class="nav-item"> <a class="nav-link dropdown-toggle" href="#" id="userMenu" data-to\ ggle="dropdown" aria-haspopup="true" aria-expanded="false"> {{ user.username }} </a> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="us\ erMenu"> <a class="dropdown-item" href="{% url 'password_change' %}">Chan\ ge password</a> Группа в ВК "Django_Python" 215 Chapter 10: Bootstrap <div class="dropdown-divider"></div> <a class="dropdown-item" href="{% url 'logout' %}">Log out</a> </div> </li> </ul> {% else %} <form class="form-inline ml-auto"> <a href="{% url 'login' %}" class="btn btn-outline-secondary">Log in\ </a> <a href="{% url 'signup' %}" class="btn btn-primary ml- ">Sign up</a> </form> {% endif %} </div> </nav> <div class="container"> {% block content %} {% endblock %} </div> <!-- Optional JavaScript --> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <script src="https://code.jquery.com/jquery- . . .slim.min.js" integrity="sh\ a -KJ o DKtIkvYIK UENzmM KCkRr/rE /Qpg aAZGJwFDMVNA/GpGFF hXpG KkN" crossorig\ in="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/ . per.min.js" integrity="sha . /umd/pop\ -ApNbgh B+Y QKtv Rn W mgPxhU K/ScQsAP hUibX j fakF\ PskvXusvfa b Q" crossorigin="anonymous"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/ . . /js/bootstrap.mi\ Группа в ВК "Django_Python" 216 Chapter 10: Bootstrap n.js" integrity="sha -JZR Spejh U d jOt vLEHfe/JQGiRRSQQxSfFWpi MquVdAyjUar +\ PVCmYl" crossorigin="anonymous"></script> </body> </html> Если вы обновите домашнюю страницу на http://127.0.0.1:8000 наша новая навигационная панель появится волшебным образом! Homepage with Bootstrap nav logged in Нажмите на имя пользователя в верхнем правом углу - wsv в моем случае - чтобы увидеть хорошее выпадающее меню, которое предлагает Bootstrap. Homepage with Bootstrap nav logged in and dropdown Если вы нажмете на ссылку «logout», то наша навигационная панель изменится, предложив ссылки либо «log in», либо «sign up». Группа в ВК "Django_Python" 217 Chapter 10: Bootstrap Homepage with Bootstrap nav logged out Еще лучше, если вы уменьшите размер окна вашего браузера. Bootstrap автоматически изменяет размеры и вносит изменения, чтобы он выглядел хорошо и на мобильном устройстве. Homepage mobile with hamburger icon Обновите домашнюю страницу, и вы увидите ее в действии. Вы даже можете изменить ширину веб-браузера, чтобы увидеть, как изменяются боковые поля при увеличении и уменьшении размера экрана. Если вы нажмете кнопку «logout», а затем «log in» в верхней панели навигации, вы также увидите, что наша страница входа в систему http://127.0.0.1:8000/users/login также выглядит лучше. Группа в ВК "Django_Python" 218 Chapter 10: Bootstrap Bootstrap login Единственное, что отвлекает - это кнопка «Login». Мы можем использовать Bootstrap, чтобы добавить приятный стиль, например сделать его зеленым и привлекательным.Измените строку “button” в templates/registration/login.html следующим образом. Code <!-- templates/registration/login.html --> ... <button class="btn btn-success ml- " type="submit">Login</button> ... Теперь обновите страницу, чтобы увидеть нашу новую кнопку. Группа в ВК "Django_Python" 219 Chapter 10: Bootstrap Bootstrap login with new button Signup Form Наша страница регистрации по адресу http://127.0.0.1:8000/users/signup/ есть стили Bootstrap, а также отвлекающий вспомогательный текст. Например, после “Username” написано “Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.” Updated navbar logged out Откуда взялся этот текст? Всякий раз, когда что-то кажется «волшебным» в Django, будьте уверены, что это определенно не так. Вероятно, код пришел из внутреннего фрагмента Django. Самый быстрый метод, который я нашел, чтобы выяснить, что происходит в Django, просто перейти к исходному коду Django на Github, использовать панель поиска и попытаться найти конкретный фрагмент текста. Например, если вы выполните поиск «150 символов или меньше», вы окажетесь на странице django / contrib / auth / models.py, расположенной, в строке 301. Группа в ВК "Django_Python" 220 Chapter 10: Bootstrap Текст поставляется как часть приложения auth, в поле имени пользователя для AbstractUser. Сейчас у нас есть три варианта: • переопределить существующий help_text • скрыть help_text • изменить стиль help_text Мы выберем третий вариант, так как это хороший способ представить отличный сторонний пакет django-crispy-forms. Работа с формами является сложной задачей, и django-crispy-forms упрощает написание DRY кода. Сначала остановите локальный сервер с Control+c. Затем используйте Pip env для установки пакета в нашем проекте. Command Line (news) $ pipenv install django-crispy-forms Добавьте новое приложение в список INSTALLED_APPS в settings.py файл. Поскольку количество приложений начинает расти, я считаю полезным различать сторонние приложения и локальные приложения, которые я добавил сам. Вот как сейчас выглядит код. Группа в ВК "Django_Python" 221 Chapter 10: Bootstrap Code # newspaper_project/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # rd Party 'crispy_forms', # Local 'users', 'pages', ] Поскольку мы используем Bootstrap 4, мы также должны добавить эту конфигурацию в settings.py файл. Это идет в нижней части файла. Code # newspaper_project/settings.py CRISPY_TEMPLATE_PACK = 'bootstrap ' Теперь в нашем шаблоне signup.html мы можем быстро использовать crispy формы. Сначала мы загружаем crispy_forms_tags сверху, а затем меняем {{ form.as_p }} на {{ form|crispy }}. Группа в ВК "Django_Python" 222 Chapter 10: Bootstrap Code <!-- templates/signup.html --> {% extends 'base.html' %} {% load crispy_forms_tags %} {% block title %}Sign Up{% endblock %} {% block content %} <h2>Sign up</h2> <form method="post"> {% csrf_token %} {{ form|crispy }} <button type="submit">Sign up</button> </form> {% endblock %} Если вы снова запустите сервер с помощью python manage.py runserver и обновите страницу регистрации, мы увидим новые изменения. Группа в ВК "Django_Python" 223 Chapter 10: Bootstrap Crispy signup page Намного лучше. Хотя, как насчет того, чтобы наша кнопка “Sign up” была немного более привлекательной? В Bootstrap есть все виды стилей кнопок, которые мы можем выбрать. Давайте использовать “success”, который имеет зеленый фон и белый текст. Обновите файл signup.html в строке для кнопки регистрации. Группа в ВК "Django_Python" 224 Chapter 10: Bootstrap Code <!-- templates/signup.html --> ... <button class="btn btn-success" type="submit">Sign up</button> ... Обновите страницу, и вы увидите нашу обновленную работу. Crispy signup page green button Следующий шаг Наше приложение Newspaper начинает выглядеть довольно хорошо. Последний шаг это настройка смены и сброса пароля. Группа в ВК "Django_Python" 225 Chapter 10: Bootstrap Здесь снова Django позаботился о тяжелой работе за нас, поэтому он требует минимального количества кода с нашей стороны. Группа в ВК "Django_Python" Глава ��: Изменение и сброс пароля В этой главе мы завершим поток авторизации нашего приложения Newspaper, добавив функцию изменения и сброса пароля. Пользователи смогут изменить свой текущий пароль или, и если они его забыли сбросить его по электронной почте. Так же как в Django поставляется с встроенным представлением и URL для входа и выхода, также поставляется и с views/URL смены и сброса пароля. Сначала мы рассмотрим версии по умолчанию, а затем узнаем, как настроить их с помощью наших собственных шаблонов Bootstrap и службы электронной почты. Изменение пароля Разрешение пользователям менять свои пароли является распространенной функцией на многих веб-сайтах. Django предоставляет реализацию по умолчанию, которая уже работает на этом этапе. Чтобы попробовать это, сначала нажмите кнопку “log in”, убедитесь, что вы вошли в систему. Затем перейдите на страницу “Password change” по адресу http://127.0.0.1:8000/users/password_change/. Группа в ВК "Django_Python" 227 Chapter 11: Password Change and Reset Password change Введите старый пароль, а затем новый. Затем нажмите кнопку “Change My Password”. Вы будете перенаправлены на страницу “Password change successful” (“пароль успешно изменен”), расположенную по адресу: http://127.0.0.1:8000/users/password_change/done/. Password change done Группа в ВК "Django_Python" Chapter 11: Password Change and Reset 228 Настройка изменения пароля Давайте настроим эти две страницы смены пароля так, чтобы они соответствовали внешнему виду нашего сайта газеты. Поскольку Django уже создал представления и URL для нас, нам нужно только добавить новые шаблоны. В командной строке создайте два новых файла шаблона в папке регистрации. Command Line (news) $ touch templates/registration/password_change_form.html (news) $ touch templates/registration/password_change_done.html Обновите password_change_form.html следующим кодом. Code <!-- templates/registration/password_change_form.html --> {% extends 'base.html' %} {% block title %}Password Change{% endblock %} {% block content %} <h1>Password change</h1> <p>Please enter your old password, for security's sake, and then enter your ne\ w password twice so we can verify you typed it in correctly.</p> <form method="POST"> {% csrf_token %} {{ form.as_p }} <input class="btn btn-success" type="submit" value="Change my password"> Группа в ВК "Django_Python" Chapter 11: Password Change and Reset 229 </form> {% endblock %} В верхней части мы расширяем base.html и устанавливаем title нашей страницы. Поскольку мы использовали title «block» в нашем файле base.html, мы можем переопределить их здесь. Форма использует POST, так как мы отправляем данные и csrf_token по соображениям безопасности. Используя form.as_p, мы просто отображаем в абзацах содержание формы для сброса пароля по умолчанию. И, наконец, мы включаем кнопку отправки, которая использует стиль Bootstrap btn btn-success, чтобы сделать его зеленым.Перейдите на страницу http://127.0.0.1:8000/users/password_change/ и обновите страницу, чтобы увидеть наши изменения. New password change form Следующий шаблон password_change_done. Группа в ВК "Django_Python" Chapter 11: Password Change and Reset 230 Code <!-- templates/registration/password_change_done.html --> {% extends 'base.html' %} {% block title %}Password Change Successful{% endblock %} {% block content %} <h1>Password change successful</h1> <p>Your password was changed.</p> {% endblock content %} Он также расширяет base.html и включает новый title. Однако на странице нет формы, только новый текст.Новая страница находится по адресу http://127.0.0.1:8000/users/ password_change/done/. New password change done Это было не так уж плохо, верно? Конечно, было гораздо меньше работы, чем создание всего с нуля, особенно весь код вокруг безопасного обновления пароля пользователя. Далее идет наша функция сброса пароля. Группа в ВК "Django_Python" Chapter 11: Password Change and Reset 231 Сброс пароля Сброс пароля обрабатывает частый случай, когда пользователи, забывают свои пароли. Шаги очень похожи на настройку смены пароля, как мы только что делали. Django уже предоставляет реализацию по умолчанию, которую мы будем использовать, а затем настроим шаблоны, чтобы они соответствовали остальной части нашего сайта. Единственная необходимая конфигурация указать Django, как отправлять электронные письма. В конце концов, пользователь может сбросить пароль, только если у него есть доступ к электронной почте, связанной с учетной записью. В производстве мы будем использовать почтовый сервис SendGrid для фактической отправки электронных писем, но в целях тестирования мы можем положиться на настройку бэкэнда консоли Django, которая вместо этого выводит текст электронной почты в консоль командной строки. Внизу файла settings.py сделайте следующее однострочное изменение. Code # newspaper_project/settings.py EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' И у нас все готово! Джанго позаботится обо всем остальном за нас. Давайте попробуем.Перейдите по адресу http://127.0.0.1:8000/users/password_reset/, чтобы просмотреть страницу сброса пароля по умолчанию. Группа в ВК "Django_Python" Chapter 11: Password Change and Reset 232 Default password reset page Убедитесь, что введенный вами адрес электронной почты соответствует одной из ваших учетных записей. После отправки вы будете перенаправлены на страницу сброса пароля по адресу: http://127.0.0.1:8000/users/password_reset/done/. Default password reset done page Тут говорится , чтобы вы проверить вашу электронную почту. Так как мы указали Django отправлять письма в консоль командной строки, текст письма теперь будет там. Это то, что я вижу в своей консоли. Группа в ВК "Django_Python" 233 Chapter 11: Password Change and Reset Command Line Content-Type: text/plain; charset="utf- " MIME-Version: . Content-Transfer-Encoding: bit Subject: Password reset on . . . : From: webmaster@localhost To: will@wsvincent.com Date: Thu, Mar : . Message-ID: < : . @ . . . .in-addr.arpa> You're receiving this email because you requested a password reset for your user\ account at . . . : . Please go to the following page and choose a new password: http:// . . . : /users/reset/MQ/ up- c db ead / Your username, in case you've forgotten: wsv Thanks for using our site! The . . . : team Текст письма должен быть одинаковым, за исключением трех строк: • "кому” в шестой строке содержит адрес электронной почты пользователя • ссылка URL содержит безопасный токен, который Django генерирует случайным образом и может быть использован только один раз Группа в ВК "Django_Python" Chapter 11: Password Change and Reset 234 • Django вежливо напоминает нам о вашем имени пользователя В ближайшее время мы настроим весь текст электронного письма по умолчанию, но пока сосредоточимся на поиске ссылки. В сообщении у меня: http://127.0.0.1:8000/users/reset/MQ/4up-678712c114db2ead7780/ Введите эту ссылку в свой браузер и вы будете перенаправлены на страницу “смены пароля”. Default change password page Теперь введите новый пароль и нажмите кнопку “Change my password”. Последним шагом будет перенаправление вас на страницу “сброс пароля выполнен”. Группа в ВК "Django_Python" Chapter 11: Password Change and Reset Default password reset complete Чтобы подтвердить, что все сработало, нажмите на ссылку “Log in” и используйте свой новый пароль. Это должно сработать. Собственные шаблоны Как и в случае с “сменой пароля”, нам нужно только создать новые шаблоны, чтобы настроить внешний вид сброса пароля. Создайте четыре новых файла шаблонов. Command Line (news) $ touch templates/registration/password_reset_form.html (news) $ touch templates/registration/password_reset_done.html (news) $ touch templates/registration/password_reset_confirm.html (news) $ touch templates/registration/password_reset_complete.html Начните с формы сброса пароля, которым является password_reset_form.html. Группа в ВК "Django_Python" 235 Chapter 11: Password Change and Reset 236 Code <!-- templates/registration/password_reset_form.html --> {% extends 'base.html' %} {% block title %}Forgot Your Password?{% endblock %} {% block content %} <h1>Forgot your password?</h1> <p>Enter your email address below, and we'll email instructions for setting a ne\ w one.</p> <form method="POST"> {% csrf_token %} {{ form.as_p }} <input class="btn btn-success" type="submit" value="Send me instructions!"> </form> {% endblock %} В верхней части мы расширяем base.html и устанавливаем titles нашей страницы. Поскольку мы использовали titles «block» в нашем файле base.html, мы можем переопределить их здесь. Форма использует POST, так как мы отправляем данные и csrf_token по соображениям безопасности. Используя form.as_p, мы просто отображаем в абзацах содержание формы для сброса пароля по умолчанию. Наконец, мы вставляем кнопку отправки и используем стиль Bootstrap btn btn-success, чтобы сделать его зеленым. Если вы перейдете по адресу http://127.0.0.1:8000/users/password_reset/ вы увидите нашу новую страницу. Группа в ВК "Django_Python" 237 Chapter 11: Password Change and Reset New password reset Теперь мы можем обновить остальные три страницы. Каждая из них имеет одинаковую форму расширения base.html, нового заголовка, нового текста контента, а также для «подтверждения сброса пароля» обновленную форму. Code <!-- templates/registration/password_reset_done.html --> {% extends 'base.html' %} {% block title %}Email Sent{% endblock %} {% block content %} <h1>Check your inbox.</h1> <p>We've emailed you instructions for setting your password. You should receiv\ e the email shortly!</p> {% endblock %} Проверьте изменения, перейдя по ссылке http://127.0.0.1:8000/users/password_reset/done/. Группа в ВК "Django_Python" 238 Chapter 11: Password Change and Reset New reset done Следующая страница подтверждения сброса пароля. Code <!-- templates/registration/password_reset_confirm.html --> {% extends 'base.html' %} {% block title %}Enter new password{% endblock %} {% block content %} <h1>Set a new password!</h1> <form method="POST"> {% csrf_token %} {{ form.as_p }} <input class="btn btn-success" type="submit" value="Change my password"> </form> {% endblock %} В командной строке выберите ссылку URL из электронного письма, выведенного на консоль - мое было http://���.�.�.�:����/users/reset/MQ/�up-������c���db�ead����/ вы увидите следующее. Группа в ВК "Django_Python" 239 Chapter 11: Password Change and Reset New set password Наконец, вот полный код сброса пароля. Code <!-- templates/registration/password_reset_complete.html --> {% extends 'base.html' %} {% block title %}Password reset complete{% endblock %} {% block content %} <h1>Password reset complete</h1> <p>Your new password has been set. You can log in now on the <a href="{% url 'lo\ gin' %}">log in page</a>.</p> {% endblock %} Вы можете просмотреть его по адресу http://127.0.0.1:8000/users/reset/done/. Группа в ВК "Django_Python" Chapter 11: Password Change and Reset 240 New password reset complete Теперь пользователи могут сбросить свой пароль учетной записи! Заключение В следующей главе мы подключим наше приложение Newspaper к почтовой службе SendGrid, чтобы фактически отправлять наши автоматические электронные письма пользователям, а не выводить их в нашей консоли командной строки. Группа в ВК "Django_Python" Глава ��: Email На данный момент вы можете чувствовать себя немного перегруженным всей информацией о конфигурации аутентификации пользователя, которую мы сделали до этого момента. Это нормально. В конце концов, мы еще даже не создали каких либо основных функций приложения Newspaper! Все было о настройке пользовательских учетных записей пользователей и прочем. Плюсом подхода Django является то, что в нем невероятно легко настроить любую часть нашего сайта. Недостатком является то, что Django требует немного больше нестандартного кода, чем некоторые конкурирующие веб-фреймворки. По мере того, как вы становитесь все более опытным в веб-разработке, вы будете понимать мудрость подхода Django. Теперь мы хотим, чтобы наши электронные письма отправлялись пользователям, а не просто выводились в консоль командной строки. Нам нужно зарегистрироваться в SendGrid и обновить ваш settings.py файл. Django позаботится об остальном. Готовы? SendGrid SendGrid это популярный сервис для отправки транзакционных писем, поэтому мы будем использовать его. Django не заботится о том, какой сервис вы выбираете, хотя; вы можете так же легко использовать MailGun или любой другой сервис по вашему выбору. На главной странице SendGrid нажмите на большую синюю кнопку “See Plans and Pricing”. Группа в ВК "Django_Python" 242 Chapter 12: Email SendGrid homepage На следующей странице немного прокрутите вниз и найдите в левой части кнопку “Try for Free”. SendGrid предоставляет бесплатный уровень, который мы можем использовать, хотя они несколько затрудняют его поиск. Группа в ВК "Django_Python" 243 Chapter 12: Email SendGrid pricing Зарегистрируйте бесплатную учетную запись на следующей странице. Группа в ВК "Django_Python" 244 Chapter 12: Email SendGrid new account Убедитесь, что учетная запись электронной почты, используемая для SendGrid, не совпадает с учетной записью электронной почты суперпользователя в проекте Newspaper, иначе могут быть странные ошибки. После подтверждения вашей новой учетной записи по электронной почте вас попросят войти в систему и перейти на страницу панели инструментов SendGrid. Группа в ВК "Django_Python" 245 Chapter 12: Email SendGrid loggedin Теперь мы можем настроить наш код Django в файле settings.py. Сначала мы обновляем почтовый сервер для использования SMTP. Code # newspaper_project/settings.py EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' Затем прямо под ним добавьте следующие пять строк конфигурации электронной почты. Обратите внимание, что в идеале вы должны хранить защищенную информацию, такую как ваш пароль, в переменной, но мы не будем этого делать здесь, что бы упростить ситуацию. Группа в ВК "Django_Python" 246 Chapter 12: Email Code # newspaper_project/settings.py EMAIL_HOST = 'smtp.sendgrid.net' EMAIL_HOST_USER = 'sendgrid_username' EMAIL_HOST_PASSWORD = 'sendgrid_password' EMAIL_PORT = EMAIL_USE_TLS = True Убедитесь, что вы используете свое собственное имя пользователя SendGrid для EMAIL_HOST_USER и пароль для EMAIL_HOST_PASSWORD. Вот и все. Мы закончили! Перейдите к форме сброса пароля снова на: http://127.0.0.1:8000/users/password_reset/ Вы должны получить электронное письмо в свой почтовый ящик! Текст будет точно таким же, как и в консоли командной строки ранее. Пользовательские письма электронной почты Текущий текст электронной почты не очень личный, не так ли? Давайте изменим. На этом этапе я мог бы просто показать вам, какие шаги предпринять, но я думаю, что было бы полезно, если бы я мог объяснить, как я понял, как это сделать. В конце концов, вы хотите иметь возможность настраивать все части Django по мере необходимости. В этом случае я знал, какой текст использовал Django по умолчанию, но не было ясно, где в исходном коде Django он был написан. И поскольку весь исходный код Django доступен на Github, мы можем просто найти его. Группа в ВК "Django_Python" 247 Chapter 12: Email Github Django Используйте панель поиска Github и введите несколько слов из текста электронного письма. Если вы введете “You’re receiving this email because”, вы попадете на страницу поиска Github. Github search Первый результат - тот, который нам нужен. Он показывает, что код находится по адресу django / contrib / ad-min / templates / registration / password_reset_email.html. Это означает, что в приложении contrib файл который нам нужен, называется password_reset_email.html. Группа в ВК "Django_Python" 248 Chapter 12: Email Вот текст по умолчанию из исходного кода Django. Code {% load i n %}{% autoescape off %} {% blocktrans %}You're receiving this email because you requested a password res\ et for your user account at {{ site_name }}.{% endblocktrans %} {% trans "Please go to the following page and choose a new password:" %} {% block reset_link %} {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb =uid token=to\ ken %} {% endblock %} {% trans "Your username, in case you've forgotten:" %} {{ user.get_username }} {% trans "Thanks for using our site!" %} {% blocktrans %}The {{ site_name }} team{% endblocktrans %} {% endautoescape %} Давайте изменим это. Нам нужно создать новый файл password_reset_email.html в нашей папке registration . Command Line (news) $ touch templates/registration/password_reset_email.html Затем используйте следующий код, который настраивает то, что Django предоставляет по умолчанию. Группа в ВК "Django_Python" 249 Chapter 12: Email Code <!-- templates/registration/password_reset_email.html --> {% load i n %}{% autoescape off %} {% trans "Hi" %} {{ user.get_username }}, {% trans "We've received a request to reset your password. If you didn't make th\ is request, you can safely ignore this email. Otherwise, click the button below \ to reset your password." %} {% block reset_link %} {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb =uid token=to\ ken %} {% endblock %} {% endautoescape %} Этот код может выглядеть немного страшно, поэтому давайте разберем его построчно. Вверху мы загружаем тег шаблона i18n, что означает, что этот текст может быть переведен на несколько языков. Django имеет надежную поддержку интернационализации, хотя ее охват выходит за рамки этой книги. Мы приветствуем пользователя по имени благодаря user.get_username. Затем мы используем встроенный блок reset_link для включения настраиваемой URL-ссылки. Вы можете прочитать больше о подходе к управлению паролями Django в официальных документах. Давайте также обновим заголовок темы электронного письма. Для этого мы создадим новый файл templates / registration / password_reset subject.txt. Группа в ВК "Django_Python" 250 Chapter 12: Email Command Line (news) $ touch templates/registration/password_reset_subject.txt Затем добавьте следующую строку кода в файл password_reset_subject.txt. Please reset your password И у нас все готово. Попробуйте снова наш новый поток, введя новый пароль по адресу http://127.0.0.1:8000/users/password_reset/. Затем проверьте свою электронную почту, и она будет иметь наш новый контент и тему. Заключение Теперь мы завершили реализацию полного процесса аутентификации пользователя. Пользователи могут зарегистрировать новую учетную запись, войти в систему, выйти из системы, изменить свой пароль и сбросить пароль. Пришло время создать наше фактическое приложение Newspaper . Группа в ВК "Django_Python" Глава ��: приложение Newspaper Пришло время создать наше приложение Newspaper. У нас будет страница статей, на которой журналисты могут публиковать статьи, установим разрешения, чтобы только автор статьи мог их редактировать или удалять, и, наконец, добавим возможность другим пользователям писать комментарии к каждой статье, в которых будет введена концепция внешних ключей. Приложение Articles(статьи) Для начала создайте приложение статей и определите модели нашей базы данных. Не существует жестких и быстрых правил, касающихся имен ваших приложений, за исключением того, что вы не можете использовать имя встроенного приложения. Если вы посмотрите раздел INSTALLED_APPS файла settings.py, вы увидите, какие имена приложений запрещены: admin, auth, contenttypes, sessions, messages, и staticfiles.. Общее практическое правило заключается в использовании множественного числа имени приложения - сообщений, платежей, пользователей и т. д. - если только это не является явно неправильным, как в случае блога, где единственное число имеет больше смысла. Start by creating our new articles app. Command Line (news) $ python manage.py startapp articles Затем добавьте его в наш INSTALLED_APPS и обновите часовой пояс, так как мы будем отмечать время в наших статьях. Вы можете найти свой часовой пояс в списке Википедии. Например, я живу в Бостоне, штат Массачусетс, который находится в восточном часовом поясе Соединенных Штатов. Поэтому моя запись - America/New_York. Группа в ВК "Django_Python" Chapter 13: Newspaper app 252 Code # newspaper_project/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # rd Party 'crispy_forms', # Local 'users', 'pages', 'articles', # new ] TIME_ZONE = 'America/New_York' Далее мы определяем нашу модель базы данных, которая содержит четыре поля: заголовок, тело, дата и автор. Обратите внимание, что мы позволяем Django автоматически устанавливать время и дату в соответствии с нашей настройкой TIME_ZONE. В поле автора мы хотим сослаться на нашу пользовательскую модель пользователя users.CustomUser, которую мы установили в файле settings.py как AUTH_USER_MODEL. Поэтому, если мы импортируем настройки, мы можем ссылаться на них как settings.AUTH_USER_MODEL. Мы также реализуем лучшие практики определения get_absolute_url с самого начала и метод __str__ для просмотра модели в нашем интерфейсе администратора. Группа в ВК "Django_Python" 253 Chapter 13: Newspaper app Code # articles/models.py from django.conf import settings from django.db import models from django.urls import reverse class Article(models.Model): title = models.CharField(max_length= ) body = models.TextField() date = models.DateTimeField(auto_now_add=True) author = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, ) def __str__(self): return self.title def get_absolute_url(self): return reverse('article_detail', args=[str(self.id)]) Поскольку у нас есть совершенно новое приложение и модель, пришло время создать новый файл миграции и затем применить его к базе данных. Группа в ВК "Django_Python" Chapter 13: Newspaper app 254 Command Line (news) $ python manage.py makemigrations articles (news) $ python manage.py migrate На этом этапе мне нравится переходить в админку, чтобы поиграться с моделью, прежде чем создавать urls/views/templates, необходимые для фактического отображения данных на веб сайте. Но сначала нам нужно обновить admin.py, чтобы наше новое приложение отобразилось. Code # articles/admin.py from django.contrib import admin from . import models admin.site.register(models.Article) Теперь мы запускаем сервер. Command Line (news) $ python manage.py runserver Перейдите к http://127.0.0.1:8000/admin/ и войдите в систему. Группа в ВК "Django_Python" 255 Chapter 13: Newspaper app Admin page Если вы нажмете “Articles” в верхней части страницы, мы сможем ввести некоторые примеры данных. Скорее всего, на данный момент у вас будет три пользователя: ваши учетные записи суперпользователя, testuser и testuser2. Используйте свой аккаунт суперпользователя в качестве автора всех трех статей. Группа в ВК "Django_Python" 256 Chapter 13: Newspaper app Admin articles add page Я добавил три новые статьи, как вы можете видеть на обновленной странице статей. Группа в ВК "Django_Python" 257 Chapter 13: Newspaper app Admin three articles Если вы нажмете на отдельную статью, вы увидите, что отображаются title, body, и author, но не дата. Это потому, что Django автоматически добавила нам дату и поэтому не может быть изменена администратором. Мы могли бы сделать дату редактируемой - в более сложных приложениях обычно есть поля create_at и updated_at - но для простоты мы просто покажем дату создания. Даже если дата здесь не отображается, мы по-прежнему сможем получить к ней доступ в наших шаблонах, чтобы ее можно было отображать на веб страницах. URLs и Views Следующий шаг - настройка URLs и представлений. Пусть наши статьи появятся в разделе articles/. Импортируйте include во вторую строку и добавьте шаблон URL для статей на уровне проекта файл urls.py. Группа в ВК "Django_Python" Chapter 13: Newspaper app 258 Code # newspaper_project/urls.py from django.contrib import admin from django.urls import path, include # new urlpatterns = [ path('', include('pages.urls')), path('articles/', include('articles.urls')), # new path('admin/', admin.site.urls), path('users/', include('users.urls')), path('users/', include('django.contrib.auth.urls')), ] Далее мы создаем файл articles/urls.py. Command Line (news) $ touch articles/urls.py Затем заполните его нашими маршрутами. Начнем со страницы со списком всех статей в articles/ которая будет использовать представление ArticleListView. Группа в ВК "Django_Python" Chapter 13: Newspaper app 259 Code # articles/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.ArticleListView.as_view(), name='article_list'), ] Теперь создайте наше представление, используя встроенный универсальный ListView от Django Code # articles/views.py from django.views.generic import ListView from . import models class ArticleListView(ListView): model = models.Article template_name = 'article_list.html' Нам нужно указать только два поля: модель Article и имя нашего шаблона, который будет article_list.html. Последний шаг это создать наш шаблон. Мы можем сделать пустой файл из командной строки. Группа в ВК "Django_Python" Chapter 13: Newspaper app 260 Command Line (news) $ touch templates/article_list.html Bootstrap имеет встроенный компонент под названием Cards , который мы можем настроить для наших отдельных статей. Напомним, что ListView возвращает объект с именем object_list, который мы можем перебрать с помощью цикла for. В каждой статье мы показываем заголовок, текст, автора и дату. Мы даже можем предоставить ссылки для «редактирования» и «удаления» функций, которые мы еще не создали. Code <!-- templates/article_list.html --> {% extends 'base.html' %} {% block title %}Articles{% endblock %} {% block content %} {% for article in object_list %} <div class="card"> <div class="card-header"> <span class="font-weight-bold">{{ article.title }}</span> &middot; <span class="text-muted">by {{ article.author }} | {{ article.date }}</s\ pan> </div> <div class="card-body"> {{ article.body }} </div> <div class="card-footer text-center text-muted"> <a href="#">Edit</a> | <a href="#">Delete</a> Группа в ВК "Django_Python" 261 Chapter 13: Newspaper app </div> </div> <br /> {% endfor %} {% endblock content %} Снова запустите сервер с помощью python manage.py runserver и посетите нашу страницу по адресу http://127.0.0.1:8000/articles/. Articles page Не плохо а? Если бы мы захотели проявить фантазию, мы могли бы создать собственный шаблонный фильтр, чтобы выводимая дата отображалась в секундах, минутах или днях. Это можно сделать с помощью некоторой логики if / else и опций даты в Django, но мы не будем реализовывать это здесь. Группа в ВК "Django_Python" 262 Chapter 13: Newspaper app Редактирование/Удаление Как мы можем добавить параметры редактирования и удаления? Нам нужны новые urls, представления и шаблоны. Давайте начнем с urls. Мы можем воспользоваться тем, что Django автоматически добавляет первичный ключ в каждую базу данных. Поэтому наша первая статья с первичным ключом 1 будет находиться в article / 1 / edit /, а маршрут удаления будет находиться в article / 1 / delete /. Code # articles/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.ArticleListView.as_view(), name='article_list'), path('<int:pk>/edit/', views.ArticleUpdateView.as_view(), name='article_edit'), # new path('<int:pk>/', views.ArticleDetailView.as_view(), name='article_detail'), # new path('<int:pk>/delete/', views.ArticleDeleteView.as_view(), name='article_delete'), # new ] Теперь запишите наши представления, которые будут использовать представления на основе классов Django для DetailView, UpdateView и DeleteView. Мы указываем, какие поля могут быть обновлены - заголовок и тело - и куда перенаправлять пользователя после удаления статьи: article_list. Группа в ВК "Django_Python" Chapter 13: Newspaper app Code # articles/views.py from django.views.generic import ListView, DetailView from django.views.generic.edit import UpdateView, DeleteView from django.urls import reverse_lazy from . import models class ArticleListView(ListView): model = models.Article template_name = 'article_list.html' class ArticleDetailView(DetailView): model = models.Article template_name = 'article_detail.html' class ArticleUpdateView(UpdateView): model = models.Article fields = ['title', 'body', ] template_name = 'article_edit.html' class ArticleDeleteView(DeleteView): model = models.Article template_name = 'article_delete.html' Группа в ВК "Django_Python" 263 Chapter 13: Newspaper app 264 success_url = reverse_lazy('article_list') Наконец нам нужно добавить наши новые шаблоны. Остановите сервер с помощью Control + c и введите следующее. Command Line (news) $ touch templates/article_detail.html (news) $ touch templates/article_edit.html (news) $ touch templates/article_delete.html Мы начнем со страницы details , где будут отображаться заголовок, дата, тело и автор со ссылками для редактирования и удаления. Она также будет ссылаться на все статьи. Напомним, что тегу url языка шаблонов Django требуется имя URL, а затем передаются все аргументы. Имя нашего маршрута редактирования - article_edit, и нам нужно передать его первичный ключ article.pk. Имя маршрута для удаления - article_delete, для него также требуется первичный ключ article.pk. Наша страница статей представляет собой ListView, поэтому ей не нужно передавать никаких дополнительных аргументов. Code <!-- templates/article_detail.html --> {% extends 'base.html' %} {% block content %} <div class="article-entry"> <h2>{{ object.title }}</h2> <p>by {{ object.author }} | {{ object.date }}</p> <p>{{ object.body }}</p> </div> Группа в ВК "Django_Python" Chapter 13: Newspaper app 265 <p><a href="{% url 'article_edit' article.pk %}">Edit</a> | <a href="{% url 'a\ rticle_delete' article.pk %}">Delete</a></p> <p>Back to <a href="{% url 'article_list' %}">All Articles</a>.</p> {% endblock content %} Для редактирования и удаления страниц мы можем использовать стиль кнопок Bootstrap, чтобы кнопка редактирования была светло-голубой, а кнопка удаления - красной. Code <!-- templates/article_edit.html --> {% extends 'base.html' %} {% block content %} <h1>Edit</h1> <form action="" method="post">{% csrf_token %} {{ form.as_p }} <button class="btn btn-info ml- " type="submit">Update</button> </form> {% endblock %} Группа в ВК "Django_Python" Chapter 13: Newspaper app 266 Code <!-- templates/article_delete.html --> {% extends 'base.html' %} {% block content %} <h1>Delete</h1> <form action="" method="post">{% csrf_token %} <p>Are you sure you want to delete "{{ article.title }}"?</p> <button class="btn btn-danger ml- " type="submit">Confirm</button> </form> {% endblock %} В качестве заключительного шага мы можем добавить ссылки редактировать и удалять на нашу страницу list в классе div для card-footer.... Они будут такими же, как те, которые добавлены на страницу detail. Code <!-- templates/article_list.html --> ... <div class="card-footer text-center text-muted"> <a href="{% url 'article_edit' article.pk %}">Edit</a> | <a href="{% url 'article_delete' article.pk %}">Delete</a> </div> ... Хорошо, мы готовы посмотреть нашу работу. Запустите сервер с помощью python manage.py runserver и перейдите на страницу статей http://127.0.0.1:8000/articles/. Нажмите на ссылку "edit” в первой статье, и вы будете перенаправлены на: http://127.0.0.1:8000/articles/1/edit/ Группа в ВК "Django_Python" 267 Chapter 13: Newspaper app Edit page Если вы обновите поле “title” и нажмете кнопку update , вы будете перенаправлены на страницу detail, которая показывает изменение. Detail page Если вы нажмете на ссылку “Delete” вы будете перенаправлены на страницу delete. Группа в ВК "Django_Python" 268 Chapter 13: Newspaper app Delete page Нажмите страшную красную кнопку для “Delete”, и вы будете перенаправлены на страницу статей, которая теперь имеет только две записи. Articles page two entries Создание страницы Последним шагом является создание страницы для новых статей, которые мы можем сделать с Django CreateView. Наши три шага - это создание представления, url-адреса и шаблона. Этот процесс должен быть уже довольно знакомым. Группа в ВК "Django_Python" Chapter 13: Newspaper app 269 В нашем файле представлений добавьте Create View к импорту вверху и создайте новый класс ArticleCreateView Create View, который указывает вашу модель, шаблон и доступные поля. Code # articles/views.py ... from django.views.generic.edit import CreateView, UpdateView, DeleteView class ArticleCreateView(CreateView): model = models.Article template_name = 'article_new.html' fields = ['title', 'body', 'author',] ... Обратите внимание, что у наших полей есть автор, так как мы хотим связать новую статью с автором, однако после создания статьи мы не хотим, чтобы пользователь мог изменить автора, поэтому ArticleUpdateView имеет только поля ['title', 'body',]. Обновите файл url новым маршрутом для представления. Группа в ВК "Django_Python" Chapter 13: Newspaper app 270 Code # articles/urls.py ... urlpatterns = [ ... path('new/', views.ArticleCreateView.as_view(), name='article_new'), ... ] Затем остановите сервер с Control+c, чтобы создать новый шаблон с именем article_new.html. Command Line (news) $ touch templates/article_new.html И обновите его следующим HTML кодом. Code <!-- templates/article_new.html --> {% extends 'base.html' %} {% block content %} <h1>New article</h1> <form action="" method="post">{% csrf_token %} {{ form.as_p }} <button class="btn btn-success ml- " type="submit">Save</button> </form> {% endblock %} В качестве заключительного шага мы должны добавить ссылку на создание новых статей в нашей панели навигации, чтобы она была доступна везде на сайте для зарегистрированных пользователей. Группа в ВК "Django_Python" Chapter 13: Newspaper app 271 Code <!-- templates/base.html --> ... <body> <nav class="navbar navbar-expand-md navbar-dark bg-dark mb- "> <a class="navbar-brand" href="{% url 'home' %}">Newspaper</a> {% if user.is_authenticated %} <ul class="navbar-nav mr-auto"> <li class="nav-item"><a href="{% url 'article_new' %}">+ New</a></li> </ul> {% endif %} <button class="navbar-toggler" type="button" data-toggle="collapse" data-tar\ get="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-\ label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> ... И почему бы не использовать Bootstrap, чтобы улучшить нашу оригинальную домашнюю страницу? Мы можем обновить templates/home.html следующим образом. Группа в ВК "Django_Python" Chapter 13: Newspaper app 272 Code <!-- templates/home.html --> {% extends 'base.html' %} {% block title %}Home{% endblock %} {% block content %} <div class="jumbotron"> <h1 class="display- ">Newspaper app</h1> <p class="lead">A Newspaper website built with Django.</p> <p class="lead"> <a class="btn btn-primary btn-lg" href="{% url 'article_list' %}" role="butt\ on">View All Articles</a> </p> </div> {% endblock %} Мы все закончили. Давайте просто подтвердим, что все работает, как ожидалось. Запустите сервер снова python manage.py runserver и перейдите на нашу домашнюю страницу по адресу: http://127.0.0.1:8000/. Группа в ВК "Django_Python" 273 Chapter 13: Newspaper app Homepage with new link in nav Нажмите на ссылку " + New” в верхней панели навигации, и вы будете перенаправлены на нашу страницу создания. Группа в ВК "Django_Python" 274 Chapter 13: Newspaper app Create page Продолжайте и создайте новую статью. Затем нажмите на кнопку “Save”. Вы будете перенаправлены на страницу detail . Почему? Потому что в нашем models.py файл мы устанавливаем метод get_-absolute_url в article_detail. Это хороший подход, потому что если мы позже изменим шаблон url для страницы detail, скажем, на articles/details/4/, перенаправление все равно будет работать. Какой бы маршрут не был связан с article_detail, будет применятся; нет жесткого кодирования самого маршрута. Группа в ВК "Django_Python" 275 Chapter 13: Newspaper app Detail page Также обратите внимание, что первичный ключ здесь 4 в URL. Хотя сейчас мы показываем только три статьи, Django не переупорядочивает первичные ключи только потому, что мы удалили один. На практике большинство реальных сайтов ничего не удаляют; вместо этого они «скрывают» удаленные поля, поскольку это облегчает поддержание целостности базы данных и дает возможность «восстановить» позже, если это необходимо. С нашим текущим подходом, как только что-то удалено, оно ушло навсегда! Нажмите на ссылку “All Articles”, чтобы увидеть нашу новую страницу /articles page. Группа в ВК "Django_Python" 276 Chapter 13: Newspaper app Updated articles page Как и ожидалось, внизу есть наша новая статья. Заключение Мы создали специальное приложение article с функцией CRUD. Но пока нет никаких разрешений или авторизаций, что означает, что каждый может сделать что угодно! Пользователь, вышедший из системы, может просматривать все url, а любой пользователь, вошедший в систему, может вносить изменения или удалять в существующую статью, даже если она не принадлежит им! В следующей главе мы добавим разрешения и авторизации в наш проект, чтобы это исправить. Группа в ВК "Django_Python" Глава ��:Разрешения и авторизация Есть несколько проблем с нашим текущим сайтом газеты. Во первых, мы хотим, чтобы наша газета была финансово устойчивой. В будущем мы могли бы добавить платежное приложение, чтобы взимать плату за доступ, но сейчас нам потребуется, чтобы пользователь вошел в систему для просмотра любых статей. Это известно как авторизация. Распространено устанавливать разные правила относительно того, кто имеет право просматривать области вашего сайта. Обратите внимание, что это отличается от аутентификации, которая представляет собой процесс регистрации и входа в систему пользователей. Авторизация ограничивает доступ; Аутентификация позволяет пользователю зарегистрироваться и войти в систему. Как развитый веб фреймворк, Django имеет встроенную функциональность для авторизации, которую мы можем быстро использовать. В этой главе мы ограничим доступ к различным страницам открыв его только зарегистрированным пользователям. Мы также добавим разрешения, чтобы только автор статьи мог ее обновить или удалить; На данный момент это может сделать любой пользователь! Улучшение CreateView В настоящее время автором новой статьи может быть задан любой пользователь. Вместо этого он должен быть автоматически установлен как текущий пользователь. Затем мы установим права на редактирование / удаление, чтобы такие изменения мог вносить только автор статьи. Созданный по умолчанию CreateView предоставляет нам множество функциональных возможностей, но для того, чтобы назначить текущего пользователя автором, нам нужно его настроить. Мы удалим автора из полей и вместо этого установим его автоматически с помощью метода form_valid. Группа в ВК "Django_Python" Chapter 14: Permissions and Authorization 278 Code # articles/views.py ... class ArticleCreateView(CreateView): model = models.Article template_name = 'article_new.html' fields = ['title', 'body'] def form_valid(self, form): form.instance.author = self.request.user return super().form_valid(form) ... Как я узнал, что могу обновить CreateView следующим образом? Ответ я посмотрел на исходный код и использовал Google. Представления на основе классов отлично подходят для запуска новых проектов, но когда вы хотите настроить их, необходимо засучить рукава и начать понимать, что происходит под капотом. Чем больше вы используете и настраиваете встроенные представления, тем удобнее вам будет выполнять такие настройки. Скорее всего, то, что вы пытаетесь сделать, уже где-то решено, либо в самом Django, либо на форуме, подобно Stack Overflow. Не бойтесь просить о помощи! Теперь перезагрузите браузер и попробуйте нажать на ссылку “+ New” в верхней части окна. Это перенаправит на обновленную страницу создания, где больше нет поля автор. Если вы создадите новую статью, а затем зайдете в админку, вы увидите, что она автоматически настроена для текущего вошедшего в систему пользователя. Группа в ВК "Django_Python" 279 Chapter 14: Permissions and Authorization New article link Авторизация Существует множество проблем, связанных с отсутствием разрешений в нашем текущем проекте. Очевидно, что мы хотели бы ограничить доступ оставив его только для пользователей, поэтому у нас есть возможность в один прекрасный день наполнить читателями нашу газету . Но помимо этого, любой случайный пользователь, вышедший из системы, который знает правильный URL, может получить доступ к любой части сайта. Подумайте, что произойдет, если вышедший из системы пользователь попытается создать новую статью? Чтобы попробовать, нажмите на свое имя пользователя в правом верхнем углу навигационной панели, затем выберите “Log out” из выпадающих меню. Ссылка“+ New” исчезает из навигационной панели, но что произойдет, если вы перейдете к ней напрямую: http://127.0.0.1:8000/articles/new/? Страница все еще здесь. Группа в ВК "Django_Python" Chapter 14: Permissions and Authorization 280 Logged out new Теперь попробуйте создать новую статью с заголовком и телом. Нажмите на кнопку “Save”. Create page error Группа в ВК "Django_Python" Chapter 14: Permissions and Authorization 281 Ошибка. Это связано с тем, что в нашей модели ожидается поле автора, которое связано с текущим вошедшим пользователем. Но поскольку мы не вошли в систему, автора нет, и поэтому отправка не удалась. Mixins(Миксины) Мы явно хотим установить некоторые полномочия, чтобы только зарегистрированные пользователи могли получить доступ к сайту. Для этого мы можем использовать миксин, который представляет собой особый вид множественного наследования, который использует Django, чтобы избежать дублирования кода и сделать возможной настройку. Например, встроенному универсальному ListView необходим способ возврата шаблона, так же, как и DetailView, и практически любой другое представление. Вместо того, чтобы повторять один и тот же код в каждом большом общем представлении, Django разбивает эту функциональность на «миксин», известный как TemplateRespon-seMixin. И ListView, и DetailView используют этот миксин для визуализации правильного шаблона. IЕсли вы прочитаете исходный код Django, который свободно доступен на Github, вы увидите, что миксины используются повсеместно. Чтобы ограничить доступ к просмотру оставив его только зарегистрированным пользователям, Django имеет миксин LoginRequired, который мы можем использовать. Он мощный и очень краткий. В существующем файле article / views.py импортируйте его вверху, а затем добавьте LoginRequiredMixin в наш ArticleCreateView. Убедитесь, что миксин находится слева от ListView, чтобы он читался первым. Мы хотим, чтобы ListView уже знал, что мы намерены ограничить доступ. И это все! Мы закончили. Группа в ВК "Django_Python" 282 Chapter 14: Permissions and Authorization Code # articles/views.py from django.contrib.auth.mixins import LoginRequiredMixin # new ... class ArticleCreateView(LoginRequiredMixin, CreateView): # new ... Вернитесь к URL создания новых сообщений по адресу http://127.0.0.1:8000/articles/new/, и вы увидите следующую ошибку: Error page Что происходит? Django автоматически перенаправил нас в местоположение по умолчанию для страницы входа в систему, которое находится по адресу / account / login, однако, если вы помните, в наших URL уровня проекта мы используем users/ в качестве маршрута. Вот почему наша страница авторизации находится на сайте users / login. Так как же нам рассказать об этом в ArticleCreateView? Если вы посмотрите на документацию для LoginRequired mixin, она скажет нам ответ. Мы можем добавить login_url для переопределения параметра по умолчанию. Мы используем именованный URL маршрут входа в систему. Группа в ВК "Django_Python" Chapter 14: Permissions and Authorization 283 Code # articles/views.py ... class ArticleCreateView(LoginRequiredMixin, CreateView): model = models.Article template_name = 'article_new.html' fields = ['title', 'body',] login_url = 'login' # new def form_valid(self, form): form.instance.author = self.request.user return super().form_valid(form) Попробуйте ссылку для создания новых сообщений еще раз: http://127.0.0.1:8000/articles/new/. Теперь она перенаправляет пользователей на страницу входа. Как мы и хотели! Updating views Теперь мы видим, что ограничение доступа к представлению - это просто добавление LoginRequiredMixin в начале всех существующих представлений и указание правильного login_url. Давайте обновим остальные представления наших статей, поскольку мы не хотим, чтобы пользователь мог создавать, читать, обновлять или удалять сообщения, если они не вошли в систему. Полностью файл views.py теперь должен выглядеть следующим образом: Группа в ВК "Django_Python" Chapter 14: Permissions and Authorization Code # articles/views.py from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import ListView, DetailView from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.urls import reverse_lazy from . import models class ArticleListView(LoginRequiredMixin, ListView): model = models.Article template_name = 'article_list.html' login_url = 'login' class ArticleDetailView(LoginRequiredMixin, DetailView): model = models.Article template_name = 'article_detail.html' login_url = 'login' class ArticleUpdateView(LoginRequiredMixin, UpdateView): model = models.Article fields = ['title', 'body', ] template_name = 'article_edit.html' login_url = 'login' Группа в ВК "Django_Python" 284 Chapter 14: Permissions and Authorization 285 class ArticleDeleteView(LoginRequiredMixin, DeleteView): model = models.Article template_name = 'article_delete.html' success_url = reverse_lazy('article_list') login_url = 'login' class ArticleCreateView(LoginRequiredMixin, CreateView): model = models.Article template_name = 'article_new.html' fields = ['title', 'body', ] login_url = 'login' def form_valid(self, form): form.instance.author = self.request.user return super().form_valid(form) Далее поиграйте с сайтом, чтобы убедиться, что перенаправления входа в систему теперь работают как положено. Если вам нужна помощь, чтобы вспомнить, какие правильные URLs, сначала войдите в систему и запишите URLs для каждого из маршрутов создания, редактирования, удаления и страницы всех статей. Заключение Наше приложение Newspaper почти готово. Наши статьи правильно настроены, настроены разрешения и авторизация, аутентификация пользователя в хорошем состоянии. Последний пункт это добавить возможность другим зарегистрированным пользователям оставлять комментарии, о которых мы расскажем в следующей главе. Группа в ВК "Django_Python" Глава ��: Комментарии Есть два способа добавить комментарии на сайт нашей газеты. Первый - создать специальное приложение comments и связать его с articles, однако на данный момент это похоже на чрезмерное проектирование. Вместо этого мы можем просто добавить дополнительную модель comment в наше приложение articles и связать ее с моделью Article с помощью внешнего ключа. К концу этой главы пользователи смогут оставлять комментарии к статьям других пользователей. Модель Для начала мы можем добавить еще одну таблицу в нашу существующую базу данных под названием Comment. Эта модель будет иметь связь внешнего ключа многие-к-одному к Article: одна статья может иметь много комментариев, но не наоборот. Традиционно поле внешнего ключа называется просто моделью, на которую оно ссылается, поэтому это поле будет называться article. Остальные два поля будут comment и author. Откройте файл articles/models.py и под существующим кодом добавьте следующее. Группа в ВК "Django_Python" 287 Chapter 15: Comments Code # articles/models.py ... class Comment(models.Model): article = models.ForeignKey(Article, on_delete=models.CASCADE) comment = models.CharField(max_length= ) author = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, ) def __str__(self): return self.comment def get_absolute_url(self): return reverse('article_list') Наша модель Comment также имеет метод __str__ и метод get_absolute_url, который возвращает на страницу статьи. Поскольку мы обновили наши модели, пришло время создать новый файл миграции и применить его. Обратите внимание, что мы добавляем articles в конец команды, что является необязательным, мы указываем, что хотим использовать в миграции только приложение articles. Это хорошая привычка. Например, что делать, если мы внесли изменения в модели в двух разных приложениях? Если бы мы не указали приложение, то изменения обоих приложений были бы включены в один и тот же файл миграции, что затруднит в будущем отладку ошибок. Держите каждую миграцию как можно компактнее и сдержаннее. Группа в ВК "Django_Python" 288 Chapter 15: Comments Command Line (news) $ python manage.py makemigrations articles (news) $ python manage.py migrate Admin После создания новой модели хорошо поиграть с ней в приложении администратора, прежде чем отображать ее на нашем фактическом веб сайте. Добавьте Comment к вашему файлу admin.py что бы он был виден. Code # articles/admin.py from django.contrib import admin from . import models admin.site.register(models.Article) admin.site.register(models.Comment) Запустите сервер с помощью python manage.py runserver и перейдите на нашу главную страницу админки http://127.0.0.1:8000/admin/ Группа в ВК "Django_Python" 289 Chapter 15: Comments Admin page with Comments Под нашим приложением “Articles” вы увидите две таблицы: Comments и Articles. Нажмите на кнопку “+ Add” рядом с Comments. Вы увидите, что под статьей находится выпадающий список существующих статей, то же самое для автора, и есть текстовое поле рядом с Comment. Группа в ВК "Django_Python" 290 Chapter 15: Comments Admin Comments Выберите статью, напишите комментарий, а затем выберите автора, который не является вашим суперпользователем, возможно, testuser, как я сделал на картинке. Затем нажмите на кнопку “Save”. Вы должны увидеть свой комментарий на странице “Comments”. Группа в ВК "Django_Python" 291 Chapter 15: Comments Admin Comment One На этом этапе мы можем добавить дополнительное поле администратора, чтобы увидеть комментарий и статью на этой странице. Но не лучше ли просто просмотреть все модели комментариев, связанные с одной моделью публикации? Оказывается, мы это можем ,с помощью функции Django admin называемой inlines, которая отображает внешние ключевые связи приятным визуальным способом. Используются два основных встроенных представления: TabularInline и StackedInline. Единственное различие между ними - это шаблон для отображения информации. В TabularInline все поля модели отображаются в одной строке, в то время как в StackedInline каждое поле имеет свою собственную строку. Мы реализуем оба, так что вы сами можете решить, какой из них вам выбрать. Обновите articles/admin.py следующим образом в текстовом редакторе. Группа в ВК "Django_Python" 292 Chapter 15: Comments Code # articles/admin.py from django.contrib import admin from . import models class CommentInline(admin.StackedInline): model = models.Comment class ArticleAdmin(admin.ModelAdmin): inlines = [ CommentInline, ] admin.site.register(models.Article, ArticleAdmin) admin.site.register(models.Comment) Теперь вернитесь на главную страницу администратора по адресу http://127.0.0.1:8000/admin/ и нажмите “Articles”. Выберите статью, для которой вы только что добавили комментарий, в моем случае была “Local news”. Группа в ВК "Django_Python" 293 Chapter 15: Comments Admin change page Лучше, не правда ли? Мы можем просматривать и изменять все связанные статьи и комментарии в одном месте. Хотя лично я предпочитаю использовать TabularInline, поскольку он показывает больше информации в меньшем пространстве. Чтобы переключиться на него, нам нужно только изменить ваш CommentInline от admin.StackedInline к admin.TabularInline. Группа в ВК "Django_Python" 294 Chapter 15: Comments Code # articles/admin.py from django.contrib import admin from . import models class CommentInline(admin.TabularInline): model = models.Comment class ArticleAdmin(admin.ModelAdmin): inlines = [ CommentInline, ] admin.site.register(models.Article, ArticleAdmin) admin.site.register(models.Comment) Обновите страницу администратора и вы увидите новое изменение: все поля для каждой модели отображаются в одной строке. Группа в ВК "Django_Python" 295 Chapter 15: Comments TabularInline page Намного лучше. Теперь нам нужно обновить шаблон для отображения комментариев. Template(шаблон) Поскольку Comment находится в нашем существующем приложении articles , нам нужно только обновить существующие шаблоны для article_list.html и article_detail.html для отображения нашего нового контента. Нам не нужно создавать новые шаблоны и возиться с urls и представлениями. То, что мы хотим сделать, это отобразить все комментарии, связанные с конкретной статьей. Это называется “query”, поскольку мы запрашиваем у базы данных определенный бит информации. В нашем случае, работая с внешним ключом, мы хотим проследить обратную связь: в каждой статье ищем связанные модели Comment. Группа в ВК "Django_Python" 296 Chapter 15: Comments Django имеет встроенный синтаксис, который мы можем использовать известный как FOO_set, где FOO - это имя исходной модели в нижнем регистре. Таким образом, для нашей модели Article мы можем использовать article_set для доступа ко всем экземплярам модели. Но лично мне сильно не нравится этот синтаксис, поскольку я нахожу его запутанным и неинтуитивным. Лучший подход это добавить атрибут related_name в нашу модель, который позволяет явно задать имя этой обратной связи. Давайте сделаем это. Для начала добавьте атрибут related_name в нашу модель Comment. Хорошим значением по умолчанию является множественное число модели с ForeignKey. Code # articles/models.py ... class Comment(models.Model): article = models.ForeignKey( Article, on_delete=models.CASCADE, related_name='comments' # new ) Поскольку мы только что внесли изменения в нашу модель базы данных, нам нужно создать файл миграции и обновить базу данных. Остановите локальный сервер с помощью Control+c и выполните следующие две команды. Затем снова запустите сервер, мы будем использовать его в ближайшее время. Группа в ВК "Django_Python" 297 Chapter 15: Comments Command Line (news) $ python manage.py makemigrations articles (news) $ python manage.py migrate (news) $ python manage.py runserver Понимание запросов занимает некоторое время, поэтому не беспокойтесь, если идея обратных связей запутана. Я покажу вам, как реализовать код по желанию. И как только вы освоите эти основные примеры, вы сможете изучить, как фильтровать свои наборы запросов очень подробно, чтобы они возвращали именно ту информацию, которую вы хотите. В нашем файле article_list.html мы можем добавить наши комментарии в card-footer. Обратите внимание, что я перенес наши изменения и удалил ссылки в card-body. Чтобы получить доступ к каждому комментарию, мы вызываем article.comments.all, что означает сначала посмотреть на модель article , а затем comments, которые являются связанным именем всей модели Comment, и выбрать все включенные. Может потребоваться некоторое время, чтобы привыкнуть к этому синтаксису для ссылки на данные внешнего ключа в шаблоне! Code <!-- template/article_list.html --> {% extends 'base.html' %} {% block title %}Articles{% endblock %} {% block content %} {% for article in object_list %} <div class="card"> <div class="card-header"> <span class="font-weight-bold">{{ article.title }}</span> &middot; <span class="text-muted">by {{ article.author }} | {{ article.date }}</s\ Группа в ВК "Django_Python" 298 Chapter 15: Comments pan> </div> <div class="card-body"> <p>{{ article.body }}</p> <a href="{% url 'article_edit' article.pk %}">Edit</a> | <a href="{% url 'article_delete' article.pk %}">Delete</a> </div> <div class="card-footer"> {% for comment in article.comments.all %} <p> <span class="font-weight-bold">{{ comment.author }} &middot;</span> {{ comment }} </p> {% endfor %} </div> </div> <br /> {% endfor %} {% endblock content %} Если мы обновим страницу articles по адресу http://127.0.0.1:8000/articles/, мы увидим наш новый комментарий, отображаемый на странице. Группа в ВК "Django_Python" 299 Chapter 15: Comments Articles page with comments Эй! Это работает. Мы можем видеть оба комментария, перечисленные под сообщением. Заключение С большим количеством времени мы бы сосредоточились на формах, чтобы пользователь мог написать новую статью прямо на странице articles , а также добавить комментарии. Но главная цель этой главы - показать, как работают связи с внешними ключями в Django. Наше приложение Newspaper теперь завершено. Оно имеет надежный поток аутентификации пользователя, включая использование электронной почты для сброса паролей. Мы также используем CustomUser, поэтому, если мы хотим добавить дополнительные поля в нашу пользовательскую модель, это так же просто, как добавить дополнительное поле. Группа в ВК "Django_Python" 300 Chapter 15: Comments У нас уже есть поле возраста для всех пользователей, которое в настоящее время устанавливается в 0 по умолчанию. Если бы мы хотели, мы могли бы добавить возрастное раскрывающееся меню в форму регистрации и ограничить доступ пользователей только для пользователей старше 13 лет. Или мы можем предложить скидки пользователям старше 65 лет. Все, что мы захотим сделать с нашей CustomUser моделью, является опцией. Большинство веб-разработок следуют тем же шаблонам и с помощью веб фреймворка, такого как Django 99% того, что мы хотим сделать, с точки зрения функциональности либо уже включено либо требуется небольшая настройка существующей функции. Группа в ВК "Django_Python" Заключение Поздравляем с окончанием Django для начинающих! Начав с абсолютного нуля, мы создали пять различных веб приложений с нуля. Мы рассмотрели все основные функции Django: шаблоны, представления, urls, пользователей, модели, безопасность, тестирование и развертывание. Теперь у вас есть знания, чтобы идти дальше и создавать свои собственные современные веб сайты с Django. Как и в случае с любым новым навыком, важно практиковать и применять то, что вы только что узнали. Функциональность CRUD в нашем блоге и газете распространена во многих, многих других веб приложениях. Например, можно ли создать веб приложение списка задач? У вас уже есть все необходимые инструменты. Веб разработка это очень глубокая область, и есть еще многое, что узнать о том, что Django может предложить. Например, более продвинутый проект Django, скорее всего, будет использовать несколько файлов настроек, переменных и PostgreSQL локально вместо SQLite. Он может даже использовать сторонние пакеты, такие как django-allauth для социальной аутентификации, django-debug-toolbar для отладки и django-extensions для дополнительных вкусностей. Лучший способ узнать больше о Django и веб разработке в целом это подумать о проекте, который вы хотите построить, а затем шаг за шагом узнать, что вам нужно для его завершения. Дополнительным ресурсом, который может помочь, является DjangoX, который является реальным стартовым проектом, который имеет социальную аутентификацию и многое другое. Вы также можете подписаться на рассылку Django для начинающих для периодических обновлений нового контента и скидок на будущие книги. И последний ресурс мой личный сайт, wsvincent.com, который регулярно обновляется и содержит статьи о некоторых из этих передовых методов: Группа в ВК "Django_Python" 302 Conclusion • Django Social Authentication • Django Login Mega-Tutorial • Django, PostgreSQL, and Docker • Django Rest Framework Tutorial • Django Rest Framework with React Ресурсы Django Чтобы продолжить изучение Джанго, я рекомендую работать через следующие бесплатные онлайн уроки: • Official Polls Tutorial • Django Girls Tutorial • MDN: Django Web Framework • A Complete Beginner’s Guide to Django Я также настоятельно рекомендую Two Scoops of Django 1.11: Best Practices for the Django Web Framework, который является текущей Библией лучших практик для разработчиков Django. Книги Python Если вы новичок в Python, есть несколько отличных книг от начинающих до продвинутых Pythonistas: • Python Crash Course это фантастическое введение в Python, которое также проведет вас через три реальных проекта, включая приложение Django. • Think Python одновременно вводит в основы Python и информатики. Группа в ВК "Django_Python" 303 Conclusion • Automate the Boring Stuff это еще одно отличное руководство по обучению и использованию Python в реальных условиях. • The Hitchhiker’s Guide to Python содержит рекомендации по программированию на Python. • Python Tricks демонстрирует, как писать Python код. • Effective Python отличное руководство не только по Python, но и по программированию в целом. • Fluent Python удивительно и обеспечивает глубокое понимание языка Python. Блоги Эти сайты предоставляют регулярные высококачественные статьи по Python и вебразработке. • Real Python • Dan Bader • Trey Hunner • Full Stack Python • Ned Batchelder • Armin Ronacher • Kenneth Reitz • Daniel Greenfeld Обратная связь Если вы прошли через всю книгу, мне бы очень хотелось услышать ваши мысли. Что тебе понравилось или не понравилось? Какие области были особенно сложными? А какой новый контент вы хотели бы видеть? Со мной можно связаться по адресу will@wsvincent.com. Группа в ВК "Django_Python" От переводчика. Перевод не является профессиональным, прошу сильно не пинать эта книга мой первый опыт в переводе книг, но я старался максимально точно передать содержимое на русском языке. Изначально переводил для себя фрагментами как шпаргалки, которые позже были собраны воедино. Надеюсь труд автора и мой не будет напрасным для вас и вы получите необходимые знания. Вступайте в группу "в контакте" https://vk.com/Python_Django_RU делитесь своим мнением, обсуждайте и предлагайте новые книги к переводу. И если вы можете помочь с переводом других книг не стесняйтесь предлагать помощь, вместе прогресс будет быстрей. Группа в ВК "Django_Python"