Отправка push-уведомлений из веб-приложений Django: подготовка среды

Интернет постоянно развивается. Сегодня он поддерживает функции, ранее доступные только на мобильных устройствах. Появление инструмента  JavaScript service workers открывает в сети такие новые возможности, как фоновая синхронизация, оффлайн-кэширование и отправка push-уведомлений.

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

Данная серия мануалов поможет вам создать приложение Django, которое будет отправлять push-уведомления в случае, если пользователь должен посетить приложение. Для создания таких уведомлений мы будем использовать пакет Django-Webpush. Также мы покажем, как настроить и зарегистрировать service worker для отображения уведомлений на клиенте.

Требования

1: Установка Django-Webpush и создание ключей VAPID

Django-Webpush — это пакет, который позволяет интегрировать push-уведомления в приложение Django. Его мы и будем использовать для запуска и отправки push-уведомлений. Давайте установим пакет Django-Webpush и получим ключи VAPID (Voluntary Application Server Identification), которые нужны для идентификации сервера и поддержки уникальности запросов.

Перейдите в каталог вашего проекта ~/djangopush:

cd ~/djangopush

Включите виртуальную среду:

source my_env/bin/activate

Обновите pip:

pip install --upgrade pip

Установите пакет Django-Webpush:

pip install django-webpush

Затем добавьте пакет в список в файле settings.py. Откройте settings.py:

nano ~/djangopush/djangopush/settings.py

И добавьте webpush в список установленных приложений, INSTALLED_APPS:

...
INSTALLED_APPS = [
...,
'webpush',
]
...

Сохраните и закройте файл.

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

python manage.py migrate

Если миграция прошла успешно, вывод будет выглядеть следующим образом:

Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, webpush
Running migrations:
Applying webpush.0001_initial... OK

Теперь нужно получить ключи VAPID. Они позволяют идентифицировать сервер приложения и снизить секретность URL-адресов подписки (так как они ограничивают подписку определенным сервером).

Чтобы получить VAPID ключи, откройте приложение wep-push-codelab. Оно предоставит вам автоматически сгенерированные ключи. Скопируйте полученные закрытые и открытые ключи.

Затем создайте в файле settings.py новую запись для ключей VAPID. Откройте этот файл:

nano ~/djangopush/djangopush/settings.py

Затем добавьте директиву WEBPUSH_SETTINGS и укажите в ней открытые и закрытые ключи VAPID, а в AUTH_PASSWORD_VALIDATORS – свой адрес электронной почты:

...
AUTH_PASSWORD_VALIDATORS = [
...
]
WEBPUSH_SETTINGS = {
"VAPID_PUBLIC_KEY": "your_vapid_public_key",
"VAPID_PRIVATE_KEY": "your_vapid_private_key",
"VAPID_ADMIN_EMAIL": "admin@example.com"
}
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/
...

Вместо your_vapid_public_key, your_vapid_private_key и admin@example.com укажите ваши данные. При возникновении проблем на сервере push-уведомлений вы получите сообщение на ваш адрес электронной почты.

Теперь давайте настроим представления (или виды), с помощью которых будет отображаться домашняя страница приложения и отправляться push-уведомления подписчикам.

2: Настройка видов

На этом этапе нужно создать вид send_push и базовый вид по имени home с объектом-ответом HttpResponse, который будет использоваться для отображения домашней страницы. Виды — это функции, которые возвращают ответы для веб-запросов. Вид send_push будет использовать библиотеку Django-Webpush для отправки push-уведомлений, в которых будут находиться данные, указанные пользователем на домашней странице.

Перейдите в каталог ~/djangopush/djangopush:

cd ~/djangopush/djangopush

Запустите команду ls, чтобы найти главные файлы проекта:

/__init__.py
/settings.py
/urls.py
/wsgi.py

Утилита django-admin, которую вы ранее использовали для создания проекта, автоматически генерирует эти файлы в этом каталоге. В settings.py хранятся конфигурации всего проекта, в том числе установленные приложения и статичный корневой каталог. В urls.py находится конфигурация URL. Здесь можно настроить маршруты согласно созданным видам.

В каталоге ~/djangopush/djangopush создайте файл views.py, который будет хранить виды проекта:

nano ~/djangopush/djangopush/views.py

