DjangoアプリケーションからWebプッシュ通知を送信する方法

_著者はhttps://www.brightfunds.org/funds/open-internet-free-speech[Open Internet / Free Speech Fund]を選択してhttps://do.co/w4do-ctaの一部として寄付を受け取りました[寄付のために書く]プログラム。

前書き

Webは常に進化しており、以前はネイティブモバイルデバイスでしか利用できなかった機能を実現できるようになりました。 JavaScript https://developers.google.com/web/fundamentals/primers/service-workers/ [サービスワーカー]の導入により、バックグラウンド同期、オフラインキャッシュ、https:// developerの送信などを行うためのWebの新たな能力が得られました。 .mozilla.org / en / docs / Web / API / Push_API [プッシュ通知]。

プッシュ通知により、ユーザーはオプトインしてモバイルアプリケーションとWebアプリケーションの更新を受信できます。 また、カスタマイズされた関連コンテンツを使用して、ユーザーが既存のアプリケーションと再エンゲージすることもできます。

このチュートリアルでは、Ubuntu 18.04でDjangoアプリケーションを設定します。これは、ユーザーがアプリケーションにアクセスする必要があるアクティビティがあるたびにプッシュ通知を送信します。 これらの通知を作成するには、https://github.com/safwanrahman/django-webpush [Django-Webpush]パッケージを使用し、サービスワーカーを設定して登録し、クライアントに通知を表示します。 通知付きの動作中のアプリケーションは次のようになります。

image:https://assets.digitalocean.com/articles/django_push_18_04/web_push_final.png [Web push final]

前提条件

このガイドを始める前に、次のものが必要です。

  • 非rootユーザーとアクティブなファイアウォールを備えた1つのUbuntu 18.04サーバー。 Ubuntu 18.04の作成方法の詳細については、このhttps://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-18-04 [初期サーバーセットアップガイド]のガイドラインに従うことができます。サーバ。

  • これらのhttps://www.digitalocean.com/community/tutorials/how-to-install-the-django-web-framework-on-ubuntu-18-04#install-に従ってインストールされた「+ pip 」および「 venv +」 with-pip-in-a-virtual-environment [ガイドライン]。

  • ホームディレクトリに作成された「+」というDjangoプロジェクトは、https://www.digitalocean.com/community/tutorials/how-to-install-the-django-web-framework-on-でこれらのガイドラインに従って設定しますubuntu-18-04#creating-a-sample-project [Ubuntu 18.04でサンプルDjangoプロジェクトを作成]。 必ずhttps://www.digitalocean.com/community/tutorials/how-to-install-the-django-web-framework-on-ubuntu-18-04#modifying-allowed_hosts-in-the-django-settings ` settings.py `ファイルの[サーバーのIPアドレスを ` ALLOWED_HOSTS +`ディレクティブに追加]。

ステップ1-Django-WebpushのインストールとVapidキーの取得

Django-Webpushは、開発者がDjangoアプリケーションにWebプッシュ通知を統合して送信できるようにするパッケージです。 このパッケージを使用して、アプリケーションからプッシュ通知をトリガーして送信します。 このステップでは、Django-Webpushをインストールし、サーバーを識別して各リクエストの一意性を確保するために必要な_Voluntary Application Server Identification(VAPID)_キーを取得します。

前提条件で作成した `+〜/ +`プロジェクトディレクトリにいることを確認します。

cd ~/

仮想環境をアクティブにします。

source /bin/activate

`+ pip +`のバージョンをアップグレードして、最新であることを確認します。

pip install --upgrade pip

Django-Webpushをインストールします。

pip install django-webpush

パッケージをインストールしたら、 `+ settings.py `ファイルのアプリケーションのリストに追加します。 最初に ` settings.py +`を開きます:

nano ~/djangopush/djangopush/settings.py

`+ INSTALLED_APPS `のリストに ` webpush +`を追加します。

〜/ djangopush / djangopush / settings.py

...

INSTALLED_APPS = [
   ...

]
...

ファイルを保存して、エディターを終了します。

アプリケーションでhttps://docs.djangoproject.com/en/2.0/topics/migrations/[migrations]を実行して、データベーススキーマに加えた変更を適用します。

python manage.py migrate

出力は次のようになり、移行が成功したことを示します。

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

Webプッシュ通知を設定する次のステップは、VAPIDキーの取得です。 これらのキーはアプリケーションサーバーを識別し、特定のサーバーへのサブスクリプションを制限するため、プッシュサブスクリプションURLの機密性を減らすために使用できます。

VAPIDキーを取得するには、https://web-push-codelab.glitch.me/ [wep-push-codelab] Webアプリケーションに移動します。 ここでは、自動的に生成されたキーが提供されます。 秘密鍵と公開鍵をコピーします。

次に、VAPID情報の新しいエントリを `+ settings.py +`に作成します。 まず、ファイルを開きます。

nano ~/djangopush/djangopush/settings.py

次に、VAPIDの公開鍵と秘密鍵を使用して、「+ WEBPUSH_SETTINGS 」という新しいディレクティブと、「 AUTH_PASSWORD_VALIDATORS +」の下にメールを追加します。

〜/ djangopush / djangopush / settings.py

...

AUTH_PASSWORD_VALIDATORS = [
   ...
]







# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/

...

