🔩 Полный фуллстек: пишем сайт на Django, Vue и GraphQL
Шаг за шагом пишем сайт с бэкендом на Django, фронтендом на Vue и связкой между ними на GraphQL. Для всех любителей Python и современной веб-разработки.
Публикация представляет собой незначительно сокращенное пособие Дэйна Хилларда Build a Blog Using Django, Vue, and GraphQL.
Это руководство проведет вас через процесс создания серверной части на Django и клиентской части на Vue со связкой между ними в виде GraphQL. Это большой пошаговый проект, делайте перерывы по мере необходимости.
Из руководства вы узнаете:
- Как транслировать модели Django в GraphQL API.
- Как одновременно запустить сервер Django и приложение на Vue.
- Как администрировать Django-проект.
- Как использовать GraphQL API для отображения данных в браузере с помощью Vue.
Суть проекта на Django, Vue и GraphQL
Традиционно стартовым проектом для веба явлется блог — такие проекты включают все стандартные CRUD-операции: создание, чтение, обновление и удаление.
Мы разработаем небольшое приложение для ведения блога с некоторыми элементарными функциями. Авторы могут писать множество сообщений, сообщения могут иметь множество тегов, могут публиковаться или сохраняться в черновиках.
Бэкенд блога мы сделаем на Django, затем реализуем передачу контента GraphQL API и, наконец, воспользуемся Vue для отображения данных в браузере. Вот наши шаги:
- Настроить блог Django.
- Создать администратора блога Django.
- Настроить Graphene-Django.
- Настроить django-cors-headers.
- Настроить Vue.js.
- Настроить Vue Router.
- Создать компоненты Vue.
- Получить и отобразить даные.
Предварительные знания
Руководство будет легче воспринять, если вы уже знакомы с концепцией веб-приложений: как работают HTTP-запросы и API. В этом плане будет полезно прочитать нашу публикацию про Python и API.
Поскольку мы будем использовать для создания серверной части блога Django, хорошо бы ознакомиться с процедурой запуска проекта на Django. Возможно, стоит для начала попробовать создать проект на чистом Django.
Поскольку мы будем использовать для интерфейса пользователя Vue, будет полезен опыт работы с реактивным JavaScript. Если в прошлом вы манипулировали DOM-элементами только с помощью jQuery, знакомство с Vue станет хорошим продолжением.
Запросы GraphQL похожи на JSON-объекты и возвращают данные в формате JSON. Поэтому полезно разобраться, что это такое. Позже в этом руководстве вам потребуется установить Node.js, прочитайте наше руководство для новичков.
Шаг 1. Настраиваем Django
Создадим каталог, в котором будем хранить код проекта. Назовем его dvg
, сокращенно от Django-Vue-GraphQL:
Мы будем разделять фронтенд и бэкенд-код, так что неплохо сразу создать подкаталоги для бэкенда:
Весь код Django мы поместим в каталог backend
, полностью изолировав его от кода Vue.
Устанавливаем Django
Чтобы отделить зависимости проекта от других ваших проектов, создадим виртуальное окружение. Далее в руководстве предполагается, что вы запускате команды, связанные с Python и Django, в активированном виртуальном окружении. Для установки зависимостей создадим в директории backend
файл requirements.txt
:
Устанавливаем Django в виртуальном окружении:
Django установлен, инициализируем проект:
Команда создаст в каталоге backend
модуль manage.py
и внутренний пакет backend
. Структура каталогов теперь выглядит так:
Мы создали новый проект, а значит, нужно создать базу данных и осуществить миграцию:
Вы увидите список миграций:
Это создаст файл базы данных SQLite с именем db.sqlite3
, в котором будут храниться данные нашего проекта.
Есть база данных — можем создать суперпользователя:
Итак, мы установили Django, создали проект, провели миграции и добавили суперпользователя. В итоге у нас есть полностью функционирующее (пусть пока и пустое) приложение. Запускается оно так:
Результат можно посмотреть по адресу http://localhost:8000/
. Вы увидите стартовую страницу пустого Django-приложения. Кроме того, станет доступна страница http://localhost:8000/admin
, с помощью которой можно администрировать проект. Чтобы попасть внутрь, используйте логин и пароль суперпользователя.
Шаг 2. Создаем приложение
Проект на Django может содержать множество различных приложений. Обычно одно приложение соответствует одному смысловому блоку сайта, например, ленте новостей, магазину товаров или корзине. Создадим приложение блога:
Будет создана директория blog
с несколькими шаблонными файлами:
Позже в руководстве мы их дополним.
Вновь созданное приложение не добавляется по умолчанию в проект. Чтобы фреймворк знал, что приложение является частью проекта, дополняем список приложений в файле настроек проекта backend/settings.py
:
Это поможет Django найти информацию о приложении: модели данных и шаблоны URL-адресов.
Создаем модели данных для блога
Создадим три следующие модели данных:
Profile
хранит информацию о пользователях блога.Tag
содержит данные о категориях, по которым группируются записи блога.Post
используется для хранения контента и метаданных о каждом посте блога.
Все модели добавляются в соответствующий файл приложения blog/models.py
. Каждая модель наследуется от стандартных моделей Django:
Модель Profile
Модель профиля содержит несколько полей:
user
— связь с пользователем Django (связь один-к-одному).website
— опциональный URL, по которому можно узнать больше о пользователе.bio
— опциональное небольшое био («о себе»).
Импортируем из Django модуль настроек settings
и опишем класс для нашей новой модели:
Метод __str__
сделает удобнее отображение профилей в панели администратора .
Модель Tag
В модели Tag
будет единственное поле, короткое имя тега:
Модель Post
Модель Post
— самая сложная, содержит множество полей: заголовок (title
), подзаголовок (subtitle
), слаг (slug
, уникальная часть URL для нашего поста), контент поста (body
) и т.д.
Некоторые пояснения:
- В подклассе
Meta
мы указываем порядок сортировки постов (ordering
) по дате публикации. - Аргумент
on_delete = models.PROTECT
для поляauthor
гарантирует, что при удалении постов мы случайно не удалим автора. - Каждый тег может быть связан со многими сообщениями, поэтому для поля
tags
используется отношениеManyToManyField
.
Конфигурируем панель администратора
Для того, чтобы определить, как будут отображаться записи в панели администрирования блога, переходим в blog/admin.py
и импортируем созданные модели:
Создаем и регистрируем классы моделей:
У постов мы не показываем все поля подряд, а только необходимые для администрирования. К ним мы добавили возможности фильтрации, редактирования и поиска. Подробно эти настройки рассмотрены в статье Customize the Django Admin With Python.
Создаем миграции модели
В Django есть вся информация, необходимая для управления содержимым сайта, но сначала нужно обновить базу данных. Ранее в этом руководстве мы запускали миграции Django для встроенных моделей. Теперь создадим и запустим миграции уже для наших моделей:
Это создаст миграцию с именем по умолчанию 0001_initial.py
. Запустим миграцию с помощью команды управления migrate
:
Теперь у нас есть модели данных и мы настроили админпанель Django, чтобы добавлять и редактировать эти модели.
Запустите или перезапустите сервер разработки Django и зайдите в панель по адресу http://localhost:8000/admin
посмотреть, что изменилось. Вы увидите ссылки на списки тегов, профилей и сообщений, а также ссылки для добавления и редактирования каждого из них. Добавьте и отредактируйте несколько из них, чтобы увидеть, как отреагирует интерфейс администратора.
Шаг 3. Настройка Graphene-Django
В результате предыдущего этапа мы завершили основную работу над бэкендом. Далее можно было бы использовать механизмы маршрутизации URL и шаблонов Django для создания страниц, которые будут показывать читателям контент. Но вместо этого мы обернем созданную нами серверную часть в GraphQL API. За счет этого мы обеспечим более удобную работу на стороне клиента.
GraphQL позволяет получать только те данные, которые нам действительно нужны, что выгодно отличает эту технологию от RESTful API. GraphQL обеспечивает гибкость при проектировании данных, за счет чего мы можем получать новые структуры данных, не изменяя логику службы, предоставляющей GraphQL API.
Устанавливаем Graphene-Django
Для интеграции Django и GraphQL мы используем библиотеку Graphene-Django. Для установки библиотеки дополняем файл requirements.txt
:
Запускаем установку через менеджер пакетов pip:
Теперь нужно добавить приложение "graphene_django"
в список INSTALLED_APPS
в модуле settings.py
:
Настраиваем Graphene-Django
Параметр GRAPHENE
в файле settings.py
указывает Graphene-Django расположение схемы GraphQL. Для нашего примера этот путь соответствует blog.schema.schema
(саму схему мы вскоре создадим):
Добавляем шаблон URL для GraphQL и GraphiQL. Чтобы позволить Django обслуживать конечную точку GraphQL и интерфейс GraphiQL, добавим новый шаблон URL в backend/urls.py
. Поскольку мы не используем функции защиты от подделки межсайтовых запросов (CSRF) шаблонизатора Django, нам необходимо импортировать декоратор Django csrf_exempt
, чтобы пометить представление, как свободное от CSRF-защиты:
Добавляем паттерн в список переменной urlpatterns
:
Аргумент graphiql = True
указывает Graphene-Django сделать доступным GraphiQL-интерфейс .
Создаем GraphQL-схему. Теперь мы создадим схему GraphQL, похожую по своей логике на конфигурацию панели администратора. Схема состоит из нескольких классов, каждый из которых связан с определенной моделью Django, а также ещё одного класса, который показывает, как разрешать несколько важных типов запросов, которые понадобятся нам во внешнем интерфейсе.
В каталоге blog/
создадим новый модуль schema.py
. Импортируем из Graphene-Django DjangoObjectType
, модели блога и модель пользователя Django:
Создадим класс для каждой из наших моделей и модели User
. Имя каждого класса должно заканчиваться на Type
, потому что каждое из них соответствует типу GraphQL. Классы должны выглядеть следующим образом:
Ещё нам нужно создать класс Query
, наследуемый от graphene.ObjectType
. Этот класс объединит все созданные нами классы типов, и мы добавим к нему методы, указывающие способы запроса моделей. Сначала импортируем модуль graphene
:
Класс Query
требует ряда атрибутов, которые являются либо graphene.List
, (если запрос возращает несколько элементов), либо graphene.Field
(если запрос возвращает один элемент).
Для каждого из атрибутов мы создадим метод решения запроса. Мы разрешаем запрос, беря информацию, предоставленную в запросе, и возвращая в ответ соответствующий запрос Django. Метод каждого преобразователя должен начинаться с resolve_
, а остальная часть имени должна соответствовать атрибуту. Например, метод разрешения запросов для атрибута all_posts
должен называться resolve_all_posts
.
В итоге получается следующий сниппет:
Теперь у нас есть все типы и преобразователи для нашей схемы. Но помним, что переменная GRAPHENE
указывает на blog.schema.schema
. Создаем переменную схемы, которая обертывает класс Query
в graphene.Schema
, чтобы связать все это вместе:
В результатае переменная соответствует значению blog.schema.schema
, которое мы настроили для Graphene-Django ранее в этом руководстве.
Итак, мы обернули модель данных с помощью Graphene-Django, чтобы использовать эти данные в GraphQL API. Запустите сервер разработки Django и посетите страницу http://localhost:8000/graphql
. Вы должны увидеть интерфейс GraphiQL с некоторыми комментариями, объясняющими, как использовать инструмент.
Разверните раздел Docs
в правом верхнем углу экрана и щелкните по query:Query
. Вы должны увидеть каждый из запросов и типов, которые мы настроили в схеме.
Если вы еще не наполняли блог тестовыми данными, сделайте это сейчас. Попробуйте выполнить следующий запрос. Он должен вернуть список всех созданных сообщений:
Ответ должен вернуть список постов. Структура каждого поста должна соответствовать форме запроса, как в следующем примере:
Если вы сохранили несколько постов и видите их в ответе, значит, можно продолжать.
Шаг 4. Настраиваем django-cors-headers
Чтобы считать работу над бэкендом завершенной, сделаем еще один шаг. Серверная часть и интерфейс будут запускаются на разных портах, а на практике так и вообще могут запускаться на разных доменах. Поэтому важное значение принимает вопрос совместного использования ресурсов (CORS). Без поддержки CORS запросы от фронтенда к бэкенду обычно блокируются браузером.
Библиотека django-cors-headers
делает работу с CORS довольно безболезненной. Мы будем использовать эту библиотеку, чтобы указать Django отвечать на запросы, даже если они исходят из другого источника. Это позволит фронтенду правильно взаимодействовать с GraphQL API.
Установка. Добавляем название модуля в зависимости (requirements.txt
):
Устанавливаем:
Добавляем в список приложений INSTALLED_APPS
в файле settings.py
:
Теперь нужно подключить библиотеку в качестве промежуточного обработчика в переменной MIDDLEWARE
:
Документация django-cors-headers
советует ставить эту строку как можно выше в списке обработчиков.
CORS существует не просто так. Мы не хотим, чтобы наше приложение было доступно для использования из любого места в Интернете. Чтобы этого избежать, мы используем две настройки, чтобы определить, насколько мы хотим открыть GraphQL API:
CORS_ORIGIN_ALLOW_ALL
определяет, должен ли Django быть полностью открыт или полностью закрыт по умолчанию.CORS_ORIGIN_WHITELIST
определяет, для каких доменов приложение Django будет разрешать запросы.
Соответственно добавляем в settings.py
две следующие строки:
Такие настройки разрешат запросы только от нашего фронтенда, которые в конечном итоге мы будем запускать локально на порту 8080.
Бэкенд готов! У нас есть модель данных, интерфейс администратора, GraphQL API на базе GraphiQL и возможность запрашивать API из внешнего интерфейса, который мы создадим дальше. Отличный момент, чтобы передохнуть, если вы ещё этого не делали ⛱️.
Шаг 5. Настраиваем Vue.js
В качестве фронтенд-фреймворка мы будем использовать Vue. Как и Django, Vue предоставляет интерфейс для создания проекта. Используя этот подход, нам не придется устанавливать вручную множество отдельных зависимостей, необходимых для запуска проекта на Vue. Достаточно использовать npx:
Указанная комана создаст подкаталог frontend/
рядом с уже существующим каталогом backend/
, установит ряд зависимостей JavaScript и создаст некоторые каркасные файлы для нашего будущего фронтенд-приложения.
Установим плагины Vue
Чтобы правильно выполнять маршрутизацию и взаимодействовать с GraphQL API, нам понадобятся плагины Vue Router и Vue Apollo. При появлении запросов выбирайте параметры по умолчанию:
Этим командам потребуется время для установки зависимостей, они добавят или изменят некоторые файлы в проекте. Теперь мы можем запустить сервер разработки:
Итак, у нас есть приложение Django, работающее по адресу http://localhost:8000, и приложение Vue, которое запускается по адресу http://localhost:8080.
Шаг 6. Настраиваем Vue Router
Важной частью клиентских приложений является обработка маршрутизации без необходимости делать новые запросы к серверу. Распространенным решением в Vue является плагин Vue Router, который мы установили ранее.
В каталоге src/
создадим модуль router.js
. Этот файл будет содержать настройки сопоставления URL-адресов и компонентов Vue. Начнем с импорта Vue
и Vue Router
:
Каждый из следующих элементов импорта соответствует нашим будущим компонентам:
Регистрируем плагин:
Теперь создадим список маршрутов. Каждый маршрут имеет два параметра:
path
— URL-шаблон, похожий по смыслу на URL-шаблоны Django.component
— компонент Vue, соответствующий пути.
Создадим константу routes
:
Создадим новый экземпляр VueRouter
и экспортируем его из модуля router.js
, чтобы другие модули могли его использовать:
Далее в начале файла src/main.js
импортируем router
:
Передаем маршрут в экземпляр Vue:
На этом настройка Vue Router завершена. Мы создали маршруты для внешнего интерфейса, которые сопоставляют шаблон URL-адреса с отображаемым компонентом. Сами маршруты пока не работают, потому как указывают на компоненты, которые еще не созданы.
Шаг 7. Создаем компоненты Vue
Теперь Vue умеет работать с маршрутами, пора создать компоненты, которые будут отображать данные из конечной точки GraphQL:
AuthorLink
– ссылка на страницу автора (используется вPost
иPostList
).PostList
– список постов в блоге (используется вAllPosts
,Author
иPostsByTag
).AllPosts
– список постов, начиная с самых последних.PostsByTag
– список постов, связанных с заданным тегом, начиная с самых недавних.Post
– метаданные и контент публикации.Author
– информация об авторе и список написанных им постов.
Компонент AuthorLink
Первый компонент, который мы создадим, отображает ссылку на автора. В каталоге src/components/
создадим файл AuthorLink.vue
. Этот файл представляет собой однофайловый компонент (single file component, SFC) Vue. SFC в одном файле хранит HTML, JavaScript и CSS, необходимые для визуализации компонента.
AuthorLink
принимает prop-объект author
, структура которого соответствует данным об авторах в GraphQL API. Компонент должен отображать имя и фамилию пользователя, если они указаны, в противном случае — имя пользователя.
Файл AuthorLink.vue
должен выглядеть следующим образом:
Этот компонент не будет использовать GraphQL напрямую. Вместо этого другие компоненты передают информацию об авторе, используя свойство author
.
Компонент PostList
Компонент PostList
принимает prop-объект posts
, структура которого соответствует данным о сообщениях в нашем GraphQL API. Компонент отображает следующие вещи:
- Заголовок и подзаголовок поста, слинкованный с самой страницей поста.
- Ссылка на автора поста через
AuthorLink
(если логическая переменнаяshowAuthor
равнаtrue
). - Дата публикации поста.
- Мета-описание поста.
- Список тегов.
Создайте PostList.vue
в каталоге src/components/
. Шаблон компонента должен выглядеть следующим образом:
Код JavaScript должен выглядеть так:
Компонент PostList
получает данные через prop вместо прямого использования GraphQL.
В том же файле можно добавить несколько дополнительных стилей CSS, чтобы сделать список постов удобнее для чтения:
Компонент AllPosts
Следующий компонент, который мы создадим, — список постов в блоге. Он должен отображать две вещи:
- Заголовок, например "Недавние сообщения" или "Recent Posts".
- Список постов с помощью
PostList
.
Создаём AllPosts.vue
в каталоге src/components/
. Должно получиться так:
Позже мы заполним переменную allPosts
динамически с помощью GraphQL-запроса.
Компонент PostsByTag
Компонент PostsByTag
очень похож на компонент AllPosts
:
Компонент Author
Компонент Author
действует, как страница профиля автора. То есть компонент должен отображать следующее:
- Заголовок с именем автора.
- Ссылка на сайт автора (если указана).
- "О себе" автора (если предоставлено).
- Список постов автора.
Компонент Post
Компонент Post
является наиболее интересным, поскольку отвечает за отображение всей информации о публикации:
- Заголовок и подзаголовок.
- Автор (с помощью ссылки
AuthorLink
). - Дата публикации.
- Мета-описание.
- Содержание ("тело" поста).
- Список связанных тегов в виде ссылок.
За счет используемой модели данных и компонентной архитектуры нам потребуется совсем немного кода:
Компонент App
Прежде чем увидеть результаты работы, необходимо обновить компонент App
, созданный при первоначальной настройке Vue. Вместо отображения страницы-заставки Vue должен отображаться компонент AllPosts
.
В каталоге src/
откроем App.vue
и заменим содержимое следующим кодом:
Здесь описан заголовок с названием блога, который ведет на главную страницу, а также компонент Vue Router
, который отображает компонент для текущего маршрута.
Итак, мы подошли к концу седьмого (предпоследнего) шага. Если вы раньше не использовали Vue, этот шаг, возможно, был трудоемким. Однако мы достигли важной вехи — работающего приложения Vue с маршрутами и представлениями, готовыми для отображения данных.
Запустите сервер разработки Vue и перейдите по адресу http://localhost:8080. Вы должны увидеть заголовок блога и заголовки недавних публикаций. На последнем шаге мы воспользуемся Apollo для обращений к GraphQL API, чтобы соединить между собой интерфейс и серверную часть.
Шаг 8. Собираем данные
Пора получить данные из GraphQL API. Плагин Vue Apollo, который мы установили ранее, интегрирует Apollo во Vue и делает удобнее процедуру выполнения запросов к GraphQL API.
Vue Apollo уже настроен «из коробки», но нужно указать правильную конечную точку запроса. Мы также можем отключить WebSocket-соединение, которое плагин пытается использовать по умолчанию, поскольку это создает лишний шум на вкладках «Сеть» и «Консоль» в средствах разработки в браузере. Отредактируем определение apolloProvider
в модуле src/main.js
, указав свойства httpEndpoint
и wsEndpoint
:
Теперь мы можем добавить запросы для заполнения страниц. Мы сделаем это, добавив функцию created()
в несколько наших SFC. Эта функция — специальный хук жизненного цикла Vue, выполняемый в момент, когда компонент готовится к рендерингу на странице. Функцию можно использовать для запроса данных, которые мы хотим визуализировать. Мы создадим запросы для следующих компонентов:
Post
Author
PostByTag
AllPosts
Запрос для получения информации о посте (Post)
Запрос для отдельного поста принимает slug нужного сообщения и возвращает необходимую информацию для отображения публикации. Для создания запроса в функции created()
мы используем функции-помощники $apollo.query
и gql
. Функция created()
будет выглядеть следующим образом:
Запрос извлекает большую часть данных о публикации, авторе и тегах. Обратите внимание, что в запросе используется заполнитель $slug
, для заполнения которого применяется свойство variables
, передаваемое в $apollo.query
. Свойство slug
соответствует имени заполнителя $slug
. Мы встретим такой же шаблон в следующих запросах.
Запрос для Author
Запрос Author
принимает username
автора и возвращает информацию, необходимую для отображения автора и его списка постов. Должно получиться так:
В этом запросе используется postSet
, который может показаться знакомым по нашей модели данных Django. Название «post set» происходит от связи, которую Django создает для поля внешнего ключа. Graphene-Django автоматически предоставляет postSet
в GraphQL API.
Запрос для PostByTag
Запрос для PostsByTag
очень похож на предыдущие. Он принимает желаемый тег и возвращает список подходящих постов:
Вы уже могли заметить, что некоторые части запросов очень похожи друг на друга. Чтобы уменьшить дублирование кода, обратите внимание на концепцию GraphQL fragments.
Запрос для AllPosts
Запрос AllPosts
не требует ввода информации и возвращает тот же набор данных, что и запрос PostsByTag
. Должно получиться так:
Ура! Это последний запрос. Теперь каждый компонент получает данные, необходимые для отображения, а мы получили работающий блог. Запустите сервер разработки Django и сервер разработки Vue. Откройте http://localhost:8080 и посмотрите на результат.
Возможные следующие шаги
Мы начали с создания серверной части блога Django для администрирования, сохранения и обслуживания данных блога. Затем создали интерфейс Vue для использования и отображения этих данных. Наконец, научили их общаться с GraphQL, используя Graphene и Apollo.
Чтобы еще раз убедиться, что блог работает должным образом, можно попробовать следующее:
- Добавить пользователей и посты, чтобы увидеть разделение по авторам.
- Создать несколько сообщений без публикации, чтобы убедиться, что они не отображаются в блоге, а сохраняются в форме черновика.
Заключение
Итак, мы узнали, как использовать GraphQL для создания гибких типизированных представлений данных. Вы можете применять эти методы как в уже созданных приложениях Django, так и в тех, что вы только планируете создать. Как и другие API, этот подход применим для большинства современных фронтенд-фреймворков. Надеемся, что эта концепция пригодится вам ещё не раз.
Вот ещё несколько материалов о GraphQL: