One place for hosting & domains

      How to Push an Existing Project to GitHub


      While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or
      edited it to ensure you have an error-free learning experience. It’s on our list, and we’re working on it!
      You can help us out by using the “report an issue” button at the bottom of the tutorial.

      GitHub is simply a cloud-hosted Git management tool. Git is distributed version control, meaning the entire repo and history lives wherever you put it. People tend use GitHub though in their business or development workflow as a managed hosting solution for backups of their repositories.

      It’s a convenient and mostly worry-free method for backing up all your code repos. It also allows you to very nicely navigate and view your code on the web. GitHub takes this even further by letting you connect with coworkers, friends, organizations, and more.

      Prerequisites:

      To initialize the repo and push it to GitHub you’ll need:

      1. A free GitHub Account
      2. git installed on your local machine

      Step 1: Create a new GitHub Repo

      Sign in to GitHub and create a new empty repo page. You can choose to either initialize a README or not. It doesn’t really matter because we’re just going to override everything in this remote repository anyways.

      Create new GitHub Repo

      Through the rest of this tutorial we’ll assume your GitHub username is sammy and the repo you created is named my-new-project (So you’ll need to swap those out with your actual username and repo name when copy/pasting commands)

      Step 2: Initialize Git in the project folder

      From your terminal, run the following commands after navigating to folder you would like to add:

      Initialize the Git Repo

      Make sure you are in the root directory of the project you want to push to GitHub and run:

      This step creates a hidden .git directory in your project folder which the git software recognizes and uses to store all the metadata and version history for the project.

      Add the files to Git index

      The git add command is used to tell git which files to include in a commit, and the -A argument means β€œinclude all”.

      Commit Added Files

      • git commit -m 'Added my project'

      The git commit command creates a new commit with all files that have been β€œadded”. the -m 'Added my project' is the message that will be included alongside the commit, used for future reference to understand the commit.

      Add new remote origin (in this case, GitHub)

      Note: Don’t forget to replace the highlighted bits above with your username and repo name.

      In git, a β€œremote” refers to a remote version of the same repository, which is typically on a server somewhere (in this case GitHub.) β€œorigin” is the default name git gives to a remote server (you can have multiple remotes) so git remote add origin is instructing git to add the URL of the default remote server for this repo.

      Push to GitHub

      • git push -u -f origin master

      With this, there are a few things to note. The -f flag stands for force. This will automatically overwrite everything in the remote directory. We’re only using it here to overwrite the README that GitHub automatically initialized. If you skipped that, the -f flag isn’t really necessary.

      The -u flag sets the remote origin as the default. This lets you later easily just do git push and git pull without having to specifying an origin since we always want GitHub in this case.

      All together

      • git init
      • git add -A
      • git commit -m 'Added my project'
      • git remote add origin [email protected]:sammy/my-new-project.git
      • git push -u -f origin master

      Conclusion

      Now you are all set to track your code changes remotely in GitHub! As a next step here’s a complete guide to how to use git

      Once you start collaborating with others on the project, you’ll want to know how to create a pull request.



      Source link

      CΓ³mo enviar notificaciones push web desde aplicaciones de Django


      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:

      Push web final

      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_publickey, `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:

      Vista inicial de la página principal

      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:

      Vista de la página de inicio 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:

      Inicio de sesión de administrador de ngrok

      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:

      Solicitud de notificaciones push

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

      Captura de pantalla de la notificación

      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Γ­.



      Source link

      How To Send Web Push Notifications from Django Applications


      The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      The web is constantly evolving, and it can now achieve functionalities that were formerly only available on native mobile devices. The introduction of JavaScript service workers gave the web newfound abilities to do things like background syncing, offline caching, and sending push notifications.

      Push notifications allow users to opt-in to receive updates to mobile and web applications. They also enable users to re-engage with existing applications using customized and relevant content.

      In this tutorial, you’ll set up a Django application on Ubuntu 18.04 that sends push notifications whenever there’s an activity that requires the user to visit the application. To create these notifications, you will use the Django-Webpush package and set up and register a service worker to display notifications to the client. The working application with notifications will look like this:

      Web push final

      Prerequisites

      Before you begin this guide you’ll need the following:

      Step 1 β€” Installing Django-Webpush and Getting Vapid Keys

      Django-Webpush is a package that enables developers to integrate and send web push notifications in Django applications. We’ll use this package to trigger and send push notifications from our application. In this step, you will install Django-Webpush and obtain the Voluntary Application Server Identification (VAPID) keys that are necessary for identifying your server and ensuring the uniqueness of each request.

      Make sure you are in the ~/djangopush project directory that you created in the prerequisites:

      Activate your virtual environment:

      • source my_env/bin/activate

      Upgrade your version of pip to ensure it's up-to-date:

      • pip install --upgrade pip

      Install Django-Webpush:

      • pip install django-webpush

      After installing the package, add it to the list of applications in your settings.py file. First open settings.py:

      • nano ~/djangopush/djangopush/settings.py

      Add webpush to the list of INSTALLED_APPS:

      ~/djangopush/djangopush/settings.py

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

      Save the file and exit your editor.

      Run migrations on the application to apply the changes you've made to your database schema:

      The output will look like this, indicating a successful migration:

      Output

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

      The next step in setting up web push notifications is getting VAPID keys. These keys identify the application server and can be used to reduce the secrecy for push subscription URLs, since they restrict subscriptions to a specific server.

      To obtain VAPID keys, navigate to the wep-push-codelab web application. Here, you'll be given automatically generated keys. Copy the private and public keys.

      Next, create a new entry in settings.py for your VAPID information. First, open the file:

      • nano ~/djangopush/djangopush/settings.py

      Next, add a new directive called WEBPUSH_SETTINGS with your VAPID public and private keys and your email below 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/
      
      ...
      

      Don't forget to replace the placeholder values your_vapid_public_key, your_vapid_private_key, and [email protected] with your own information. Your email address is how you will be notified if the push server experiences any issues.

      Next, we'll set up views that will display the application's home page and trigger push notifications to subscribed users.

      Step 2 β€” Setting Up Views

      In this step, we'll setup a basic home view with the HttpResponse response object for our home page, along with a send_push view. Views are functions that return response objects from web requests. The send_push view will use the Django-Webpush library to send push notifications that contain the data entered by a user on the home page.

      Navigate to the ~/djangopush/djangopush folder:

      • cd ~/djangopush/djangopush

      Running ls inside the folder will show you the project's main files:

      Output

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

      The files in this folder are auto-generated by the django-admin utility that you used to create your project in the prerequisites. The settings.py file contains project-wide configurations like installed applications and the static root folder. The urls.py file contains the URL configurations for the project. This is where you will set up routes to match your created views.

      Create a new file inside the ~/djangopush/djangopush directory called views.py, which will contain the views for your project:

      • nano ~/djangopush/djangopush/views.py

      The first view we'll make is the home view, which will display the home page where users can send push notifications. Add the following code to the file:

      ~/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>')
      

      The home view is decorated by the require_GET decorator, which restricts the view to GET requests only. A view typically returns a response for every request made to it. This view returns a simple HTML tag as a response.

      The next view we'll create is send_push, which will handle sent push notifications using the django-webpush package. It will be restricted to POST requests only and will be exempted from Cross Site Request Forgery (CSRF) protection. Doing this will allow you to test the view using Postman or any other RESTful service. In production, however, you should remove this decorator to avoid leaving your views vulnerable to CSRF.

      To create the send_push view, first add the following imports to enable JSON responses and access the send_user_notification function in the webpush library:

      ~/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
      

      Next, add the require_POST decorator, which will use the request body sent by the user to create and trigger a push notification:

      ~/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"})
      

      We are using two decorators for the send_push view: the require_POST decorator, which restricts the view to POST requests only, and the csrf_exempt decorator, which exempts the view from CSRF protection.

      This view expects POST data and does the following: it gets the body of the request and, using the json package, deserializes the JSON document to a Python object using json.loads. json.loads takes a structured JSON document and converts it to a Python object.

      The view expects the request body object to have three properties:

      • head: The title of the push notification.
      • body: The body of the notification.
      • id: The id of the request user.

      If any of the required properties are missing, the view will return a JSONResponse with a 404 "Not Found" status. If the user with the given primary key exists, the view will return the user with the matching primary key using the get_object_or_404 function from the django.shortcuts library. If the user doesn't exist, the function will return a 404 error.

      The view also makes use of the send_user_notification function from the webpush library. This function takes three parameters:

      • User: The recipient of the push notification.
      • payload: The notification information, which includes the notification head and body.
      • ttl: The maximum time in seconds that the notification should be stored if the user is offline.

      If no errors occur, the view returns a JSONResponse with a 200 "Success" status and a data object. If a KeyError occurs, the view will return a 500 "Internal Server Error" status. A KeyError occurs when the requested key of an object doesn't exist.

      In the next step, we'll create corresponding URL routes to match the views we've created.

      Step 3 β€” Mapping URLs to Views

      Django makes it possible to create URLs that connect to views with a Python module called a URLconf. This module maps URL path expressions to Python functions (your views). Usually, a URL configuration file is auto-generated when you create a project. In this step, you will update this file to include new routes for the views you created in the previous step, along with the URLs for the django-webpush app, which will provide endpoints to subscribe users to push notifications.

      For more information about views, please see How To Create Django Views.

      Open urls.py:

      • nano ~/djangopush/djangopush/urls.py

      The file will look like this:

      ~/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),
      ]
      

      The next step is to map the views you've created to URLs. First, add the include import to ensure that all of the routes for the Django-Webpush library will be added to your project:

      ~/djangopush/djangopush/urls.py

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

      Next, import the views you created in the last step and update the urlpatterns list to map your views:

      ~/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')),
                    ]
      

      Here, the urlpatterns list registers the URLs for the django-webpush package and maps your views to the URLs /send_push and /home.

      Let's test the /home view to be sure that it's working as intended. Make sure you're in the root directory of the project:

      Start your server by running the following command:

      • python manage.py runserver your_server_ip:8000

      Navigate to http://your_server_ip:8000. You should see the following home page:

      Initial Home Page view

      At this point, you can kill the server with CTRL+C, and we will move on to creating templates and rendering them in our views using the render function.

      Step 4 β€” Creating Templates

      Django’s template engine allows you to define the user-facing layers of your application with templates, which are similar to HTML files. In this step, you will create and render a template for the home view.

      Create a folder called templates in your project's root directory:

      • mkdir ~/djangopush/templates

      If you run ls in the root folder of your project at this point, the output will look like this:

      Output

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

      Create a file called home.html in the templates folder:

      • nano ~/djangopush/templates/home.html

      Add the following code to the file to create a form where users can enter information to create push notifications:

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

      The body of the file includes a form with two fields: an input element will hold the head/title of the notification and a textarea element will hold the notification body.

      In the head section of the file, there are two meta tags that will hold the VAPID public key and the user's id. These two variables are required to register a user and send them push notifications. The user's id is required here because you'll be sending AJAX requests to the server and the id will be used to identify the user. If the current user is a registered user, then the template will create a meta tag with their id as the content.

      The next step is to tell Django where to find your templates. To do this, you will edit settings.py and update the TEMPLATES list.

      Open the settings.py file:

      • nano ~/djangopush/djangopush/settings.py

      Add the following to the DIRS list to specify the path to the templates directory:

      ~/djangopush/djangopush/settings.py

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

      Next, in your views.py file, update the home view to render the home.html template. Open the file:

      • nano ~/djangpush/djangopush/views.py

      First, add some additional imports, including the settings configuration, which contains all of the project's settings from the settings.py file, and the render function from django.shortcuts:

      ~/djangopush/djangopush/views.py

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

      Next, remove the initial code you added to the home view and add the following, which specifies how the template you just created will be rendered:

      ~/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})
      

      The code assigns the following variables:

      • webpush_settings: This is assigned the value of the WEBPUSH_SETTINGS attribute from the settings configuration.
      • vapid_key: This gets the VAPID_PUBLIC_KEY value from the webpush_settings object to send to the client. This public key is checked against the private key to ensure that the client with the public key is permitted to receive push messages from the server.
      • user: This variable comes from the incoming request. Whenever a user makes a request to the server, the details for that user are stored in the user field.

      The render function will return an HTML file and a context object containing the current user and the server's vapid public key. It takes three parameters here: the request, the template to be rendered, and the object that contains the variables that will be used in the template.

      With our template created and the home view updated, we can move on to configuring Django to serve our static files.

      Step 5 β€” Serving Static Files

      Web applications include CSS, JavaScript, and other image files that Django refers to as β€œstatic files”. Django allows you to collect all of the static files from each application in your project into a single location from which they are served. This solution is called django.contrib.staticfiles. In this step, we'll update our settings to tell Django where our static files will be stored.

      Open settings.py:

      • nano ~/djangopush/djangopush/settings.py

      In settings.py, first ensure that the STATIC_URL has been defined:

      ~/djangopush/djangopush/settings.py

      ...
      STATIC_URL = '/static/'
      

      Next, add a list of directories called STATICFILES_DIRS where Django will look for static files:

      ~/djangopush/djangopush/settings.py

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

      You can now add the STATIC_URL to the list of paths defined in your urls.py file.

      Open the file:

      • nano ~/djangopush/djangopush/urls.py

      Add the following code, which will import the static url configuration and update the urlpatterns list. The helper function here uses the STATIC_URL and STATIC_ROOT properties we provided in the settings.py file to serve the project's static files:

      ~/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)
      

      With our static files settings configured, we can move on to styling the application's home page.

      Step 6 β€” Styling the Home Page

      After setting up your application to serve static files, you can create an external stylesheet and link it to the home.html file to style the home page. All of your static files will be stored in a static directory in the root folder of your project.

      Create a static folder and a css folder within the static folder:

      • mkdir -p ~/djangopush/static/css

      Open a css file called styles.css inside the css folder:

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

      Add the following styles for the home page:

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

      With the stylesheet created, you can link it to the home.html file using static template tags. Open the home.html file:

      • nano ~/djangopush/templates/home.html

      Update the head section to include a link to the external stylesheet:

      ~/djangopush/templates/home.html

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

      Make sure that you are in your main project directory and start your server again to inspect your work:

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

      When you visit http://your_server_ip:8000, it should look like this:

      Home page view
      Again, you can kill the server with CTRL+C.

      Now that you have successfully created the home.html page and styled it, you can subscribe users to push notifications whenever they visit the home page.

      Step 7 β€” Registering a Service Worker and Subscribing Users to Push Notifications

      Web push notifications can notify users when there are updates to applications they are subscribed to or prompt them to re-engage with applications they have used in the past. They rely on two technologies, the push API and the notifications API. Both technologies rely on the presence of a service worker.

      A push is invoked when the server provides information to the service worker and the service worker uses the notifications API to display this information.

      We'll subscribe our users to the push and then we'll send the information from the subscription to the server to register them.

      In the static directory, create a folder called js:

      • mkdir ~/djangopush/static/js

      Create a file called registerSw.js:

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

      Add the following code, which checks if service workers are supported on the user's browser before attempting to register a 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 ☹️😒")
          }
      };
      

      First, the registerSw function checks if the browser supports service workers before registering them. After registration, it calls the initializeState function with the registration data. If service workers are not supported in the browser, it calls the showNotAllowed function.

      Next, add the following code below the registerSw function to check if a user is eligible to receive push notifications before attempting to subscribe them:

      ~/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');
      };
      

      The initializeState function checks the following:

      • Whether or not the user has enabled notifications, using the value of reg.showNotification.
      • Whether or not the user has granted the application permission to display notifications.
      • Whether or not the browser supports the PushManager API.
        If any of these checks fail, the showNotAllowed function is called and the subscription is aborted.

      The showNotAllowed function displays a message on the button and disables it if a user is ineligible to receive notifications. It also displays appropriate messages if a user has restricted the application from displaying notifications or if the browser doesn't support push notifications.

      Once we ensure that a user is eligible to receive push notifications, the next step is to subscribe them using pushManager. Add the following code below the showNotAllowed function:

      ~/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)
      };
      

      Calling the pushManager.getSubscription function returns the data for an active subscription. When an active subscription exists, the sendSubData function is called with the subscription info passed in as a parameter.

      When no active subscription exists, the VAPID public key, which is Base64 URL-safe encoded, is converted to a Uint8Array using the urlB64ToUint8Array function. pushManager.subscribe is then called with the VAPID public key and the userVisible value as options. You can read more about the available options here.

      After successfully subscribing a user, the next step is to send the subscription data to the server. The data will be sent to the webpush/save_information endpoint provided by the django-webpush package. Add the following code below the subscribe function:

      ~/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();
      

      The save_information endpoint requires information about the status of the subscription (subscribe and unsubscribe), the subscription data, and the browser. Finally, we call the registerSw() function to begin the process of subscribing the user.

      The completed file looks like this:

      ~/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();
      

      Next, add a script tag for the registerSw.js file in home.html. Open the file:

      • nano ~/djangopush/templates/home.html

      Add the script tag before the closing tag of the body element:

      ~/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>
      

      Because a service worker doesn't yet exist, if you left your application running or tried to start it again, you would see an error message. Let's fix this by creating a service worker.

      Step 8 β€” Creating a Service Worker

      To display a push notification, you'll need an active service worker installed on your application's home page. We'll create a service worker that listens for push events and displays the messages when ready.

      Because we want the scope of the service worker to be the entire domain, we will need to install it in the application's root. You can read more about the process in this article outlining how to register a service worker. Our approach will be to create a sw.js file in the templates folder, which we will then register as a view.

      Create the file:

      • nano ~/djangopush/templates/sw.js

      Add the following code, which tells the service worker to listen for push events:

      ~/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'
              })
          );
      });
      

      The service worker listens for a push event. In the callback function, the event data is converted to text. We use default title and body strings if the event data doesn't have them. The showNotification function takes the notification title, the header of the notification to be displayed, and an options object as parameters. The options object contains several properties to configure the visual options of a notification.

      For your service worker to work for the entirety of your domain, you will need to install it in the root of the application. We'll use TemplateView to allow the service worker access to the whole domain.

      Open the urls.py file:

      • nano ~/djangopush/djangopush/urls.py

      Add a new import statement and path in the urlpatterns list to create a class-based view:

      ~/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)
      

      Class-based views like TemplateView allow you to create flexible, reusable views. In this case, the TemplateView.as_view method creates a path for the service worker by passing the recently created service worker as a template and application/x-javascript as the content_type of the template.

      You have now created a service worker and registered it as a route. Next, you'll set up the form on the home page to send push notifications.

      Step 9 β€” Sending Push Notifications

      Using the form on the home page, users should be able to send push notifications while your server is running. You can also send push notifications using any RESTful service like Postman. When the user sends push notifications from the form on the home page, the data will include a head and body, as well as the id of the receiving user. The data should be structured in the following manner:

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

      To listen for the submit event of the form and send the data entered by the user to the server, we will create a file called site.js in the ~/djangopush/static/js directory.

      Open the file:

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

      First, add a submit event listener to the form that will enable you to get the values of the form inputs and the user id stored in the meta tag of your template:

      ~/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
      });
      

      The pushForm function gets the input, textarea, and button inside the form. It also gets the information from the meta tag, including the name attribute user_id and the user's id stored in the content attribute of the tag. With this information, it can send a POST request to the /send_push endpoint on the server.

      To send requests to the server, we'll use the native Fetch API. We're using Fetch here because it is supported by most browsers and doesn't require external libraries to function. Below the code you've added, update the pushForm function to include the code for sending AJAX requests:

      ~/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;
          }
      });
      

      If the three required parameters head, body, and id are present, we send the request and disable the submit button temporarily.

      The completed file looks like this:

      ~/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;
          }    
      });
      

      Finally, add the site.js file to home.html:

      • nano ~/djangopush/templates/home.html

      Add the script tag:

      ~/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>
      

      At this point, if you left your application running or tried to start it again, you would see an error, since service workers can only function in secure domains or on localhost. In the next step we'll use ngrok to create a secure tunnel to our web server.

      Step 10 β€” Creating a Secure Tunnel to Test the Application

      Service workers require secure connections to function on any site except localhost since they can allow connections to be hijacked and responses to be filtered and fabricated. For this reason, we'll create a secure tunnel for our server with ngrok.

      Open a second terminal window and ensure you're in your home directory:

      If you started with a clean 18.04 server in the prerequisites, then you will need to install unzip:

      • sudo apt update && sudo apt install unzip

      Download ngrok:

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

      Move ngrok to /usr/local/bin, so that you will have access to the ngrok command from the terminal:

      • sudo mv ngrok /usr/local/bin

      In your first terminal window, make sure that you are in your project directory and start your server:

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

      You will need to do this before creating a secure tunnel for your application.

      In your second terminal window, navigate to your project folder, and activate your virtual environment:

      • cd ~/djangopush
      • source my_env/bin/activate

      Create the secure tunnel to your application:

      • ngrok http your_server_ip:8000

      You will see the following output, which includes information about your secure ngrok URL:

      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

      Copy the ngrok_secure_url from the console output. You will need to add it to the list of ALLOWED_HOSTS in your settings.py file.

      Open another terminal window, navigate to your project folder, and activate your virtual environment:

      • cd ~/djangopush
      • source my_env/bin/activate

      Open the settings.py file:

      • nano ~/djangopush/djangopush/settings.py

      Update the list of ALLOWED_HOSTS with the ngrok secure tunnel:

      ~/djangopush/djangopush/settings.py

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

      Navigate to the secure admin page to log in: https://ngrok_secure_url/admin/. You will see a screen that looks like this:

      ngrok admin login

      Enter your Django admin user information on this screen. This should be the same information you entered when you logged into the admin interface in the prerequisite steps. You are now ready to send push notifications.

      Visit https://ngrok_secure_url in your browser. You will see a prompt asking for permission to display notifications. Click the Allow button to let your browser display push notifications:

      push notifications request

      Submitting a filled form will display a notification similar to this:

      screenshot of notification

      Note: Be sure that your server is running before attempting to send notifications.

      If you received notifications then your application is working as expected.

      You have created a web application that triggers push notifications on the server and, with the help of service workers, receives and displays notifications. You also went through the steps of obtaining the VAPID keys that are required to send push notifications from an application server.

      Conclusion

      In this tutorial, you've learned how to subscribe users to push notifications, install service workers, and display push notifications using the notifications API.

      You can go even further by configuring the notifications to open specific areas of your application when clicked. The source code for this tutorial can be found here.



      Source link