プレースホルダーの値「」、「」、「++」を独自の情報に置き換えることを忘れないでください。 メールアドレスは、プッシュサーバーで問題が発生した場合に通知される方法です。

次に、アプリケーションのホームページを表示し、購読しているユーザーにプッシュ通知をトリガーするビューを設定します。

ステップ2-ビューのセットアップ

このステップでは、https://docs.djangoproject.com/enで基本的な + home + viewを設定しますホームページの/2.1/ref/request-response/#django.http.HttpRequest [`+ HttpResponse `応答オブジェクト]と、 ` send_push `ビュー。 ビューは、Web要求から応答オブジェクトを返す関数です。 ` send_push +`ビューはDjango-Webpushライブラリを使用して、ユーザーがホームページに入力したデータを含むプッシュ通知を送信します。

`+〜/ django push / django push`フォルダーに移動します:

cd ~/djangopush/djangopush

フォルダ内で「+ ls +」を実行すると、プロジェクトのメインファイルが表示されます。

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

このフォルダー内のファイルは、前提条件でプロジェクトを作成するために使用した `+ django-admin `ユーティリティによって自動生成されます。 ` settings.py `ファイルには、インストール済みアプリケーションや静的ルートフォルダーなどのプロジェクト全体の設定が含まれています。 ` urls.py +`ファイルには、プロジェクトのURL設定が含まれています。 ここで、作成したビューに一致するようにルートを設定します。

プロジェクトのビューを含む、「+ views.py 」という名前の「〜/ djangopush / djangopush +」ディレクトリ内に新しいファイルを作成します。

nano ~/djangopush/djangopush/views.py

最初に作成するビューは、ユーザーがプッシュ通知を送信できるホームページを表示する「+ home +」ビューです。 ファイルに次のコードを追加します。

〜/ djangopush / djangopush / views.py

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 `で、これは ` django-webpush +`パッケージを使用して送信されたプッシュ通知を処理します。 POSTリクエストのみに制限され、https://docs.djangoproject.com/en/2.0/ref/csrf/ [Cross Site Request Forgery(CSRF)]保護から免除されます。 これにより、https://www.getpostman.com/postman [Postman]またはその他のRESTfulサービスを使用してビューをテストできます。 ただし、実稼働環境では、このデコレーターを削除して、ビューがCSRFに対して脆弱になるのを防ぐ必要があります。

`+ send_push `ビューを作成するには、まず次のインポートを追加してJSON応答を有効にし、 ` webpush `ライブラリの ` send_user_notification +`関数にアクセスします。

〜/ djangopush / djangopush / views.py

from django.http.response import , HttpResponse
from django.views.decorators.http import require_GET,

次に、 `+ require_POST +`デコレータを追加します。これは、ユーザーが送信したリクエスト本文を使用してプッシュ通知を作成およびトリガーします。

〜/ djangopush / djangopush / views.py

@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 `ビューに2つのデコレーターを使用しています。ビューをPOSTリクエストのみに制限する ` require_POST `デコレーターと、ビューをCSRF保護から免除する ` csrf_exempt +`デコレーターです。

このビューはPOSTデータを予期し、以下を実行します。リクエストの「+ body 」を取得し、https://docs.python.org/3/library/json.html [json]パッケージを使用して、JSONドキュメントをデシリアライズしますhttps://docs.python.org/3.6/library/json.html [` json.loads `]を使用してPythonオブジェクトに追加します。 ` json.loads +`は構造化されたJSONドキュメントを受け取り、それをPythonオブジェクトに変換します。

ビューは、リクエスト本文オブジェクトに3つのプロパティがあることを期待しています。

  • + head +:プッシュ通知のタイトル。

  • + body +:通知の本文。

  • + id +:リクエストユーザーの + id +

必要なプロパティのいずれかが欠落している場合、ビューは404“ Not Found”ステータスで + JSONResponse +`を返します。 指定された主キーを持つユーザーが存在する場合、ビューはhttps://docs.djangoproject.com/en/2.1/topics/http/shortcuts/#get-objectを使用して、一致する主キーを持つ `+ user +`を返します-または-404 [+ django.shortcuts `ライブラリの[` get_object_or_404 +`関数]。 ユーザーが存在しない場合、関数は404エラーを返します。

ビューは、 `+ webpush `ライブラリの ` send_user_notification +`関数も利用します。 この関数は3つのパラメーターを取ります。

  • + User +:プッシュ通知の受信者。

  • + payload +:通知情報。これには、通知 `+ head `および ` body +`が含まれます。

  • + ttl +:ユーザーがオフラインの場合に通知を保存する最大時間(秒単位)。

エラーが発生しない場合、ビューは200の「成功」ステータスとデータオブジェクトを持つ「+ JSONResponse 」を返します。 ` KeyError `が発生すると、ビューは500“ Internal Server Error”ステータスを返します。 オブジェクトのリクエストされたキーが存在しない場合、 ` KeyError +`が発生します。

次のステップでは、作成したビューと一致するように、対応するURLルートを作成します。

ステップ3-ビューへのURLのマッピング