Первый вид, который мы создадим, — это home, он отвечает за отображение домашней страницы, откуда пользователи смогут отправлять push-уведомления. Вставьте в файл такой код:

from django.http.response import HttpResponse
from django.views.decorators.http import require_GET
@require_GET
def home(request):
return HttpResponse('<h1>Home Page<h1>')

Вид home формируется с помощью декоратора require_GET, который ограничивает его запросами GET. Обычно виды возвращают ответ для каждого поступившего запроса. Наш вид будет отправлять простой HTML тег в качестве ответа.

Затем давайте создадим вид send_push, который будет обрабатывать отправленные push уведомления при помощи пакета django-webpush. Он ограничится только запросами POST и не будет защищаться от межсайтовой подделки запросов (CSRF). Так мы сможем протестировать вид с помощью Postman или другого RESTful сервиса.

Примечание: Но в настоящем рабочем проекте вам нужно удалить этот декоратор, чтобы защитить ваши виды от CSRF.

Чтобы создать вид send_push, добавьте следующие импорты, они активируют ответы JSON и предоставят доступ к функции send_user_notification из библиотеки webpush:

from django.http.response import JsonResponse, HttpResponse
from django.views.decorators.http import require_GET, require_POST
from django.shortcuts import get_object_or_404
from django.contrib.auth.models import User
from django.views.decorators.csrf import csrf_exempt
from webpush import send_user_notification
import json

Затем добавьте декоратор require_POST, который будет использовать тело отправленного пользователем запроса, чтобы создать и вызвать push-уведомление.

@require_GET
def home(request):
...
@require_POST
@csrf_exempt
def send_push(request):
try:
body = request.body
data = json.loads(body)
if 'head' not in data or 'body' not in data or 'id' not in data:
return JsonResponse(status=400, data={"message": "Invalid data format"})
user_id = data['id']
user = get_object_or_404(User, pk=user_id)
payload = {'head': data['head'], 'body': data['body']}
send_user_notification(user=user, payload=payload, ttl=1000)
return JsonResponse(status=200, data={"message": "Web push successful"})
except TypeError:
return JsonResponse(status=500, data={"message": "An error occurred"})

Для вида send_push мы используем два декоратора: require_POST, который ограничивает вид запросами POST, и csrf_exempt, который отключает защиту CSRF для данного вида.

Этот вид ждет поступления данных POST и работает так: получив тело запроса, он десериализует документ JSON с помощью пакета json в объект Python, используя json.loads. Библиотека json.loads получает структурированный документ JSON и конвертирует его в объект Python.

Объект запроса для данного вида должен иметь три свойства:

  • head: заголовок push-уведомления.
  • body: тело уведомления.
  • id: id отправившего запрос пользователя.

Если какое-то из этих свойств отсутствует, вид выдаст ошибку JSONResponse со статусом 404 “Not Found”. Если пользователь с указанным первичным ключом существует, вид вернет user с соответствующим ключом с помощью функции get_object_or_404 из библиотеки django.shortcuts. Если пользователя не существует, функция выдаст ошибку 404.

Кроме того, этот вид использует функцию send_user_notification из библиотеки webpush. Она принимает три параметра:

  • User: адресат push-уведомления.
  • payload: само уведомление, которое включает head и body .
  • ttl: максимальный срок (в секундах), в течение которого уведомление буде храниться, если пользователь оффлайн.

Если ошибок нет, вид вернет JSONResponse со статусом 200 “Success” и объектом данных. Если возникла ошибка KeyError, вид вернет статус 500 “Internal Server Error”. KeyError возникает, если отсутствует запрошенный ключ объекта.

3: Разметка URL-адресов для представлений

Django позволяет создавать URL-адреса, которые можно подключить к видам с помощью модуля Python под названием URLconf. Он размечает маршруты URL для функций Python (видов). Обычно конфигурационный файл для URL автоматически создается вместе с проектом. Давайте обновим этот файл и включим в него новые маршруты для видов, созданных на предыдущем этапе, а также URL-адреса приложения django-webpush, которые будут обслуживать конечные точки подписанных на push-уведомления пользователей.

Читайте также: Создание видов в Django

Откройте файл urls.py:

nano ~/djangopush/djangopush/urls.py

Он выглядит примерно так:

