El autor seleccionΓ³ a Open Internet/Free Speech Fund para recibir una donaciΓ³n como parte del programa Write for DOnations.
IntroducciΓ³n
La web evoluciona de manera constante y ahora puede lograr las funcionalidades que antes solo estaban disponibles en dispositivos mΓ³viles nativos. La introducciΓ³n de los trabajos de servicio de JavaScript incorporΓ³ a la Web habilidades reciΓ©n descubiertas para actividades como la sincronizaciΓ³n en segundo plano, el almacenamiento en cachΓ© fuera de lΓnea y el envΓo de notificaciones push.
Las notificaciones push permiten a los usuarios recibir actualizaciones para aplicaciones mΓ³viles y web. TambiΓ©n permiten que estos vuelvan a usar aplicaciones existentes mediante contenido personalizado y pertinente.
A travΓ©s de este tutorial, configurarΓ‘ una en Ubuntu 18.04 aplicaciΓ³n de Django que envΓe notificaciones push cuando haya alguna actividad en la cual se requiera que el usuario ingrese a la aplicaciΓ³n. Para crear estas notificaciones, utilizarΓ‘ el paquete Django-Webpush, y configurarΓ‘ y registrarΓ‘ un trabajo de servicio para mostrar las notificaciones al cliente. La aplicaciΓ³n, en condiciones de funcionamiento y con las notificaciones, tendrΓ‘ este aspecto:

Requisitos previos
Para completar esta guΓa, necesitarΓ‘ lo siguiente:
Paso 1: Instalar Django-Webpush y generar claves de Vapid
Django-Webpush es un paquete que permite a los desarrolladores integrar y enviar notificaciones push web en aplicaciones de Django. Usaremos este paquete para activar y enviar las notificaciones desde nuestra aplicaciΓ³n. En este paso, instalarΓ‘ Django-Webpush y obtendrΓ‘ las claves de IdentificaciΓ³n voluntaria del servidor de aplicaciones (VAPID) necesarias para identificar su servidor y garantizar la singularidad de cada solicitud.
AsegΓΊrese de posicionarse en el directorio del proyecto ~/djangopush
que creΓ³ en los requisitos previos:
Active su entorno virtual:
- source my_env/bin/activate
Actualice su versiΓ³n de pip
para garantizar que estΓ© vigente:
- pip install --upgrade pip
Instale Django-Webpush:
- pip install django-webpush
DespuΓ©s de instalar el paquete, agrΓ©guelo a la lista de aplicaciones de su archivo settings.py
. Primero abra settings.py
:
- nano ~/djangopush/djangopush/settings.py
AΓ±ada webpush
a la lista de INSTALLED_APPS
:
~/djangopush/djangopush/settings.py
...
INSTALLED_APPS = [
...,
'webpush',
]
...
Guarde el archivo y cierre el editor.
Ejecute migraciones en la aplicaciΓ³n para implementar los cambios que realizΓ³ en el esquema de su base de datos:
El resultado tendrΓ‘ el siguiente aspecto, lo cual indicarΓ‘ que la migraciΓ³n se realizΓ³ con Γ©xito:
Output
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, webpush
Running migrations:
Applying webpush.0001_initial... OK
El siguiente paso para configurar las notificaciones push web consiste en obtener claves de VAPID. Estas claves identifican el servidor de la aplicaciΓ³n y pueden utilizarse para reducir la confidencialidad de las URL de suscripciones push, ya que limitan las suscripciones a un servidor especΓfico.
Para obtener claves de VAPID, dirΓjase a la aplicaciΓ³n web de wep-push-codelab. AquΓ, recibirΓ‘ claves generadas de forma automΓ‘tica. Copie las claves privadas y pΓΊblicas.
A continuaciΓ³n, cree una nueva entrada en settings.py
para su informaciΓ³n de VAPID. Primero, abra el archivo:
- nano ~/djangopush/djangopush/settings.py
A continuaciΓ³n, agregue una nueva directiva llamada WEBPUSH_SETTINGS
con sus claves pΓΊblicas y privadas de VAPID y su correo electrΓ³nico por debajo de AUTH_PASSWORD_VALIDATORS
:
~/djangopush/djangopush/settings.py
...
AUTH_PASSWORD_VALIDATORS = [
...
]
WEBPUSH_SETTINGS = {
"VAPID_PUBLIC_KEY": "your_vapid_public_key",
"VAPID_PRIVATE_KEY": "your_vapid_private_key",
"VAPID_ADMIN_EMAIL": "[email protected]"
}
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/
...
No olvide sustituir los valores del marcador de posiciΓ³n your_vapid_p
ublickey, `yourvapidpublickeyy
[email protected]` por su propia informaciΓ³n. A travΓ©s de su direcciΓ³n de correo electrΓ³nico, se le notificarΓ‘ si el servidor push experimenta problemas.
A continuaciΓ³n, configuraremos las vistas que mostrarΓ‘n la pΓ‘gina de inicio de la aplicaciΓ³n y activarΓ‘n notificaciones push para los usuarios suscritos.
Paso 2: Configurar vistas
En este paso, configuraremos una vista home
bΓ‘sica con el objeto response HttpResponse
para nuestra pΓ‘gina de inicio, junto con una vista de send_push.
Las vistas son funciones que muestran objetos response de solicitudes web. La vista de send_push
usarΓ‘ la biblioteca de Django-Webpush para enviar notificaciones push que contienen los datos ingresados por un usuario en la pΓ‘gina de inicio.
DirΓjase a la carpeta ~/djangopush/djangopush
:
- cd ~/djangopush/djangopush
Ejecutar ls
dentro de la carpeta le mostrarΓ‘ los archivos principales del proyecto:
Output
/__init__.py
/settings.py
/urls.py
/wsgi.py
Los archivos de esta carpeta se generan de forma automΓ‘tica a travΓ©s de la utilidad django-admin
que utilizΓ³ para crear su proyecto en los requisitos previos. El archivo settings.py
contiene configuraciones de todo el proyecto, como las aplicaciones instaladas y la carpeta root estΓ‘tica. El archivo urls.py
contiene las configuraciones de URL para el proyecto. AquΓ es donde establecerΓ‘ las rutas para que coincidan con las vistas que creΓ³.
Cree un nuevo archivo dentro del directorio ~/djangopush/djangopush
llamado views.py
, que contendrΓ‘ las vistas para su proyecto:
- nano ~/djangopush/djangopush/views.py
La primera vista que haremos es home
, que mostrarΓ‘ la pΓ‘gina de inicio en la cual los usuarios pueden enviar notificaciones push. AΓ±ada el siguiente cΓ³digo al archivo:
~/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>')
La vista home
estΓ‘ representada por el decorador require_GET
, que la limita a exclusivamente a solicitudes GET. Una vista suele mostrar una respuesta a cada solicitud que se le hace. Esta vista muestra una etiqueta HTML simple como respuesta.
La siguiente vista que crearemos es send_push
, que se encargarΓ‘ de las notificaciones push enviadas usando el paquete django-webpush
. Se limitarΓ‘ ΓΊnicamente a solicitudes POST y quedarΓ‘ exento de la protecciΓ³n contra la* falsificaciΓ³n de solicitud entre sitios cruzados* (CSRF). Realizar esto le permitirΓ‘ probar la vista usando Postman o cualquier otro servicio de RESTful. Sin embargo, para la producciΓ³n debe quitar este decorador a fin de evitar que sus vistas sean vulnerables a CSRF.
Para crear la vista send_push
, primero agregue las siguientes importaciones a fin de habilitar las respuestas de JSON y acceder a la funciΓ³n send_user_notification
en la biblioteca webpush
:
~/djangopush/djangopush/views.py
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
A continuaciΓ³n, agregue el decorador require_POST
, que usarΓ‘ el cuerpo de la solicitud enviada por el usuario para crear y activar una notificaciΓ³n push.
~/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"})
Usaremos dos decoradores para la vista send_push
: el decorador require_POST
, que limita la vista ΓΊnicamente a las solicitudes de POST, y el decorador de csrf_exempt
, que exenta a la vista de la protecciΓ³n CSRF.
Esta vista espera datos de POST y realiza lo siguiente: obtiene el body
de la solicitud y, usando el paquete de json, deserializa el documento JSON a un objeto de Python con json.loads
. json.loads
obtiene un documento JSON estructurado y lo convierte en un objeto de Python.
La vista espera que el objeto body de la solicitud tenga tres propiedades:
head
: el tΓtulo de la notificaciΓ³n push.body
: el cuerpo de la notificaciΓ³n.id
: el id
del usuario de la solicitud.
Si falta alguna de las propiedades necesarias, en la vista se mostrarΓ‘ una respuesta JSONResponse
con un estado 404 βNot Foundβ. Si el usuario con la clave primaria dada existe, la vista mostrarΓ‘ el user
con la clave primaria correspondiente usando la funciΓ³n get_objet_or_404
de la biblioteca django.shortcuts
. Si el usuario no existe, la funciΓ³n mostrarΓ‘ un error 404.
La vista tambiΓ©n utiliza la funciΓ³n send_user_notification
de la biblioteca webpush
. Esta funciΓ³n toma tres parΓ‘metros:
User
: el destinatario de la notificaciΓ³n push.payload
: la informaciΓ³n de la notificaciΓ³n, que incluye el head
y el body
de esta.ttl
: el tiempo mΓ‘ximo en segundos durante el cual la notificaciΓ³n debe almacenarse si el usuario se encuentra fuera de lΓnea.
Si no se producen errores, la vista muestra una respuesta JSONResponse
con un estado 200 βSuccessβ y un objeto de datos. Si se produce un KeyError
, la vista mostrarΓ‘ un estado 500 de βInternal Server Errorβ. Un KeyError
se produce cuando no existe la clave solicitada de un objeto.
En el siguiente paso, crearemos las rutas URL correspondientes para que coincidan con las vistas que creamos.
Paso 3: Asignar URL a vistas
Django permite crear URL que establezcan conexiΓ³n con vistas mediante un mΓ³dulo de Python llamado URLconf
. Este mΓ³dulo asigna expresiones de rutas de URL a funciones de Python (sus vistas). Normalmente, se genera de forma automΓ‘tica un archivo de configuraciΓ³n de URL cuando se crea un proyecto. Al completar este paso, actualizarΓ‘ este archivo a fin de incluir nuevas rutas para las vistas que creΓ³ en el paso anterior, junto con las URL para la aplicaciΓ³n django-webpush
; esta proporcionarΓ‘ extremos para suscribir usuarios a notificaciones push.
Para obtener mΓ‘s informaciΓ³n sobre vistas, consulte CΓ³mo crear vistas de Django.
Abra urls.py
:
- nano ~/djangopush/djangopush/urls.py
El archivo tendrΓ‘ este aspecto:
~/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),
]
El siguiente paso es asignar a URL las vistas que creΓ³. Primero, agregue la importaciΓ³n include
a fin de garantizar que todas las rutas para la biblioteca de Django-Webpush se aΓ±adan a su proyecto:
~/djangopush/djangopush/urls.py
"""webpushdjango URL Configuration
...
"""
from django.contrib import admin
from django.urls import path, include
A continuaciΓ³n, importe las vistas que creΓ³ en el ΓΊltimo paso y actualice la lista de urlpatterns
para asignar sus vistas:
~/djangopush/djangopush/urls.py
"""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')),
]
AquΓ, la lista de urlpatterns
registra las URL para el paquete django-webpush
y asigna sus vistas a las URL /send_push
y /home
.
Realicemos una prueba de la vista de /home
para asegurarnos de que funcione como se pretende. AsegΓΊrese de estar posicionado en el directorio root del proyecto:
Inicie su servidor ejecutando el siguiente comando:
- python manage.py runserver your_server_ip:8000
DirΓjase a http://your_server_ip:8000
. DeberΓa ver la siguiente pΓ‘gina de inicio:

En este punto, puede detener el servidor con CTRL+C
. A continuaciΓ³n, procederemos a crear plantillas y a suministrarlas en nuestras vistas usando la funciΓ³n render
.
Paso 4: Crear plantillas
El motor de plantillas de Django le permite definir las capas de su aplicaciΓ³n orientadas al usuario con plantillas similares a archivos HTML. En este paso, crearΓ‘ y representarΓ‘ una plantilla para la vista home
.
Cree una carpeta llamada templates
en el directorio root de su proyecto:
- mkdir ~/djangopush/templates
Si ejecuta ls
en la carpeta root de su proyecto en este punto, el resultado tendrΓ‘ este aspecto:
Output
/djangopush
/templates
db.sqlite3
manage.py
/my_env
Cree un archivo llamado home.html
en la carpeta templates
:
- nano ~/djangopush/templates/home.html
AΓ±ada el siguiente cΓ³digo al archivo para crear un formulario en el que los usuarios puedan introducir informaciΓ³n y crear notificaciones 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>
El body
del archivo incluye un formulario con dos campos: un elemento input
contendrΓ‘ el encabezado o tΓtulo de la notificaciΓ³n y un elemento textarea
contendrΓ‘ el cuerpo de la notificaciΓ³n.
En la secciΓ³n head
del archivo, existen dos etiquetas meta
que almacenarΓ‘n la clave pΓΊblica de VAPID y la identificaciΓ³n del usuario. Estas dos variables son necesarias para registrar un usuario y enviarle notificaciones push. Se requiere aquΓ la identificaciΓ³n del usuario, ya que enviarΓ‘ solicitudes AJAX al servidor y el id
se usarΓ‘ para identificar el usuario. Si el usuario actual es un usuario registrado, la plantilla crearΓ‘ una etiqueta meta
con su id
como contenido.
El siguiente paso es indicar a Django dΓ³nde encontrar sus plantillas. Para realizar esto, editarΓ‘ settings.py
y actualizarΓ‘ la lista TEMPLATES
.
Abra el archivo settings.py
:
- nano ~/djangopush/djangopush/settings.py
AΓ±ada lo siguiente a la lista DIRS
para especificar la ruta al directorio de plantillas:
~/djangopush/djangopush/settings.py
...
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
...
],
},
},
]
...
A continuaciΓ³n, en su archivo views.py
, actualice la vista de home
para representar la plantilla de home.html
. Abra el archivo:
- nano ~/djangpush/djangopush/views.py
Primero agregue algunas importaciones, incluida la configuraciΓ³n de settings
, que contiene todas las configuraciones del proyecto del archivo settings.py
, y la funciΓ³n render
de django.shortcuts
:
~/djangopush/djangopush/views.py
...
from django.shortcuts import render, get_object_or_404
...
import json
from django.conf import settings
...
A continuaciΓ³n, elimine el cΓ³digo inicial que agregΓ³ a la vista de home
y agregue lo siguiente, lo cual especifica cΓ³mo se representarΓ‘ la plantilla que acaba de crear:
~/djangopush/djangopush/views.py
...
@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})
El cΓ³digo asigna las siguientes variables:
webpush_settings
: se asigna el valor del atributo de WEBPUSH_SETTINGS
desde la configuraciΓ³n de settings
.vapid_key
: obtiene el valor de VAPID_PUBLIC_KEY
del objeto webpush_settings
para enviarlo al cliente. Esta clave pΓΊblica se verifica con la clave privada a fin de de garantizar que el cliente que dispone de la clave pΓΊblica tenga permiso para recibir mensajes push del servidor.user
: esta variable proviene de la solicitud entrante. Cuando un usuario realiza una solicitud al servidor, los detalles para ese usuario se almacenan en el campo user
.
La funciΓ³n render
proporcionarΓ‘ un archivo HTML y un objeto de contexto que contiene el usuario actual y la clave pΓΊblica de vapid del servidor. AquΓ se utilizan tres parΓ‘metros: la request
, la template
que se representarΓ‘ y el objeto que contiene las variables que se utilizarΓ‘n en la plantilla.
Una vez que creemos nuestra plantilla y actualicemos la vista de home
, podremos configurar Django para proporcionar nuestros archivos estΓ‘ticos.
Paso 5: Proporcionar archivos estΓ‘ticos
Las aplicaciones web incluyen CSS, JavaScript y otros archivos de imagen que en Django se denominan βarchivos estΓ‘ticosβ. Django le permite recopilar todos los archivos estΓ‘ticos de cada aplicaciΓ³n en su proyecto en una sola ubicaciΓ³n desde la que se proporcionan. Esta soluciΓ³n se llama django.contrib.staticfiles
. En este paso, actualizaremos nuestra configuraciΓ³n para indicar a Django dΓ³nde almacenar nuestros archivos estΓ‘ticos.
Abra settings.py
:
- nano ~/djangopush/djangopush/settings.py
En settings.py
, primero asegΓΊrese de que se haya definido STATIC_URL
:
~/djangopush/djangopush/settings.py
...
STATIC_URL = '/static/'
A continuaciΓ³n, agregue una lista de directorios llamada STATICFILES_DIRS
donde Django buscarΓ‘ archivos estΓ‘ticos:
~/djangopush/djangopush/settings.py
...
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
Ahora podrΓ‘ aΓ±adir STATIC_URL
a la lista de las rutas definidas en su archivo urls.py
.
Abra el archivo:
- nano ~/djangopush/djangopush/urls.py
AΓ±ada el siguiente cΓ³digo, que importarΓ‘ la configuraciΓ³n de la url static
y actualizarΓ‘ la lista de urlpatterns
. La funciΓ³n auxiliar aquΓ utiliza las propiedades de STATIC_URL
y STATIC_ROOT
que aportamos en el archivo settings.py
para proporcionar los archivos estΓ‘ticos del proyecto:
~/djangopush/djangopush/urls.py
...
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
...
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
Una vez configurados los ajustes de nuestros archivos estΓ‘ticos, podremos aplicar retoques de estilo a la pΓ‘gina de inicio de la aplicaciΓ³n.
Paso 6: Aplicar retoques de estilo a la pΓ‘gina de inicio
DespuΓ©s de configurar su aplicaciΓ³n para presentar los archivos estΓ‘ticos, puede crear una hoja de estilo externa y enlazarla al archivo home.html
para aplicar ajustes de estilo a la pΓ‘gina de inicio. Todos sus archivos estΓ‘ticos se almacenarΓ‘n en un directorio static de
la carpeta root de su proyecto.
Cree una carpeta static
y una carpeta css
dentro de la carpeta static
:
- mkdir -p ~/djangopush/static/css
Abra un archivo css llamado styles.css
dentro de la carpeta css
:
- nano ~/djangopush/static/css/styles.css
AΓ±ada los siguientes estilos para la pΓ‘gina de inicio:
~/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;
}
Una vez creada la hoja de estilo, podrΓ‘ enlazarla al archivo home.html
usando etiquetas de plantillas estΓ‘ticas. Abra el archivo home.html
:
- nano ~/djangopush/templates/home.html
Actualice la secciΓ³n head
para incluir un enlace a la hoja de estilo externa:
~/djangopush/templates/home.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
...
<link href="https://www.digitalocean.com/{% static"/css/styles.css' %}" rel="stylesheet">
</head>
<body>
...
</body>
</html>
AsegΓΊrese de posicionarse en el directorio principal de su proyecto y vuelva a iniciar su servidor para inspeccionar su trabajo:
- cd ~/djangopush
- python manage.py runserver your_server_ip:8000
Cuando visite http://your_server_ip:8000
, deberΓ‘ tener el siguiente aspecto:
Una vez mΓ‘s, podrΓ‘ detener el servidor con CTRL+C
.
Ahora que creΓ³ la pΓ‘gina home.html
y le aplicΓ³ ajustes de estilo con Γ©xito, puede suscribir usuarios para recibir notificaciones push cuando visiten la pΓ‘gina de inicio.
Paso 7: Registrar un trabajo de servicio y suscribir usuarios para recibir notificaciones push
Las notificaciones push web pueden dar aviso a los usuarios cuando existen actualizaciones de las aplicaciones a las que estΓ‘n suscritos o solicitarles que se vuelvan a conectar con las aplicaciones que han utilizaron en el pasado. Se basan en dos tecnologΓas: la API push y la API de notificaciones. Ambas tecnologΓas dependen de la presencia de un trabajo de servicio.
Una notificaciΓ³n push se invoca cuando el servidor proporciona informaciΓ³n al trabajo de servicio y este ΓΊltimo utiliza la API de notificaciones para mostrar esta informaciΓ³n.
Suscribiremos a nuestros usuarios a las notificaciones push y luego enviaremos la informaciΓ³n de la suscripciΓ³n al servidor para registrarlos.
En el directorio static
, cree una carpeta llamada js
:
- mkdir ~/djangopush/static/js
Cree un archivo llamado registerSw.js
:
- nano ~/djangopush/static/js/registerSw.js
AΓ±ada el siguiente cΓ³digo, que comprueba si los trabajados de servicio son compatibles con el navegador del usuario antes de intentar registrar un trabajo de servicio:
~/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 βΉοΈπ’")
}
};
Primero, la funciΓ³n registerSw
comprueba si el navegador es compatible con los trabajados de servicio antes de registrarlos. DespuΓ©s del registro, llama a la funciΓ³n initializeState
con los datos de registro. Si los trabajados de servicio no son compatibles con el navegador, llama a la funciΓ³n showNotAllowed
.
A continuaciΓ³n, agregue el siguiente cΓ³digo debajo de la funciΓ³n registerSw
a fin de comprobar si un usuario reΓΊne las condiciones para recibir notificaciones push antes de intentar suscribirlos:
~/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');
};
La funciΓ³n initializeState
comprueba lo siguiente:
- Si el usuario habilitΓ³ o no las notificaciones, usando el valor de
reg.showNotification
. - Si el usuario concediΓ³ permiso o no a la aplicaciΓ³n para mostrar notificaciones.
- Si el navegador es compatible o no con la API
PushManager
. Si alguna de estas comprobaciones falla, se llama a la funciΓ³n showNotAllowed
y se cancela la suscripciΓ³n.
La funciΓ³n showNotAllowed
muestra un mensaje en el botΓ³n y lo deshabilita si un usuario no reΓΊne las condiciones para recibir notificaciones. TambiΓ©n muestra mensajes correspondientes si un usuario restringiΓ³ la aplicaciΓ³n para no mostrar notificaciones o si el navegador no admite notificaciones push.
Una vez que nos aseguremos de que el usuario reΓΊna las condiciones para recibir notificaciones push, el siguiente paso es suscribirlo usando pushManager
. AΓ±ada el siguiente cΓ³digo debajo de la funciΓ³n 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)
};
Al llamar a la funciΓ³n pushManager.getSubscription
, se muestran los datos de una suscripciΓ³n activa. Cuando existe una suscripciΓ³n activa, se llama a la funciΓ³n sendSubData
con la informaciΓ³n de suscripciΓ³n transmitida como un parΓ‘metro.
Cuando no existe una suscripciΓ³n activa, la clave pΓΊblica de VAPID, la cual cuenta con codificaciΓ³n segura de URL Base64, se convierte a un Uint8Array mediante la funciΓ³n urlB64ToUint8Array
. Luego se a llama pushManager.subscribe
con la clave pΓΊblica de VAPID y el valor de userVisible
como opciones. Puede obtener mΓ‘s informaciΓ³n sobre las opciones disponibles aquΓ.
DespuΓ©s de suscribir con Γ©xito a un usuario, el siguiente paso es enviar los datos de la suscripciΓ³n al servidor. Los datos se enviarΓ‘n al extremo webpush/save_information
proporcionado por el paquete de django-webpush
. AΓ±ada el siguiente cΓ³digo debajo de la funciΓ³n 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();
El extremo save_information
requiere informaciΓ³n sobre el estado de la suscripciΓ³n (subscribe
y unsubscribe
), los datos de suscripciΓ³n y el navegador. Por ΓΊltimo, llamaremos a la funciΓ³n registerSw()
para iniciar el proceso de suscripciΓ³n del usuario.
El archivo completo tiene el siguiente aspecto:
~/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();
A continuaciΓ³n, agregue una etiqueta script
para el archivo registerSw.js
en home.html
. Abra el archivo:
- nano ~/djangopush/templates/home.html
AΓ±ada la etiqueta script
antes de la etiqueta de cierre del elemento body
:
~/djangopush/templates/home.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
...
<script src="https://www.digitalocean.com/{% static"/js/registerSw.js' %}"></script>
</body>
</html>
Debido a que aΓΊn no existe un trabajo de servicio, si dejΓ³ su aplicaciΓ³n en ejecuciΓ³n o intentΓ³ iniciarla obtendrΓ‘ un mensaje de error. Corregiremos esto creando un trabajo de servicio.
Paso 8: Crear un trabajo de servicio
Para mostrar una notificaciΓ³n push, necesitarΓ‘ un trabajo de servicio activo instalado en la pΓ‘gina de inicio de su aplicaciΓ³n. Crearemos un trabajo de servicio que escuche eventos push
y muestre los mensajes cuando estΓ© listo.
Debido a que queremos que el alcance del trabajador de servicio comprenda el dominio completo, debemos instalarlo en el directorio root de la aplicaciΓ³n. Puede obtener mΓ‘s informaciΓ³n mΓ‘s sobre el proceso en este artΓculo acerca de cΓ³mo registrar un trabajo de servicio. Nuestro enfoque consistirΓ‘ en crear un archivo sw.js
en la carpeta templates
, que luego registraremos como una vista.
Cree el archivo:
- nano ~/djangopush/templates/sw.js
AΓ±ada el siguiente cΓ³digo, que indica al trabajador de servicio que debe escuchar eventos push:
~/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'
})
);
});
El trabajo de servicio aguarda un evento push. En la funciΓ³n de devoluciΓ³n de llamada, los datos de event
se convierten a texto. Utilizamos las cadenas title
y body
predeterminadas si no se encuentran en los datos de event. La funciΓ³n showNotification
toma el tΓtulo de la notificaciΓ³n, el encabezado de la notificaciΓ³n que se mostrarΓ‘ y un objeto options como parΓ‘metros. El objeto de options contiene varias propiedades para configurar las opciones visuales de una notificaciΓ³n.
Para que su trabajo de servicio funcione en la totalidad de su dominio, deberΓ‘ instalarlo en el directorio root de la aplicaciΓ³n. Usaremos TemplateView
para permitir que el trabajo de servicio tenga acceso a todo el dominio.
Abra el archivo urls.py
:
- nano ~/djangopush/djangopush/urls.py
AΓ±ada una nueva instrucciΓ³n de import y una ruta en la lista de urlpatterns
para crear una vista basada en clases:
~/djangopush/djangopush/urls.py
...
from django.views.generic import TemplateView
urlpatterns = [
...,
path('sw.js', TemplateView.as_view(template_name='sw.js', content_type='application/x-javascript'))
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
Las vistas basadas en clases como TemplateView
permiten crear vistas flexibles y reutilizables. En este caso, el mΓ©todo TemplateView.as_view
crea una ruta para el trabajo de servicio al pasar el trabajo de servicio reciΓ©n creado como una plantilla y application/x-javascript
como el content_type
de la plantilla.
Con esto, habrΓ‘ creado un trabajo de servicio y lo habrΓ‘ registrado como una ruta. A continuaciΓ³n, configurarΓ‘ el formulario de la pΓ‘gina de inicio para enviar notificaciones push.
Paso 9: Instalar notificaciones push
Con el formulario de la pΓ‘gina de inicio, los usuarios deben poder enviar notificaciones push mientras su servidor estΓ‘ en ejecuciΓ³n. TambiΓ©n puede enviar notificaciones push usando cualquier servicio de RESTful como Postman. Cuando el usuario envΓe las notificaciones push desde el formulario en la pΓ‘gina de inicio, los datos incluirΓ‘n un head
y un body
, asΓ como el id
del usuario receptor. Los datos deben estructurarse de la siguiente manera:
{
head: "Title of the notification",
body: "Notification body",
id: "User's id"
}
Para escuchar el evento submit
del formulario y enviar los datos ingresados por el usuario al servidor, crearemos un archivo llamado site.js
en el directorio ~/djangopush/static/js
.
Abra el archivo:
- nano ~/djangopush/static/js/site.js
Primero, agregue una escucha de eventos submit
al formulario que le permitirΓ‘ obtener los valores de las entradas del formulario y el id del usuario almacenado en la etiqueta meta
de su plantilla:
~/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
});
La funciΓ³n pushForm
obtiene input
, textarea
y button
dentro del formulario. TambiΓ©n obtiene la informaciΓ³n de la etiqueta meta
, incluido el atributo de nombre user_id
y el id de usuario almacenado en el atributo content
de la etiqueta. Con esta informaciΓ³n, puede enviar una solicitud POST al extremo de /send_push
en el servidor.
Para enviar las solicitudes al servidor, usaremos la API nativa Fetch. Usaremos Fetch aquΓ, ya que es compatible con la mayorΓa de los navegadores y no necesita bibliotecas externas para funcionar. Debajo del cΓ³digo que agregΓ³, actualice la funciΓ³n pushForm
para incluir el cΓ³digo que sirve para enviar solicitudes de 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;
}
});
Si estΓ‘n presentes los tres parΓ‘metros necesarios head
, body
e id
, se envΓa la solicitud y se deshabilita temporalmente el botΓ³n de enviar.
El archivo completo tiene el siguiente aspecto:
~/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;
}
});
Por ΓΊltimo, agregue el archivo site.js
a home.html
:
- nano ~/djangopush/templates/home.html
AΓ±ada la etiqueta script
:
~/djangopush/templates/home.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
...
<script src="https://www.digitalocean.com/{% static"/js/site.js' %}"></script>
</body>
</html>
En este punto, si dejΓ³ su aplicaciΓ³n en ejecuciΓ³n o intentΓ³ iniciarla, verΓ‘ un error, ya que los trabajos de servicio solo pueden funcionar en dominios seguros o en localhost
. En el siguiente paso, usaremos ngrok para crear un tΓΊnel seguro hacia nuestro servidor web.
Paso 10: Crear un tΓΊnel seguro para probar la aplicaciΓ³n
Los trabajadores de servicio requieren conexiones seguras para funcionar en cualquier sitio, a excepciΓ³n de localhost,
ya que pueden permitir la infiltraciΓ³n maliciosa en las conexiones y la filtraciΓ³n y generaciΓ³n de respuestas. Por este motivo, crearemos un tΓΊnel seguro para nuestro servidor con ngrok.
Abra una segunda ventana de terminal y asegΓΊrese de estar en su directorio de inicio:
Si comenzΓ³ con un servidor 18.04 limpio en los requisitos previos, entonces tendrΓ‘ que instalar unzip
:
- sudo apt update && sudo apt install unzip
Descargue ngrok:
- wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
- unzip ngrok-stable-linux-amd64.zip
Mueva ngrok
a /usr/local/bin
, para tener acceso al comando ngrok
desde el terminal:
- sudo mv ngrok /usr/local/bin
En la primera ventana de su terminal, asegΓΊrese de estar posicionado en el directorio de su proyecto e inicie su servidor:
- cd ~/djangopush
- python manage.py runserver your_server_ip:8000
DeberΓ‘ hacerlo antes de crear un tΓΊnel seguro para su aplicaciΓ³n.
En la segunda ventana de su terminal, dirΓjase a la carpeta de su proyecto y active su entorno virtual:
- cd ~/djangopush
- source my_env/bin/activate
Cree el tΓΊnel seguro a su aplicaciΓ³n:
- ngrok http your_server_ip:8000
VisualizarΓ‘ el siguiente resultado, que incluye informaciΓ³n sobre su URL de ngrok segura:
Output
ngrok 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://ngrok_secure_url -> 203.0.113.0:8000
Forwarding https://ngrok_secure_url -> 203.0.113.0:8000
Connections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00
Copie ngrok_secure_url
del resultado de la consola. NecesitarΓ‘ aΓ±adirlo a la lista de ALLOWED_HOSTS
en su archivo settings.py
.
Abra otra ventana de terminal, dirΓjase a la carpeta de su proyecto y active su entorno virtual:
- cd ~/djangopush
- source my_env/bin/activate
Abra el archivo settings.py
:
- nano ~/djangopush/djangopush/settings.py
Actualice la lista de ALLOWED_HOSTS
con el tΓΊnel seguro de ngrok:
~/djangopush/djangopush/settings.py
...
ALLOWED_HOSTS = ['your_server_ip', 'ngrok_secure_url']
...
DirΓjase a la pΓ‘gina de administraciΓ³n segura para iniciar sesiΓ³n: https://ngrok_secure_url/admin/
. VerΓ‘ una pantalla similar a esta:

Introduzca la informaciΓ³n de su usuario administrador de Django en esta pantalla. Esta deberΓ‘ ser la misma informaciΓ³n que ingresΓ³ cuando iniciΓ³ sesiΓ³n en la interfaz de administrador en los pasos de los requisitos previos. Con esto, estarΓ‘ listo para enviar notificaciones push.
Visite https://ngrok_secure_url
en su navegador. VisualizarΓ‘ un mensaje en el que se solicitarΓ‘ permiso para mostrar notificaciones. Haga clic en el botΓ³n Allow para permitir que su navegador muestre notificaciones push:

Con el envΓo de un formulario completo se mostrarΓ‘ una notificaciΓ³n similar a la siguiente:

Nota: AsegΓΊrese de que su servidor estΓ© activo antes de intentar enviar notificaciones.
Si recibiΓ³ notificaciones, significa que su aplicaciΓ³n funciona segΓΊn lo previsto.
Pudo crear una aplicaciΓ³n web que activa notificaciones push en el servidor y, con la ayuda de los trabajados de servicio, recibe y muestra notificaciones. TambiΓ©n completΓ³ los pasos para obtener las claves de VAPID que se necesitan para enviar notificaciones push desde un servidor de aplicaciones.
ConclusiΓ³n
A travΓ©s de este tutorial, aprendiΓ³ a suscribir usuarios a notificaciones push,instalar trabajados de servicio y mostrar notificaciones push mediante la API de notificaciones.
Puede dar un paso mΓ‘s configurando las notificaciones para que abran Γ‘reas especΓficas de su aplicaciΓ³n cuando se haga clic en ellas. Puede encontrar el cΓ³digo fuente para este tutorial aquΓ.