Djangoでは、 `+ URLconf `と呼ばれるPythonモジュールでビューに接続するhttps://docs.djangoproject.com/en/2.0/topics/http/urls/[URLs]を作成できます。 このモジュールは、URLパス式をPython関数(ビュー)にマッピングします。 通常、プロジェクトを作成すると、URL構成ファイルが自動生成されます。 このステップでは、このファイルを更新して、前のステップで作成したビューの新しいルートと、通知をプッシュするユーザーをサブスクライブするエンドポイントを提供する ` django-webpush +`アプリのURLを含めます。

ビューの詳細については、https://www.digitalocean.com/community/tutorials/how-to-create-django-views [Djangoビューの作成方法]を参照してください。

`+ urls.py +`を開きます:

nano ~/djangopush/djangopush/urls.py

ファイルは次のようになります。

〜/ 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ライブラリのすべてのルートがプロジェクトに追加されるようにします。

〜/ djangopush / djangopush / urls.py

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

次に、最後のステップで作成したビューをインポートし、ビューをマップするために `+ urlpatterns +`リストを更新します:

〜/ djangopush / djangopush / urls.py

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



urlpatterns = [
                 path('admin/', admin.site.urls),



             ]

ここで、 `+ urlpatterns `リストは ` django-webpush `パッケージのURLを登録し、ビューをURL ` / send_push `と ` / home +`にマッピングします。

「+ / home +」ビューをテストして、意図したとおりに機能することを確認します。 プロジェクトのルートディレクトリにいることを確認します。

cd ~/djangopush

次のコマンドを実行してサーバーを起動します。

python manage.py runserver :8000

`+ http://:8000 +`に移動します。 次のホームページが表示されるはずです。

image:https://assets.digitalocean.com/articles/django_push_18_04/django_push_home.png [初期ホームページビュー]

この時点で、 `+ CTRL + C `でサーバーを強制終了できます。そして、 ` render +`関数を使用してテンプレートを作成し、ビューでそれらをレンダリングします。

ステップ4-テンプレートの作成

Djangoのテンプレートエンジンを使用すると、HTMLファイルに似たテンプレートを使用して、アプリケーションのユーザー向けレイヤーを定義できます。 このステップでは、 `+ home +`ビューのテンプレートを作成してレンダリングします。

プロジェクトのルートディレクトリに「+ templates +」というフォルダーを作成します。

mkdir ~/djangopush/templates

この時点でプロジェクトのルートフォルダで `+ ls +`を実行すると、出力は次のようになります。

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

+ templates`フォルダーに + home.html`というファイルを作成します:

nano ~/djangopush/templates/home.html

次のコードをファイルに追加して、ユーザーが情報を入力してプッシュ通知を作成できるフォームを作成します。

{% 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>

ファイルの「+ body 」には、2つのフィールドを持つフォームが含まれます。「 input 」要素には通知のヘッド/タイトルが、「 textarea +」要素には通知本文が含まれます。

ファイルの「+ head」セクションには、VAPID公開キーとユーザーのIDを保持する2つの「+ meta」タグがあります。 これら2つの変数は、ユーザーを登録してプッシュ通知を送信するために必要です。 AJAXリクエストをサーバーに送信し、ユーザーを識別するために「+ id 」が使用されるため、ここにはユーザーのIDが必要です。 現在のユーザーが登録ユーザーである場合、テンプレートはコンテンツとして「 id」を使用して「+ meta」タグを作成します。

次のステップは、Djangoにテンプレートの場所を伝えることです。 これを行うには、 `+ settings.py `を編集し、 ` TEMPLATES +`リストを更新します。

`+ settings.py +`ファイルを開きます:

nano ~/djangopush/djangopush/settings.py

以下を `+ DIRS +`リストに追加して、テンプレートディレクトリへのパスを指定します。

〜/ djangopush / djangopush / settings.py

...
TEMPLATES = [
   {
       'BACKEND': 'django.template.backends.django.DjangoTemplates',
       'DIRS': [],
       'APP_DIRS': True,
       'OPTIONS': {
           'context_processors': [
               ...
           ],
       },
   },
]
...

次に、 + views.py`ファイルで、 + home + ビューを更新して + home.html`テンプレートをレンダリングします。 ファイルを開きます。

nano ~/djangpush/djangopush/views.py

まず、追加のインポートを追加します。これには、「+ settings.py 」ファイルのプロジェクトのすべての設定を含む「 settings 」設定、および「 django.shortcuts 」の「 render +」関数が含まれます。

〜/ djangopush / djangopush / views.py

...
from django.shortcuts import , get_object_or_404
...
import json


...

次に、 `+ home +`ビューに追加した初期コードを削除し、作成したテンプレートのレンダリング方法を指定する次を追加します。

〜/ djangopush / djangopush / views.py

...

@require_GET
def home(request):

コードは次の変数を割り当てます。

  • + webpush_settings +:これには、 `+ settings `設定の ` WEBPUSH_SETTINGS +`属性の値が割り当てられます。

  • + vapid_key +:これは、 `+ webpush_settings `オブジェクトから ` VAPID_PUBLIC_KEY +`値を取得してクライアントに送信します。 この公開鍵は秘密鍵と照合され、公開鍵を持つクライアントがサーバーからプッシュメッセージを受信できることを確認します。

  • + user +:この変数は、着信リクエストから取得されます。 ユーザーがサーバーにリクエストを行うたびに、そのユーザーの詳細は「+ user +」フィールドに保存されます。

https://docs.djangoproject.com/en/2.1/topics/http/shortcuts/#render [+ render + function]は、HTMLファイルとhttps://docs.djangoproject.com/en/2.1を返します。 /ref/templates/api/#django.template.Context[context object]現在のユーザーとサーバーの無効な公開鍵が含まれています。 ここでは、3つのパラメーターを取ります: + request +、レンダリングされる + template +、およびテンプレートで使用される変数を含むオブジェクト。

テンプレートを作成し、 `+ home +`ビューを更新したら、静的ファイルを提供するためのDjangoの構成に進むことができます。

ステップ5-静的ファイルの提供

Webアプリケーションには、CSS、JavaScript、およびDjangoが「静的ファイル」と呼ぶ他の画像ファイルが含まれます。 Djangoを使用すると、プロジェクト内の各アプリケーションからすべての静的ファイルを収集して、それらが提供される単一の場所に集めることができます。 このソリューションは「+ django.contrib.staticfiles +」と呼ばれます。 このステップでは、静的ファイルが保存される場所をDjangoに伝えるために設定を更新します。

`+ settings.py +`を開きます:

nano ~/djangopush/djangopush/settings.py

`+ settings.py `で、最初に ` STATIC_URL +`が定義されていることを確認します:

〜/ djangopush / djangopush / settings.py

...
STATIC_URL = '/static/'

次に、Djangoが静的ファイルを探す「+ STATICFILES_DIRS +」というディレクトリのリストを追加します。

〜/ djangopush / djangopush / settings.py

...
STATIC_URL = '/static/'

これで、 `+ urls.py `ファイルで定義されたパスのリストに ` STATIC_URL +`を追加できます。

ファイルを開きます。

nano ~/djangopush/djangopush/urls.py

次のコードを追加します。これにより、 + static + URL設定がインポートされ、 `+ urlpatterns `リストが更新されます。 ここのヘルパー関数は、プロジェクトの静的ファイルを提供するために、 ` settings.py `ファイルで提供した ` STATIC_URL `および ` STATIC_ROOT +`プロパティを使用します。

〜/ djangopush / djangopush / urls.py

...



urlpatterns = [
   ...
]

静的ファイルの設定を構成したら、アプリケーションのホームページのスタイル設定に進むことができます。

ステップ6-ホームページのスタイル設定

静的ファイルを提供するようにアプリケーションを設定したら、外部スタイルシートを作成し、それを `+ home.html `ファイルにリンクしてホームページをスタイルできます。 すべての静的ファイルは、プロジェクトのルートフォルダーの ` static +`ディレクトリに保存されます。

`+ static `フォルダーと ` static `フォルダー内に ` css +`フォルダーを作成します。

mkdir -p ~/djangopush/static/css

`+ css `フォルダー内の ` styles.css +`というCSSファイルを開きます。

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

ホームページに次のスタイルを追加します。

〜/ 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;
}