"""untitled URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2.Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]

Сейчас нам нужно привязать созданные ранее виды к URL-ам. Для начала добавьте импорт include, чтобы убедиться, что все маршруты библиотеки Django-Webpush будут добавлены в проект.

"""webpushdjango URL Configuration
...
"""
from django.contrib import admin
from django.urls import path, include

Затем импортируйте виды, созданные в предыдущем разделе, и обновите список urlpatterns для разметки видов:

"""webpushdjango URL Configuration
...
"""
from django.contrib import admin
from django.urls import path, include
from .views import home, send_push
urlpatterns = [
path('admin/', admin.site.urls),
path('', home),
path('send_push', send_push),
path('webpush/', include('webpush.urls')),
]

Список urlpatterns содержит URL-адреса пакета django-webpush и связвает виды с URL-ами /send_push и /home.

Давайте проверим, как работает вид /home. Перейдите в корневой каталог проекта:

cd ~/djangopush

Запустите сервер с помощью этой команды:

python manage.py runserver your_server_ip:8000

Перейдите по адресу http://your_server_ip:8000. Вы должны увидеть простую базовую домашнюю страницу:

Home Page

Теперь можно остановить сервер с помощью Ctrl+C. Далее мы будем создавать шаблоны и отобразим их в наших видах с помощью функции render.

4: Создание шаблонов

Движок шаблонов Django позволяет задать те уровни приложения, к которым будет доступ у пользователей. Шаблоны аналогичны файлам HTML. На этом этапе мы создадим и отобразим шаблон для вида home.

Создайте каталог templates в корневом каталоге проекта:

mkdir ~/djangopush/templates

Запустив команду ls в корневом каталоге проекта, вы получите такой вывод:

/djangopush
/templates
db.sqlite3
manage.py
/my_env

Создайте файл home.html в каталоге templates:

nano ~/djangopush/templates/home.html

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

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="vapid-key" content="{{ vapid_key }}">
{% if user.id %}
<meta name="user_id" content="{{ user.id }}">
{% endif %}
<title>Web Push</title>
<link href="https://fonts.googleapis.com/css?family=PT+Sans:400,700" rel="stylesheet">
</head>
<body>
<div>
<form id="send-push__form">
<h3 class="header">Send a push notification</h3>
<p class="error"></p>
<input type="text" name="head" placeholder="Header: Your favorite airline 😍">
<textarea name="body" id="" cols="30" rows="10" placeholder="Body: Your flight has been cancelled 😱😱😱"></textarea>
<button>Send Me</button>
</form>
</div>
</body>
</html>

Тело файла содержит форму с двумя полями: input – для заголовка уведомления, а textarea – для тела уведомления.

Раздел head содержит два тега meta для хранения открытого ключа VAPID и идентификатора пользователя. Эти переменные необходимы для регистрации пользователя и отправки ему push-уведомления. Идентификатор пользователя необходим потому, что мы будем направлять запросы AJAX на сервер, а id будет использоваться для идентификации пользователя. Если текущий пользователь зарегистрирован, шаблон создаст тег meta, а id пользователя использует в качестве контента.

Теперь нужно показать Django, где должны храниться шаблоны. Для этого мы отредактируем файл settings.py и обновим список TEMPLATES.

Откройте settings.py:
nano ~/djangopush/djangopush/settings.py

Вставьте эти строки в список DIRS, чтобы указать путь к каталогу шаблонов:

...
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
...
],
},
},
]
...

В файле views.py обновите вид home, чтобы он мог обрабатывать шаблон home.html. Откройте файл:

nano ~/djangpush/djangopush/views.py

Сначала нужно добавить импорты, в том числе и конфигурацию settings, в которой находятся все параметры проекта из settings.py и функция render из django.shortcuts:

...
from django.shortcuts import render, get_object_or_404
...
import json
from django.conf import settings
...

Затем нужно удалить начальный код, добавленный в вид home, и вставить в файл следующие данные. Они определяют, как будет отображаться шаблон:

...
@require_GET
def home(request):
webpush_settings = getattr(settings, 'WEBPUSH_SETTINGS', {})
vapid_key = webpush_settings.get('VAPID_PUBLIC_KEY')
user = request.user
return render(request, 'home.html', {user: user, 'vapid_key': vapid_key})

Код содержит такие переменные:

  • webpush_settings: присваивает значение атрибуту WEBPUSH_SETTINGS из конфигурации settings.
  • vapid_key: извлекает значение VAPID_PUBLIC_KEY из webpush_settings для отправки клиенту. Этот открытый ключ сопоставляется с закрытым. Эта процедура позволяет проверить, позволено ли клиенту с открытым ключом получать push-сообщения от сервера.
  • user: переменная, которая поступает из входящего запроса. В этом поле сохраняются данные пользователя, который отправляет запрос на сервер.

Функция render возвращает файл HTML и объект context, в котором находится текущий пользователь и открытый ключ VAPID. Здесь нужно указать три параметра: request (запрос), template (шаблон для отображения) и object (объект, который содержит переменные, используемые в шаблоне).

5: Обслуживание статичных файлов

Веб-приложения содержат файлы CSS, JavaScript и другие файлы, которые Django воспринимает как статичные. Статичные файлы каждого приложения в вашем проекте Django собирает в одном месте, откуда они и будут обслуживаться. Это называется django.contrib.staticfiles. Сейчас нам нужно обновить настройки и показать Django, где будут храниться статичные файлы.

Откройте settings.py:

nano ~/djangopush/djangopush/settings.py

В файле settings.py нужно определить STATIC_URL:

...
STATIC_URL = '/static/'

Затем нужно добавить список каталогов STATICFILES_DIRS, в которых Django будет искать статичные файлы:

...
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]

Добавьте STATIC_URL в список путей в файле urls.py. Откройте файл:

nano ~/djangopush/djangopush/urls.py

Вставьте этот код. Он импортирует конфигурацию  URL-адресов static  и обновит список urlpatterns. Вспомогательная функция использует STATIC_URL и STATIC_ROOT, которые вы определили в файле settings.py.

...
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
...
]  + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

6: Настройка стиля домашней страницы

Когда приложение готово к обслуживанию статичных файлов, можно создать таблицу стилей и связать ее с файлом home.html. Так определяется стиль домашней страницы. Все статичные файлы будут храниться в каталоге static внутри корневого каталога проекта.

Создайте static, а внутри создайте каталог css:

mkdir -p ~/djangopush/static/css

Откройте файл styles.css в каталоге css:

nano ~/djangopush/static/css/styles.css

Вставьте следующие стили для домашней страницы приложения:

body {
height: 100%;
background: rgba(0, 0, 0, 0.87);
font-family: 'PT Sans', sans-serif;
}
div {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
form {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 35%;
margin: 10% auto;
}
form > h3 {
font-size: 17px;
font-weight: bold;
margin: 15px 0;
color: orangered;
text-transform: uppercase;
}
form > .error {
margin: 0;
font-size: 15px;
font-weight: normal;
color: orange;
opacity: 0.7;
}
form > input, form > textarea {
border: 3px solid orangered;
box-shadow: unset;
padding: 13px 12px;
margin: 12px auto;
width: 80%;
font-size: 13px;
font-weight: 500;
}
form > input:focus, form > textarea:focus {
border: 3px solid orangered;
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.2);
outline: unset;
}
form > button {
justify-self: center;
padding: 12px 25px;
border-radius: 0;
text-transform: uppercase;
font-weight: 600;
background: orangered;
color: white;
border: none;
font-size: 14px;
letter-spacing: -0.1px;
cursor: pointer;
}
form > button:disabled {
background: dimgrey;
cursor: not-allowed;
}

Создав таблицу стилей, вы можете связать ее с файлом home.html с помощью тегов статичного шаблона. Откройте home.html:

nano ~/djangopush/templates/home.html

Обновите раздел head, добавив в него ссылку на внешнюю таблицу стилей:

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
...
<link href="{% static '/css/styles.css' %}" rel="stylesheet">
</head>
<body>
...
</body>
</html>

Перейдите в главный каталог проекта, а затем запустите свой сервер, чтобы проверить, как все работает:

cd ~/djangopush
python manage.py runserver your_server_ip:8000

Посетите http://your_server_ip:8080, и вы увидите домашнюю страницу вашего приложения.

Остановите сервер, нажав CTRL+C.

Заключение

В этой части вы подготовили базовое приложение, установили все зависимости и настроили стили домашней страницы. Теперь у вас есть тестовое веб-приложение Django, на примере которого можно настроить отправку push-уведомлений. Об этом речь пойдет в следующей части мануала.

Tags: , , ,