スタイルシートを作成したら、https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#std:templatetag-static [静的テンプレートタグ]を使用して `+ home.html `ファイルにリンクできます。 ` home.html +`ファイルを開きます:

nano ~/djangopush/templates/home.html

`+ head +`セクションを更新して、外部スタイルシートへのリンクを含めます。

〜/ djangopush / templates / home.html

{% load static %}
<!DOCTYPE html>
<html lang="en">

<head>
   ...

</head>
<body>
   ...
</body>
</html>

メインプロジェクトディレクトリにいることを確認し、サーバーを再起動して作業を確認します。

cd ~/djangopush
python manage.py runserver :8000

`+ http://:8000 +`にアクセスすると、次のようになります。

image:https://assets.digitalocean.com/articles/django_push_18_04/push_styled_home.png [ホームページビュー] もう一度、 ` CTRL + C +`でサーバーを強制終了できます。

`+ home.html`ページの作成とスタイル設定が正常に完了したので、ユーザーがサブスクライブしてホームページにアクセスするたびに通知をプッシュできます。

ステップ7-Service Workerの登録とプッシュ通知へのユーザーのサブスクライブ

Webプッシュ通知は、サブスクライブしているアプリケーションに更新がある場合にユーザーに通知したり、過去に使用したアプリケーションとの再エンゲージメントを促すことができます。 それらは、https://developer.mozilla.org/en-US/docs/Web/API/Push_API [push] APIとhttps://developer.mozilla.org/en-US/docs/の2つのテクノロジーに依存しています。 Web / API / Notifications_API [notifications] API。 どちらのテクノロジーも、サービスワーカーの存在に依存しています。

サーバーがサービスワーカーに情報を提供し、サービスワーカーが通知APIを使用してこの情報を表示すると、プッシュが呼び出されます。

ユーザーをプッシュにサブスクライブし、サブスクリプションからサーバーに情報を送信して登録します。

`+ static `ディレクトリで、 ` js +`というフォルダーを作成します。

mkdir ~/djangopush/static/js

`+ registerSw.js +`というファイルを作成します。

nano ~/djangopush/static/js/registerSw.js

Service Workerを登録する前に、ユーザーのブラウザでService Workerがサポートされているかどうかを確認する次のコードを追加します。

〜/ djangopush / static / js / registerSw.js

const registerSw = async () => {
   if ('serviceWorker' in navigator) {
       const reg = await navigator.serviceWorker.register('sw.js');
       initialiseState(reg)

   } else {
       showNotAllowed("You can't send push notifications ☹️😢")
   }
};

最初に、 `+ registerSw `関数は、登録する前にブラウザーがService Workerをサポートしているかどうかを確認します。 登録後、登録データで ` initializeState `関数を呼び出します。 ブラウザでService Workerがサポートされていない場合、 ` showNotAllowed +`関数を呼び出します。

次に、 `+ registerSw +`関数の下に次のコードを追加して、ユーザーがサブスクライブを試みる前にプッシュ通知を受信する資格があるかどうかを確認します。

〜/ djangopush / static / js / registerSw.js

...

const initialiseState = (reg) => {
   if (!reg.showNotification) {
       showNotAllowed('Showing notifications isn\'t supported ☹️😢');
       return
   }
   if (Notification.permission === 'denied') {
       showNotAllowed('You prevented us from showing notifications ☹️🤔');
       return
   }
   if (!'PushManager' in window) {
       showNotAllowed("Push isn't allowed in your browser 🤔");
       return
   }
   subscribe(reg);
}

const showNotAllowed = (message) => {
   const button = document.querySelector('form>button');
   button.innerHTML = `${message}`;
   button.setAttribute('disabled', 'true');
};

`+ initializeState +`関数は以下をチェックします:

  • `+ reg.showNotification +`の値を使用して、ユーザーが通知を有効にしているかどうか。

  • ユーザーが通知を表示するためのアプリケーション許可を付与したかどうか。

  • ブラウザが + PushManager + APIをサポートしているかどうか。 これらのチェックのいずれかが失敗すると、 `+ showNotAllowed +`関数が呼び出され、サブスクリプションが中止されます。

`+ showNotAllowed +`関数はボタンにメッセージを表示し、ユーザーが通知を受信する資格がない場合は無効にします。 また、ユーザーがアプリケーションによる通知の表示を制限している場合、またはブラウザーがプッシュ通知をサポートしていない場合にも、適切なメッセージを表示します。

ユーザーがプッシュ通知を受け取る資格があることを確認したら、次のステップは `+ pushManager `を使用してそれらをサブスクライブすることです。 ` showNotAllowed +`関数の下に次のコードを追加します。

〜/ djangopush / static / js / registerSw.js

...

function urlB64ToUint8Array(base64String) {
   const padding = '='.repeat((4 - base64String.length % 4) % 4);
   const base64 = (base64String + padding)
       .replace(/\-/g, '+')
       .replace(/_/g, '/');

   const rawData = window.atob(base64);
   const outputArray = new Uint8Array(rawData.length);
   const outputData = outputArray.map((output, index) => rawData.charCodeAt(index));

   return outputData;
}

const subscribe = async (reg) => {
   const subscription = await reg.pushManager.getSubscription();
   if (subscription) {
       sendSubData(subscription);
       return;
   }

   const vapidMeta = document.querySelector('meta[name="vapid-key"]');
   const key = vapidMeta.content;
   const options = {
       userVisibleOnly: true,
       // if key exists, create applicationServerKey property
       ...(key && {applicationServerKey: urlB64ToUint8Array(key)})
   };

   const sub = await reg.pushManager.subscribe(options);
   sendSubData(sub)
};

`+ pushManager.getSubscription `関数を呼び出すと、アクティブなサブスクリプションのデータが返されます。 アクティブなサブスクリプションが存在する場合、サブスクリプション情報をパラメーターとして渡して、 ` sendSubData +`関数が呼び出されます。

アクティブなサブスクリプションが存在しない場合、Base64 URLセーフでエンコードされたVAPID公開キーは、 `+ urlB64ToUint8Array `関数を使用してUint8Arrayに変換されます。 次に、VAPID公開キーと ` userVisible `値をオプションとして使用して、 ` pushManager.subscribe +`が呼び出されます。 利用可能なオプションの詳細については、https://developers.google.com/web/fundamentals/push-notifications/subscribing-a-user#uservisibleonly_options [こちら]をご覧ください。

ユーザーを正常にサブスクライブしたら、次のステップはサーバーにサブスクリプションデータを送信することです。 データは、 `+ django-webpush `パッケージによって提供される ` webpush / save_information `エンドポイントに送信されます。 ` subscribe +`関数の下に次のコードを追加します。

〜/ djangopush / static / js / registerSw.js

...

const sendSubData = async (subscription) => {
   const browser = navigator.userAgent.match(/(firefox|msie|chrome|safari|trident)/ig)[0].toLowerCase();
   const data = {
       status_type: 'subscribe',
       subscription: subscription.toJSON(),
       browser: browser,
   };

   const res = await fetch('/webpush/save_information', {
       method: 'POST',
       body: JSON.stringify(data),
       headers: {
           'content-type': 'application/json'
       },
       credentials: "include"
   });

   handleResponse(res);
};

const handleResponse = (res) => {
   console.log(res.status);
};

registerSw();

+ save_information +`エンドポイントには、サブスクリプション( `+ subscribe +`および `+ unsubscribe +)のステータス、サブスクリプションデータ、およびブラウザーに関する情報が必要です。 最後に、 `+ registerSw()+`関数を呼び出して、ユーザーのサブスクライブプロセスを開始します。

完成したファイルは次のようになります。

〜/ djangopush / static / js / registerSw.js

const registerSw = async () => {
   if ('serviceWorker' in navigator) {
       const reg = await navigator.serviceWorker.register('sw.js');
       initialiseState(reg)

   } else {
       showNotAllowed("You can't send push notifications ☹️😢")
   }
};

const initialiseState = (reg) => {
   if (!reg.showNotification) {
       showNotAllowed('Showing notifications isn\'t supported ☹️😢');
       return
   }
   if (Notification.permission === 'denied') {
       showNotAllowed('You prevented us from showing notifications ☹️🤔');
       return
   }
   if (!'PushManager' in window) {
       showNotAllowed("Push isn't allowed in your browser 🤔");
       return
   }
   subscribe(reg);
}

const showNotAllowed = (message) => {
   const button = document.querySelector('form>button');
   button.innerHTML = `${message}`;
   button.setAttribute('disabled', 'true');
};

function urlB64ToUint8Array(base64String) {
   const padding = '='.repeat((4 - base64String.length % 4) % 4);
   const base64 = (base64String + padding)
       .replace(/\-/g, '+')
       .replace(/_/g, '/');

   const rawData = window.atob(base64);
   const outputArray = new Uint8Array(rawData.length);
   const outputData = outputArray.map((output, index) => rawData.charCodeAt(index));

   return outputData;
}

const subscribe = async (reg) => {
   const subscription = await reg.pushManager.getSubscription();
   if (subscription) {
       sendSubData(subscription);
       return;
   }

   const vapidMeta = document.querySelector('meta[name="vapid-key"]');
   const key = vapidMeta.content;
   const options = {
       userVisibleOnly: true,
       // if key exists, create applicationServerKey property
       ...(key && {applicationServerKey: urlB64ToUint8Array(key)})
   };

   const sub = await reg.pushManager.subscribe(options);
   sendSubData(sub)
};

const sendSubData = async (subscription) => {
   const browser = navigator.userAgent.match(/(firefox|msie|chrome|safari|trident)/ig)[0].toLowerCase();
   const data = {
       status_type: 'subscribe',
       subscription: subscription.toJSON(),
       browser: browser,
   };

   const res = await fetch('/webpush/save_information', {
       method: 'POST',
       body: JSON.stringify(data),
       headers: {
           'content-type': 'application/json'
       },
       credentials: "include"
   });

   handleResponse(res);
};

const handleResponse = (res) => {
   console.log(res.status);
};

registerSw();

次に、 + home.html +`の `+ register.js`ファイルに + script`タグを追加します。 ファイルを開きます。

nano ~/djangopush/templates/home.html

+ body`要素の終了タグの前に + script`タグを追加します。

〜/ djangopush / templates / home.html

{% load static %}
<!DOCTYPE html>
<html lang="en">

<head>
  ...
</head>
<body>
  ...

</body>
</html>

サービスワーカーがまだ存在しないため、アプリケーションを実行したままにしておくか、もう一度起動しようとすると、エラーメッセージが表示されます。 サービスワーカーを作成して、これを修正しましょう。

ステップ8-サービスワーカーの作成

プッシュ通知を表示するには、アプリケーションのホームページにアクティブなサービスワーカーがインストールされている必要があります。 `+ push +`イベントをリッスンし、準備ができたらメッセージを表示するサービスワーカーを作成します。

Service Workerのスコープはドメイン全体にする必要があるため、アプリケーションのルートにインストールする必要があります。 この記事のプロセスの詳細については、https://developers.google.com/web/fundamentals/primers/service-workers/#register_a_service_worker [サービスワーカーの登録方法]をご覧ください。 私たちのアプローチは、 + templates`フォルダーに + sw.js`ファイルを作成し、それをビューとして登録することです。

ファイルを作成します。

nano ~/djangopush/templates/sw.js

Service Workerにプッシュイベントをリッスンするように指示する次のコードを追加します。

〜/ djangopush / templates / sw.js

// Register event listener for the 'push' event.
self.addEventListener('push', function (event) {
   // Retrieve the textual payload from event.data (a PushMessageData object).
   // Other formats are supported (ArrayBuffer, Blob, JSON), check out the documentation
   // on https://developer.mozilla.org/en-US/docs/Web/API/PushMessageData.
   const eventInfo = event.data.text();
   const data = JSON.parse(eventInfo);
   const head = data.head || 'New Notification 🕺🕺';
   const body = data.body || 'This is default content. Your notification didn\'t have one 🙄🙄';

   // Keep the service worker alive until the notification is created.
   event.waitUntil(
       self.registration.showNotification(head, {
           body: body,
           icon: 'https://i.imgur.com/MZM3K5w.png'
       })
   );
});

サービスワーカーはプッシュイベントをリッスンします。 コールバック関数では、 `+ event `データはテキストに変換されます。 イベントデータにこれらが含まれていない場合は、デフォルトの ` title `および ` body `文字列を使用します。 ` showNotification +`関数は、通知タイトル、表示される通知のヘッダー、およびhttps://developers.google.com/web/fundamentals/push-notifications/display-a-notification#visual_options[options]を受け取りますパラメータとしてのオブジェクト。 optionsオブジェクトには、通知の視覚的なオプションを設定するためのいくつかのプロパティが含まれています。

サービスワーカーがドメイン全体で機能するには、アプリケーションのルートにインストールする必要があります。 https://docs.djangoproject.com/en/2.1/topics/class-based-views/ [+ TemplateView +]を使用して、サービスワーカーがドメイン全体にアクセスできるようにします。

`+ urls.py +`ファイルを開きます:

nano ~/djangopush/djangopush/urls.py

`+ urlpatterns +`リストに新しいimportステートメントとパスを追加して、クラスベースのビューを作成します。

〜/ djangopush / djangopush / urls.py

...


urlpatterns = [
                 ...

             ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

`+ TemplateView `のようなクラスベースのビューを使用すると、柔軟で再利用可能なビューを作成できます。 この場合、 ` TemplateView.as_view `メソッドは、最近作成されたサービスワーカーをテンプレートとして、 ` application / x-javascript `をテンプレートの ` content_type +`として渡すことにより、サービスワーカーのパスを作成します。

これで、Service Workerが作成され、ルートとして登録されました。 次に、プッシュ通知を送信するためのフォームをホームページに設定します。

ステップ9-プッシュ通知の送信

ホームページのフォームを使用すると、ユーザーはサーバーの実行中にプッシュ通知を送信できます。 PostmanなどのRESTfulサービスを使用してプッシュ通知を送信することもできます。 ユーザーがホームページのフォームからプッシュ通知を送信すると、データには「+ head 」と「 body 」、および受信ユーザーの「 id +」が含まれます。 データは次のように構成する必要があります。

{
   head: "Title of the notification",
   body: "Notification body",
   id: "User's id"
}

フォームの「+ submit 」イベントをリッスンし、ユーザーが入力したデータをサーバーに送信するには、「〜/ djangopush / static / js 」ディレクトリに「 site.js +」というファイルを作成します。

ファイルを開きます。

nano ~/djangopush/static/js/site.js

まず、フォーム入力の値とテンプレートの `+ meta `タグに保存されたユーザーIDを取得できるようにするために、フォームに ` submit +`イベントリスナーを追加します。

〜/ djangopush / static / js / site.js

const pushForm = document.getElementById('send-push__form');
const errorMsg = document.querySelector('.error');

pushForm.addEventListener('submit', async function (e) {
   e.preventDefault();
   const input = this[0];
   const textarea = this[1];
   const button = this[2];
   errorMsg.innerText = '';

   const head = input.value;
   const body = textarea.value;
   const meta = document.querySelector('meta[name="user_id"]');
   const id = meta ? meta.content : null;
   ...
   // TODO: make an AJAX request to send notification
});

+ pushForm +`関数は、フォーム内の `+ input、` + textarea`、および `+ button`を取得します。 また、名前属性「+ user_id 」やタグの「 content 」属性に保存されているユーザーIDなど、「 meta 」タグから情報を取得します。 この情報を使用して、サーバー上の「 / send_push +」エンドポイントにPOSTリクエストを送信できます。

サーバーにリクエストを送信するには、ネイティブhttps://developer.mozilla.org/en/docs/Web/API/Fetch_API[Fetch] APIを使用します。 ここでFetchを使用しているのは、ほとんどのブラウザでサポートされており、機能するために外部ライブラリを必要としないためです。 追加したコードの下で、 `+ pushForm +`関数を更新して、AJAXリクエストを送信するためのコードを含めます。

〜/ djangopush / static / js / site.js

const pushForm = document.getElementById('send-push__form');
const errorMsg = document.querySelector('.error');

pushForm.addEventListener('submit', async function (e) {
    ...
   const id = meta ? meta.content : null;

    if (head && body && id) {
       button.innerText = 'Sending...';
       button.disabled = true;

       const res = await fetch('/send_push', {
           method: 'POST',
           body: JSON.stringify({head, body, id}),
           headers: {
               'content-type': 'application/json'
           }
       });
       if (res.status === 200) {
           button.innerText = 'Send another 😃!';
           button.disabled = false;
           input.value = '';
           textarea.value = '';
       } else {
           errorMsg.innerText = res.message;
           button.innerText = 'Something broke 😢..  Try again?';
           button.disabled = false;
       }
   }
   else {
       let error;
       if (!head || !body){
           error = 'Please ensure you complete the form 🙏🏾'
       }
       else if (!id){
           error = "Are you sure you're logged in? 🤔. Make sure! 👍🏼"
       }
       errorMsg.innerText = error;
   }
});

3つの必須パラメータ「+ head 」、「 body 」、および「 id +」が存在する場合、リクエストを送信し、送信ボタンを一時的に無効にします。

完成したファイルは次のようになります。

〜/ djangopush / static / js / site.js

const pushForm = document.getElementById('send-push__form');
const errorMsg = document.querySelector('.error');

pushForm.addEventListener('submit', async function (e) {
   e.preventDefault();
   const input = this[0];
   const textarea = this[1];
   const button = this[2];
   errorMsg.innerText = '';

   const head = input.value;
   const body = textarea.value;
   const meta = document.querySelector('meta[name="user_id"]');
   const id = meta ? meta.content : null;

   if (head && body && id) {
       button.innerText = 'Sending...';
       button.disabled = true;

       const res = await fetch('/send_push', {
           method: 'POST',
           body: JSON.stringify({head, body, id}),
           headers: {
               'content-type': 'application/json'
           }
       });
       if (res.status === 200) {
           button.innerText = 'Send another 😃!';
           button.disabled = false;
           input.value = '';
           textarea.value = '';
       } else {
           errorMsg.innerText = res.message;
           button.innerText = 'Something broke 😢..  Try again?';
           button.disabled = false;
       }
   }
   else {
       let error;
       if (!head || !body){
           error = 'Please ensure you complete the form 🙏🏾'
       }
       else if (!id){
           error = "Are you sure you're logged in? 🤔. Make sure! 👍🏼"
       }
       errorMsg.innerText = error;
   }
});

最後に、 + site.js`ファイルを + home.html`に追加します。

nano ~/djangopush/templates/home.html

`+ script`タグを追加します:

〜/ djangopush / templates / home.html

{% load static %}
<!DOCTYPE html>
<html lang="en">

<head>
  ...
</head>
<body>
  ...

</body>
</html>

この時点で、サービスワーカーはセキュアドメインまたは「+ localhost +」でのみ機能するため、アプリケーションを実行したままにしておくか、再起動しようとするとエラーが表示されます。 次のステップでは、https://ngrok.com/ [ngrok]を使用して、Webサーバーへの安全なトンネルを作成します。

手順10-アプリケーションをテストするための安全なトンネルの作成

サービスワーカーは、「+ localhost +」を除くすべてのサイトで機能するために安全な接続を必要とします。これは、接続を乗っ取り、応答をフィルタリングして作成できるためです。 このため、https://ngrok.com [ngrok]を使用してサーバーの安全なトンネルを作成します。

2番目のターミナルウィンドウを開き、ホームディレクトリにいることを確認します。

cd ~

前提条件でクリーンな18.04サーバーで開始した場合は、 `+ unzip +`をインストールする必要があります。

sudo apt update && sudo apt install unzip

ngrokをダウンロードします。

wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
unzip ngrok-stable-linux-amd64.zip

ターミナルから「+ ngrok 」コマンドにアクセスできるように、「 ngrok 」を「 / usr / local / bin +」に移動します。

sudo mv ngrok /usr/local/bin

最初のターミナルウィンドウで、プロジェクトディレクトリにいることを確認し、サーバーを起動します。

cd ~/djangopush
python manage.py runserver :8000

アプリケーションの安全なトンネルを作成する前に、これを行う必要があります。

2番目のターミナルウィンドウで、プロジェクトフォルダーに移動し、仮想環境をアクティブにします。

cd ~/djangopush
source my_env/bin/activate

アプリケーションへの安全なトンネルを作成します。

ngrok http :8000

安全なngrok URLに関する情報を含む次の出力が表示されます。

Outputngrok by @inconshreveable                                                                                                                       (Ctrl+C to quit)

Session Status                online
Session Expires               7 hours, 59 minutes
Version                       2.2.8
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http:// -> 203.0.113.0:8000
Forwarding                    https:// -> 203.0.113.0:8000

Connections                   ttl     opn     rt1     rt5     p50     p90
                             0       0       0.00    0.00    0.00    0.00

コンソール出力から ++`をコピーします。 `+ settings.py`ファイルの + ALLOWED_HOSTS`のリストに追加する必要があります。

別のターミナルウィンドウを開き、プロジェクトフォルダーに移動して、仮想環境をアクティブにします。

cd ~/djangopush
source my_env/bin/activate

`+ settings.py +`ファイルを開きます:

nano ~/djangopush/djangopush/settings.py

ngrokセキュアトンネルで `+ ALLOWED_HOSTS +`のリストを更新します。

〜/ djangopush / djangopush / settings.py

...

ALLOWED_HOSTS = ['your_server_ip', '']
...

ログインするには、安全な管理ページに移動します: + https:/// admin / +。 次のような画面が表示されます。

画像:https://assets.digitalocean.com/articles/django_push_18_04/ngrok_login.png [ngrok管理者ログイン]

この画面でDjango管理者ユーザー情報を入力します。 これは、https://www.digitalocean.com/community/tutorials/how-to-install-the-django-web-framework-on-ubuntu-18の管理インターフェイスにログインしたときに入力した情報と同じである必要があります-04#testing-the-development-server [前提条件の手順]。 これで、プッシュ通知を送信する準備ができました。

ブラウザで `+ https:// +`にアクセスします。 通知を表示する許可を求めるプロンプトが表示されます。 [許可]ボタンをクリックして、ブラウザにプッシュ通知を表示します。

image:https://assets.digitalocean.com/articles/django_push_18_04/allow_push_two.png [プッシュ通知リクエスト]

記入済みのフォームを送信すると、次のような通知が表示されます。

image:https://assets.digitalocean.com/articles/django_push_18_04/web_push_final.png [通知のスクリーンショット]

通知を受け取った場合、アプリケーションは期待どおりに機能しています。

サーバーでプッシュ通知をトリガーし、サービスワーカーの助けを借りて通知を受信および表示するWebアプリケーションを作成しました。 また、アプリケーションサーバーからプッシュ通知を送信するために必要なVAPIDキーを取得する手順も実行しました。

結論

このチュートリアルでは、通知APIを使用して、プッシュ通知、サービスワーカーのインストール、プッシュ通知の表示をユーザーにサブスクライブする方法を学習しました。

クリックすると、アプリケーションの特定の領域を開くように通知を構成することで、さらに先へ進むことができます。 このチュートリアルのソースコードはhttps://github.com/HackAfro/django-push [こちら]にあります。