One place for hosting & domains

      Cómo instalar el servidor web de Apache en CentOS 7


      Introducción

      El servidor HTTP Apache es el más usado del mundo. Ofrece muchas características potentes, entre las que se incluyen módulos que se cargan de forma dinámica, una sólida compatibilidad con medios y amplia integración con otras herramientas de software populares.

      A través de esta guía, instalará un servidor web de Apache con hosts virtuales en su servidor de CentOS 7.

      Requisitos previos

      Necesitará lo siguiente para completar esta guía:

      Paso 1: Instalar Apache

      Apache está disponible dentro de los repositorios de software predeterminados de CentOS, lo cual significa que puede instalarlo con el administrador de paquetes yum.

      Como usuario sudo no root configurado en los requisitos previos, actualice el índice local de paquetes de Apache httpd para reflejar los últimos cambios:

      Una vez que se actualicen los paquetes, instale el paquete de Apache:

      Una vez confirmada la instalación, yum instalará Apache y todas las dependencias necesarias. Una vez que la instalación se complete, estará listo para iniciar el servicio.

      Paso 2: Comprobar su servidor web

      Apache no se inicia de forma automática en CentOS una vez que se completa la instalación. Deberá iniciar el proceso de Apache de forma manual:

      • sudo systemctl start httpd

      Verifique que el servicio funcione con el siguiente comando:

      • sudo systemctl status httpd

      Visualizará un estado active cuando el servicio esté en ejecución:

      Output

      Redirecting to /bin/systemctl status httpd.service ● httpd.service - The Apache HTTP Server Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled) Active: active (running) since Wed 2019-02-20 01:29:08 UTC; 5s ago Docs: man:httpd(8) man:apachectl(8) Main PID: 1290 (httpd) Status: "Processing requests..." CGroup: /system.slice/httpd.service ├─1290 /usr/sbin/httpd -DFOREGROUND ├─1291 /usr/sbin/httpd -DFOREGROUND ├─1292 /usr/sbin/httpd -DFOREGROUND ├─1293 /usr/sbin/httpd -DFOREGROUND ├─1294 /usr/sbin/httpd -DFOREGROUND └─1295 /usr/sbin/httpd -DFOREGROUND ...

      Como puede ver en este resultado, parece que el servicio se inició correctamente. Sin embargo, la mejor forma de comprobarlo es solicitar una página de Apache.

      Puede acceder a la página de destino predeterminada de Apache para confirmar que el software funcione correctamente mediante su dirección IP: Si no conoce la dirección IP de su servidor, puede obtenerla de varias formas desde la línea de comandos.

      Escriba esto en la línea de comandos de su servidor:

      Con este comando se mostrarán todas las direcciones de red del host, de modo que obtendrá algunas direcciones IP separadas por espacios. Puede probar cada una de ellas en su navegador web para ver si funcionan.

      De forma alternativa, puede utilizar curl para solicitar su IP de icanhazip.com, que le brindará su dirección IPv4 pública como se muestra desde otra ubicación en Internet:

      Cuando tenga la dirección IP de su servidor, introdúzcala en la barra de direcciones de su navegador:

      http://your_server_ip
      

      Visualizará la página web predeterminada para Apache de CentOS 7:

      Página predeterminada de Apache para CentOS 7

      Esta página indica que Apache funciona correctamente. También incluye información básica sobre archivos y ubicaciones de directorios importantes de Apache. Ahora que el servicio está instalado y en ejecución, puede utilizar diferentes comandos de systemctl para administrarlo.

      Paso 3: Administrar el proceso de Apache

      Ahora el servidor web funciona, repasemos algunos comandos de administración básicos.

      Para detener su servidor web, escriba lo siguiente:

      • sudo systemctl stop httpd

      Para iniciar el servidor web cuando se detenga, escriba lo siguiente:

      • sudo systemctl start httpd

      Para detener y luego iniciar el servicio de nuevo, escriba lo siguiente:

      • sudo systemctl restart httpd

      Si solo realiza cambios de configuración, Apache a menudo puede recargarse sin cerrar conexiones. Para hacerlo, utilice este comando:

      • sudo systemctl reload httpd

      Por defecto, Apache está configurado para iniciarse automáticamente cuando el servidor lo hace. Si no es lo que quiere, deshabilite este comportamiento escribiendo lo siguiente:

      • sudo systemctl disable httpd

      Para volver a habilitar el servicio de modo que se cargue en el inicio, escriba lo siguiente:

      • sudo systemctl enable httpd

      Ahora, Apache se iniciará de forma automática cuando el servidor se inicie de nuevo.

      La configuración predeterminada de Apache permitirá a su servidor host alojar un único sitio web. Si piensa alojar varios dominios en su servidor, deberá configurar hosts virtuales en su servidor web de Apache.

      Paso 4: Configuración de hosts virtuales (recomendado)

      Al emplear el servidor web Apache, puede utilizar hosts virtuales (similares a bloques de servidor de Nginx) para encapsular detalles de configuración y alojar más de un dominio desde un único servidor. En este paso, configurará un dominio llamado example.com, pero debería cambiarlo por su propio nombre de dominio. Consulte nuestra Introducción a DNS de DigitalOcean para hallar más información sobre la configuración de un nombre de dominio con DigitalOcean.

      Por defecto, Apache en CentOS 7 tiene habilitado un bloque de servidor que está configurado para proporcionar documentos del directorio /var/www/html. Si bien esto funciona bien para un solo sitio, puede ser difícil de manejar si aloja varios. En lugar de modificar /var/www/html, creará una estructura de directorio dentro de /var/www para el sitio example.com y dejará /var/www/html instalado como directorio predeterminado que se presentará si una solicitud de cliente no coincide con ningún otro sitio.

      Cree el directorio ​​​​​html​​​​​​ para el sitio example.com de la siguiente manera, utilizando el indicador -p para crear cualquier directorio principal necesario:

      • sudo mkdir -p /var/www/example.com/html

      Cree un directorio adicional para almacenar archivos de registro para el sitio:

      • sudo mkdir -p /var/www/example.com/log

      A continuación, asigne la propiedad del directorio html con la variable de entorno $USER:

      • sudo chown -R $USER:$USER /var/www/example.com/html

      Asegúrese de que estén configurados los permisos predeterminados de su root web:

      • sudo chmod -R 755 /var/www

      A continuación, cree una página de ejemplo index.html utilizando vi o su editor favorito:

      • sudo vi /var/www/example.com/html/index.html

      Pulse i para realizar un cambio al modo INSERT y agregar el siguiente ejemplo HTML al archivo:

      /var/www/example.com/html/index.html

      <html>
        <head>
          <title>Welcome to Example.com!</title>
        </head>
        <body>
          <h1>Success! The example.com virtual host is working!</h1>
        </body>
      </html>
      

      Guarde y cierre el archivo presionando ESC, escribiendo :wq y presionando ENTER.

      Una vez establecidos el directorio de su sitio y el archivo de índice de ejemplo, casi estará listo para crear archivos de host virtuales. Mediante los archivos de host virtuales se especifica la configuración de sus sitios individuales y se informa al servidor web de Apache la manera de responder a varias solicitudes de dominio.

      Antes de crear sus hosts virtuales, deberá crear un directorio sites-available para almacenarlos. También creará el directorio sites-enabled que indica a Apache que un host virtual está preparado para visitantes. En el directorio sites-enabled se almacenarán enlaces simbólicos a hosts virtuales que deseemos publicar. Cree ambos directorios con el siguiente comando:

      • sudo mkdir /etc/httpd/sites-available /etc/httpd/sites-enabled

      A continuación, indicará a Apache que busque hosts virtuales en el directorio sites-enabled. Para lograr esto, edite el archivo de configuración principal de Apache y añada una línea que declare un directorio opcional para archivos de configuración adicionales:

      • sudo vi /etc/httpd/conf/httpd.conf

      Añada esta línea al final del archivo:

      IncludeOptional sites-enabled/*.conf
      

      Guarde y cierre el archivo cuando termine de añadir esa línea. Ahora que están listos sus directorios de hosts virtuales, creará su archivo de host virtual.

      Comience creando un nuevo archivo en el directorio sites-available:

      • sudo vi /etc/httpd/sites-available/example.com.conf

      Agregue el siguiente bloque de configuración y cambie el dominio example.com por su nombre de dominio:

      /etc/httpd/sites-available/example.com.conf

      <VirtualHost *:80>
          ServerName www.example.com
          ServerAlias example.com
          DocumentRoot /var/www/example.com/html
          ErrorLog /var/www/example.com/log/error.log
          CustomLog /var/www/example.com/log/requests.log combined
      </VirtualHost>
      

      Esto indicará a Apache dónde encontrar directamente el root que contiene los documentos web de acceso público. También, dónde almacenar errores y solicitar registros para este sitio en particular.

      Guarde y cierre el archivo cuando haya terminado.

      Ahora que creó archivos de host virtuales, los habilitará para que Apache sepa presentarlos a los visitantes. Para lograrlo, cree un enlace simbólico para cada host virtual en el directorio sites-enabled:

      • sudo ln -s /etc/httpd/sites-available/example.com.conf /etc/httpd/sites-enabled/example.com.conf

      Su host virtual quedará, así, configurado y listo para ofrecer contenido. Antes de reiniciar el servicio de Apache, nos aseguraremos de que a través de SELinux se implementen las políticas correctas para sus hosts virtuales.

      Paso 5: Ajustar los permisos de SELinux para hosts virtuales (recomendado)

      SELinux está configurado para funcionar con la configuración predeterminada de Apache. Dado que configuró un directorio de registro personalizado en el archivo de configuración de hosts virtuales, verá un mensaje de error si intenta iniciar el servicio de Apache. Para resolver esto, deberá actualizar las políticas SELinux a fin de permitir que Apache escriba en los archivos necesarios. SELinux aporta mayor seguridad a su entorno de CentOS 7. Por lo tanto, no se recomienda desactivar por completo el módulo del kernel.

      Existen diferentes formas de configurar políticas según las necesidades de su entorno, ya que SELinux le permite personalizar su nivel de seguridad. En este paso se abarcarán dos métodos de ajuste de políticas de Apache: el universal y el que se aplica a un directorio específico. Ajustar políticas en directorios es más seguro y, por lo tanto, es el enfoque recomendado.

      Ajustar políticas de Apache de forma universal

      Establecer la política de Apache de forma universal indicará a SELinux que trate de forma idéntica todos los procesos de Apache usando el booleano httpd_unified. Si bien este enfoque es más conveniente, no le brindará el mismo nivel de control que uno centrado en una política de archivos o directorios.

      Ejecute el siguiente comando para configurar una política universal de Apache:

      • sudo setsebool -P httpd_unified 1

      El comando setsebool cambia los valores booleanos de SELinux. El marcador -P actualizará el valor de tiempo de inicio y hará que este cambio persista en los reinicios. httpd_unified es el booleano que indicará a SELinux que trate todos los procesos de Apache como el mismo tipo, por lo que lo habrá habilitado con un valor de 1.

      Ajustar políticas de Apache en un directorio

      Configurar individualmente los permisos de SELinux para el directorio /var/www/example.com/log le brindará mayor control sobre sus políticas de Apache, pero también puede requerir más mantenimiento. Debido a que esta opción no establece políticas de forma universal, deberá fijar de forma manual el tipo de contexto para cualquier nuevo directorio de registro especificado en sus configuraciones de host virtuales.

      Primero, compruebe el tipo de contexto que SELinux dio al directorio /var/www/example.com/log:

      • sudo ls -dZ /var/www/example.com/log/

      Este comando enumera e imprime el contexto de SELinux del directorio. El resultado debe ser similar a lo siguiente:

      Output

      drwxr-xr-x. root root unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/example.com/log/

      El contexto actual es httpd_sys_content_t, que indica a SELinux que el proceso de Apache solo puede leer archivos creados en este directorio. A través de este tutorial, cambiará el tipo de contexto del directorio /var/www/example.com/log a httpd_log_t. Este tipo permitirá que Apache genere archivos de registro de la aplicación web y realice anexos a ellos:

      • sudo semanage fcontext -a -t httpd_log_t "/var/www/example.com/log(/.*)?"

      A continuación, utilice el comando restorecon para aplicar estos cambios y hacer que persistan a través de los reinicios:

      • sudo restorecon -R -v /var/www/example.com/log

      El indicador -R ejecuta este comando de forma recursiva, lo que significa que actualizará cualquier archivo existente para que utilice el nuevo contexto. El indicador -v imprimirá los cambios de contexto realizados por el comando. Verá el siguiente resultado que confirma los cambios:

      Output

      restorecon reset /var/www/example.com/log context unconfined_u:object_r:httpd_sys_content_t:s0->unconfined_u:object_r:httpd_log_t:s0

      Puede enumerar los contextos una vez más para ver los cambios:

      • sudo ls -dZ /var/www/example.com/log/

      El resultado refleja el tipo de contexto actualizado:

      Output

      drwxr-xr-x. root root unconfined_u:object_r:httpd_log_t:s0 /var/www/example.com/log

      Ahora que el directorio /var/www/example.com/log usa el tipo httpd_log_t, estará listo para probar su configuración de host virtual.

      Paso 6: Probar el host virtual (recomendado)

      Una vez que el contexto se SELinux se haya actualizado con cualquiera de los métodos, Apache podrá realizar tareas de escritura en el directorio /var/www/example.com/log. Ahora podrá reiniciar con éxito el servicio de Apache:

      • sudo systemctl restart httpd

      Enumere el contenido del directorio /var/www/example.com/log para ver si Apache creó los archivos de registro:

      • ls -lZ /var/www/example.com/log

      Verá que Apache pudo crear los archivos error.log y requests.log especificados en la configuración del host virtual:

      Output

      -rw-r--r--. 1 root root 0 Feb 26 22:54 error.log -rw-r--r--. 1 root root 0 Feb 26 22:54 requests.log

      Ahora que tiene su host virtual configurado y los permisos SELinux actualizados, Apache proporcionará su nombre de dominio. Puede comprobarlo visitando http://example.com, donde debería ver algo como esto:

      ¡Éxito! ¡El host virtual example.com funciona!

      Esto confirma que su host virtual está correctamente configurado y ofrece contenido. Repita los pasos 4 y 5 para crear nuevos hosts virtuales con permisos de SELinux para dominios adicionales.

      Conclusión

      A través de este tutorial, instaló y gestionó el servidor web de Apache. Ahora que ha instaló su servidor web, dispone de varias opciones respecto del tipo de contenido que puede ofrecer y de las tecnologías que puede utilizar para crear una experiencia más completa.

      Si desea construir una pila de aplicaciones más completa, puede consultar este artículo sobre cómo configurar una pila LAMP en CentOS 7.



      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": "admin@example.com"
      }
      
      # Internationalization
      # https://docs.djangoproject.com/en/2.0/topics/i18n/
      
      ...
      

      No olvide sustituir los valores del marcador de posición your_vapid_publickey, `yourvapidpublickeyyadmin@example.com` 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

      Como Construir um Aplicativo Web Moderno para Gerenciar Informações de Clientes com Django e React no Ubuntu 18.04


      O autor selecionou a Open Sourcing Mental Ilness Ltd para receber uma doação como parte do programa Write for DOnations.

      Introdução

      As pessoas usam tipos diferentes de dispositivos para se conectar à internet e navegar pela Web. Por isso, os aplicativos precisam ser acessíveis de uma variedade de locais. Para sites tradicionais, ter uma interface responsiva geralmente é o suficiente, mas aplicativos mais complexos requerem muitas vezes o uso de outras técnicas e arquiteturas. Essas incluem ter aplicativos (separados) REST com back-end e front-end – que podem ser implementados como aplicativos Web do lado do cliente, Progressive Web Applications (PWAs – Aplicativos Progressivos para Web) ou aplicativos móveis nativos.

      Algumas ferramentas que você pode usar ao construir aplicativos mais complexos incluem:

      • O React, um framework JavaScript que permite que os desenvolvedores construam front-ends para Web e nativos para seus back-ends de API REST.
      • O Django, um framework Web gratuito e de código aberto, escrito em Python, que segue o padrão arquitetônico de software Model View Controller (MVC) [Modelo-Visão-Controle].
      • Framework Django REST, um kit de ferramentas eficaz e flexível para a criação de APIs REST no Django.

      Neste tutorial, você irá construir um aplicativo Web moderno com uma API REST separada com back-end e front-end, usando React, Django e o Django REST Framework. Ao usar o React com o Django, você conseguirá se beneficiar dos últimos avanços em JavaScript e desenvolvimento front-end. Em vez de construir um aplicativo Django que utilize um mecanismo de template interno, você usará o React como uma biblioteca de UI, beneficiando-se de sua abordagem informativa virtual Modelo de Documento por Objetos (do inglês Document Object Model – DOM) e de componentes que processam rapidamente alterações nos dados.

      O aplicativo Web que você construirá armazena registros sobre os clientes em um banco de dados, e você pode usá-lo como um ponto de partida para um aplicativo CRM. Quando você terminar, você conseguirá criar, ler, atualizar e excluir registros usando uma interface React estilizada com o Bootstrap 4.

      Pré-requisitos

      Para completar este tutorial, você precisará de:

      Passo 1 — Criando um Ambiente Virtual em Python e Instalando Dependências

      Neste passo, vamos criar um ambiente virtual e instalar as dependências necessárias para nosso aplicativo, incluindo o Django, o Django REST framework, e o django-cors-headers.

      Nosso aplicativo usará dois servidores diferentes para o Django e o React. Eles executarão em portas diferentes e funcionarão como dois domínios separados. Por isso, precisamos habilitar o compartilhamento de recursos com origens diferentes (CORS) para enviar pedidos HTTP do React para o Django sem sermos bloqueado pelo navegador.

      Navegue até seu diretório principal (home) e crie um ambiente virtual usando o módulo do Python 3 venv:

      • cd ~
      • python3 -m venv ./env

      Ative o ambiente virtual criado usando source:

      A seguir, instale as dependências do projeto com o pip. Essas incluem:

      • Django: o framework Web para o projeto.
      • Framework Django REST: um aplicativo de terceiros que constrói APIs REST com o Django.
      • django-cors-headers: um pacote que habilita o CORS.

      Instale o framework Django:

      • pip install django djangorestframework django-cors-headers

      Com as dependências do projeto instaladas, você pode criar o projeto Django e o front-end do React.

      Passo 2 — Criando o Projeto Django

      Neste passo, vamos gerar o projeto Django usando os comandos e utilitários a seguir:

      • **django-admin startproject project-name**: django-admin é um utilitário de linha de comando usado para realizar tarefas com o Django. O comando startproject cria um novo projeto Django.

      • **python manage.py startapp myapp**: manage.py é um script utilitário, adicionado automaticamente a cada projeto Django, que executa uma série de tarefas administrativas: criar novos aplicativos, migrar o banco de dados e atender ao projeto Django localmente. Seu comando startapp cria um aplicativo Django dentro do projeto Django. No Django, o termo aplicativo descreve um pacote Python que fornece alguns recursos em um projeto.

      Para começar, crie o projeto Django com django-admin startproject. Vamos chamar nosso projeto de djangoreactproject:

      • django-admin startproject djangoreactproject

      Antes de continuar, vamos ver a estrutura do diretório do nosso projeto Django usando o comando tree.

      Dica: tree é um comando útil para visualizar estruturas do arquivo e diretório da linha de comando. Você pode instalá-lo com o seguinte comando:

      • sudo apt-get install tree

      Para usá-lo, cd no diretório que você queira e digite tree ou forneça o caminho para o ponto inicial com tree /home/sammy/sammys-project.

      Navegue até a pasta djangoreactproject na raiz do seu projeto e execute o comando tree:

      • cd ~/djangoreactproject
      • tree

      Você verá o seguinte resultado:

      Output

      ├── djangoreactproject │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py

      A pasta ~/djangoreactproject é a raiz do projeto. Dentro dessa pasta, há vários arquivos que serão importantes para seu trabalho:

      • manage.py: o script utilitário que faz uma série de tarefas administrativas.
      • settings.py: o arquivo de configuração principal para o projeto Django onde você pode modificar as configurações do projeto. Essas configurações incluem variáveis como INSTALLED_APPS, uma lista de strings que designam os aplicativos habilitados para seu projeto. A documentação do Django tem mais informações sobre as configurações disponíveis.
      • urls.py: este arquivo contém uma lista de padrões de URL e visualizações relacionadas. Cada padrão mapeia uma conexão entre um URL e a função que deve ser chamada para aquele URL. Para obter mais informações sobre URLs e visualizações, consulte nosso tutorial em Como Criar Visualizações no Django.

      Nosso primeiro passo no desenvolvimento do projeto será configurar os pacotes que instalamos no passo anterior, incluindo o Django REST framework e o pacote Django CORS, adicionando-os às settings.py. Abra o arquivo com o nano ou com o seu editor favorito:

      • nano ~/djangoreactproject/djangoreactproject/settings.py

      Navegue até a configuração INSTALLED_APPS e adicione os aplicativos rest_framework e corsheaders no final da lista:

      ~/djangoreactproject/djangoreactproject/settings.py

      ...
      INSTALLED_APPS = [
          'django.contrib.admin',
          'django.contrib.auth',
          'django.contrib.contenttypes',
          'django.contrib.sessions',
          'django.contrib.messages',
          'django.contrib.staticfiles',
          'rest_framework',
          'corsheaders'
      ]
      

      A seguir, adicione o middleware corsheaders.middleware.CorsMiddleware do pacote CORS previamente instalado para a configuração do MIDDLEWARE. Esta configuração é uma lista de middlewares, uma classe Python que contém códigos processados cada vez que seu aplicativo Web lida com uma solicitação ou resposta:

      ~/djangoreactproject/djangoreactproject/settings.py

      ...
      
      MIDDLEWARE = [
      ...
      'django.contrib.messages.middleware.MessageMiddleware',
      'django.middleware.clickjacking.XFrameOptionsMiddleware',
      'corsheaders.middleware.CorsMiddleware'
      ]
      

      A seguir, você pode habilitar o CORS. A configuração CORS_ORIGIN_ALLOW_ALL especifica se você quer ou não permitir o CORS para todos os domínios e aCORS_ORIGIN_WHITELISTé uma tupla Python que contém URLs permitidos. No nosso caso, uma vez que o servidor de desenvolvimento para React estará executando em http://lochhost:3000, vamos adicionar as novas configurações CORS_ORIGIN_ALLOW_ALL = False e CORS_ORIGIN_WHHELIST('localhost:3000',) ao nosso arquivo settings.py. Adicione essas configurações em qualquer lugar do arquivo:

      ~/djangoreactproject/djangoreactproject/settings.py

      
      ...
      CORS_ORIGIN_ALLOW_ALL = False
      
      CORS_ORIGIN_WHITELIST = (
             'localhost:3000',
      )
      ...
      

      Você pode encontrar mais opções de configuração nos django-cors-headers docs.

      Salve o arquivo e saia do editor quando você terminar.

      Ainda no diretório ~/djangoreactproject, faça um novo aplicativo Django chamado customers:

      • python manage.py startapp customers

      Isso irá conter os modelos e exibições para gerenciar clientes. Modelos (models) definem os campos e comportamentos dos dados do aplicativo, enquanto exibições (views) habilitam nosso aplicativo para lidar corretamente com solicitações Web e retornar as respostas necessárias.

      A seguir, adicione este aplicativo na lista de aplicativos instalados no arquivo do seu projeto settings.py para que o Django o reconheça como parte do projeto. Abra as settings.py novamente:

      • nano ~/djangoreactproject/djangoreactproject/settings.py

      Adicione o aplicativo customers:

      ~/djangoreactproject/djangoreactproject/settings.py

      ...
      INSTALLED_APPS = [
          ...
          'rest_framework',
          'corsheaders',
          'customers'
      ]
      ...
      

      A seguir, migre o banco de dados e inicie o servidor local de desenvolvimento. Migrações consistem no modo que o Django tem de propagar as alterações que você faz nos modelos do esquema do seu banco de dados. Essas alterações podem incluir coisas como adicionar um campo ou excluir um modelo, por exemplo. Para saber mais sobre modelos e migrações, consulte o tópico Como Criar Modelos do Django.

      Migre o banco de dados:

      Inicie o servidor local de desenvolvimento:

      • python manage.py runserver

      Você verá um resultado similar ao seguinte:

      Output

      Performing system checks... System check identified no issues (0 silenced). October 22, 2018 - 15:14:50 Django version 2.1.2, using settings 'djangoreactproject.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.

      Seu aplicativo Web estará executando do endereço http://127.0.0.1:8000. Se você navegar até este endereço no seu navegador Web você verá a seguinte página:

      Django demo page

      Neste ponto, deixe o aplicativo funcionando e abra um novo terminal para continuar desenvolvendo o projeto.

      Passo 3 — Criando a Front-end React

      Nesta seção, vamos criar o aplicativo com front-end do nosso projeto usando o React.

      O React tem um utilitário oficial que permite que você crie rapidamente projetos React sem ter que configurar o Webpack diretamente. Webpack é um empacotador de módulos usado para empacotar ativos Web como o código JavaScript, CSS e imagens. Normalmente, antes de poder usar o Webpack você precisa definir várias opções de configuração, mas graças ao utilitário create-react-app você não precisa lidar com o Webpack diretamente até decidir que você precisa de mais controle. Para executar o create-react-app você pode usar o npx, uma ferramenta que executa pacotes npm binários.

      No seu segundo terminal, certifique-se de estar em seu diretório de projeto:

      Crie um projeto React chamado frontend usando o create-react-app e o npx:

      • npx create-react-app frontend

      A seguir, navegue dentro do seu aplicativo React e inicie o servidor de desenvolvimento:

      • cd ~/djangoreactproject/frontend
      • npm start

      Seu aplicativo estará executando a partir do endereço http://localhost:3000/:

      React demo page

      Deixe o servidor React de desenvolvimento funcionando e abra outra janela do terminal para prosseguir.

      Para ver a estrutura do diretório do projeto inteiro até o momento, navegue até a pasta raiz e execute novamente o tree:

      • cd ~/djangoreactproject
      • tree

      Você verá uma estrutura como essa:

      Output

      ├── customers │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── djangoreactproject │ ├── __init__.py │ ├── __pycache__ │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── frontend │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── README.md │ ├── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ └── registerServiceWorker.js │ └── yarn.lock └── manage.py

      Nosso aplicativo usará o Bootstrap 4 para estilizar a interface do React. Assim,nós o incluiremos no arquivo frontend/src/App.css, que gerencia nossas configurações CSS. Abra o arquivo:

      • nano ~/djangoreactproject/frontend/src/App.css

      Adicione o seguinte import ao início do arquivo. Você pode excluir o conteúdo do arquivo existente, embora isso não seja necessário:

      ~/djangoreactproject/frontend/src/App.css

      @import  'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';
      

      Aqui, @import é uma instrução CSS que é usada para importar regras de estilo de outras folhas de estilo.

      Agora que criamos os aplicativos com back-end e front-end, vamos criar o modelo Customer e alguns dados demonstrativos.

      Passo 4 — Criando o Modelo de Cliente e Dados Iniciais

      Após criar o aplicativo Django e o front-end React, nosso próximo passo será criar o modelo Customer, que representa a tabela do banco de dados que irá reter informações sobre os clientes. Você não precisa de nenhuma SQL (Structured Query Language, ou Linguagem de Consulta Estruturada), uma vez que o Object Relational Mapper (ORM) [Mapeamento Objeto-Relacional] do Django gerenciará as operações de banco de dados, mapeando as classes e variáveis em Python até as tabelas e colunas em SQL. Desta forma, o ORM do Django separa as interações em SQL com o banco de dados através de uma interface em Python.

      Ative seu ambiente virtual novamente:

      • cd ~
      • source env/bin/activate

      Vá até o diretório customers e abra o models.py, um arquivo Python que possui os modelos do seu aplicativo:

      • cd ~/djangoreactproject/customers/
      • nano models.py

      O arquivo terá o seguinte conteúdo:

      ~/djangoreactproject/customers/models.py

      from django.db import models
      # Create your models here.
      

      A API do modelo Customer já foi importada para o arquivo, graças à declaração de importação from django.db import models. Agora, você adicionará a classe Customer, que estende models.Model. Cada modelo no Django é uma classe em Python que estende o django.db.models.Model.

      O modelo Customer terá esses campos de banco de dados:

      • first_name — O primeiro nome do cliente.
      • last_name — O sobrenome do cliente.
      • email — O endereço de e-mail do cliente.
      • phone — O número de telefone do cliente.
      • address — O endereço do cliente.
      • description — A descrição do cliente.
      • createdAt — A data em que o cliente é adicionado.

      Também adicionaremos a função __str__(), que define como o modelo será exibido. No nosso caso, ela estará com o primeiro nome do cliente. Para saber mais sobre a construção de classes e definição de objetos, consulte o tópico pelo link How To Construct Classes and Define Objects in Python 3.

      Adicione o código a seguir ao arquivo:

      ~/djangoreactproject/customers/models.py

      from django.db import models
      
      class Customer(models.Model):
          first_name = models.CharField("First name", max_length=255)
          last_name = models.CharField("Last name", max_length=255)
          email = models.EmailField()
          phone = models.CharField(max_length=20)
          address =  models.TextField(blank=True, null=True)
          description = models.TextField(blank=True, null=True)
          createdAt = models.DateTimeField("Created At", auto_now_add=True)
      
          def __str__(self):
              return self.first_name
      

      A seguir, migre o banco de dados para criar as tabelas de banco de dados. O comando makemigrations cria os arquivos de migração onde as alterações do modelo serão adicionadas e o comando migrate aplica as alterações feitas nos arquivos de migrações ao banco de dados.

      Navegue novamente para a pasta raiz do projeto:

      Execute o que vem a seguir para criar os arquivos de migração:

      • python manage.py makemigrations

      Você receberá um resultado que se parece com este:

      Output

      customers/migrations/0001_initial.py - Create model Customer

      Aplique essas alterações ao banco de dados:

      Você verá o resultado indicando uma migração bem-sucedida:

      Output

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

      Depois, você usará um arquivo de migração de dados para criar os dados iniciais do cliente. Um arquivo de migração de dados é uma migração que adiciona ou altera dados no banco de dados. Crie um arquivo de migração de dados vazio para o aplicativo customers:

      • python manage.py makemigrations --empty --name customers customers

      Você verá a seguinte confirmação com o nome do seu arquivo de migração:

      Output

      Migrations for 'customers': customers/migrations/0002_customers.py

      Note que o nome do seu arquivo de migração é 0002_customers.py.

      Na sequência, navegue dentro da pasta de migração do aplicativo customers:

      • cd ~/djangoreactproject/customers/migrations

      Abra o arquivo de migração criado:

      Este é o conteúdo inicial do arquivo:

      ~/djangoreactproject/customers/migrations/0002_customers.py

      from django.db import migrations
      
      class Migration(migrations.Migration):
          dependencies = [
              ('customers', '0001_initial'),
          ]
          operations = [
          ]        
      

      A instrução de importação importa a API migrations, uma API Django para criação de migrações, do django.db, um pacote integrado que contém classes para trabalhar com bancos de dados.

      A classe Migration é uma classe em Python que descreve as operações que são executadas durante a migração de bancos de dados. Esta classe estende migrations.Migration e tem duas listas:

      • dependencies: contém as migrações dependentes.
      • operations: contém as operações que serão executadas quando aplicarmos a migração.

      Na sequência, adicione um method para criar os dados de cliente da demonstração. Adicione o seguinte método antes da definição da classe Migration:

      ~/djangoreactproject/customers/migrations/0002_customers.py

      ...
      def create_data(apps, schema_editor):
          Customer = apps.get_model('customers', 'Customer')
          Customer(first_name="Customer 001", last_name="Customer 001", email="customer001@email.com", phone="00000000", address="Customer 000 Address", description= "Customer 001 description").save()
      
      ...
      

      Neste método, estamos pegando a classe Customer do nosso app customers e criando um cliente de demonstração para inserir no banco de dados.

      Para obter a classe Customer, que irá habilitar a criação de novos clientes, usamos o método get_model() do objeto apps. O objeto apps representa o registry dos aplicativos instalados e seus modelos de banco de dados.

      O objeto apps passará do método RunPython() quando o usamos para executar o create_data(). Adicione o método migrations.RunPython() à lista operations vazia:

      ~/djangoreactproject/customers/migrations/0002_customers.py

      
      ...
          operations = [
              migrations.RunPython(create_data),
          ]  
      

      O RunPython() faz parte da API com Migrations que permite que você execute códigos em Python personalizados em uma migração. Nossa lista operations especifica que este método será executado quando aplicarmos a migração.

      Este é o arquivo completo:

      ~/djangoreactproject/customers/migrations/0002_customers.py

      from django.db import migrations
      
      def create_data(apps, schema_editor):
          Customer = apps.get_model('customers', 'Customer')
          Customer(first_name="Customer 001", last_name="Customer 001", email="customer001@email.com", phone="00000000", address="Customer 000 Address", description= "Customer 001 description").save()
      
      class Migration(migrations.Migration):
          dependencies = [
              ('customers', '0001_initial'),
          ]
          operations = [
              migrations.RunPython(create_data),
          ]        
      

      Para obter mais informações sobre a migração de dados, consulte a documentação sobre migrações de dados no Django.

      Para migrar seu banco de dados, navegue primeiro de volta para a pasta raiz do seu projeto:

      Migre o seu banco de dados para criar os dados para demonstração:

      Você verá o resultado que confirma a migração:

      Output

      Operations to perform: Apply all migrations: admin, auth, contenttypes, customers, sessions Running migrations: Applying customers.0002_customers... OK

      Para obter mais detalhes sobre este processo, volte para o link How To Create Django Models.

      Com o modelo Customer e dados de demonstração criados, podemos continuar para a construção da API REST.

      Passo 5 — Criando a API REST

      Neste passo, vamos criar a API REST utilizando o Django REST Framework. Vamos criar várias *visualizações da API *diferentes. Uma visualização de API é uma função que lida com um pedido ou chamada de API, enquanto um *ponto de extremidade de API *é um URL único que representa um ponto de contato com o sistema REST. Por exemplo, quando o usuário envia um pedido GET para um ponto de extremidade de API, o Django chama a função correspondente ou a visualização de API para lidar com o pedido e retornar quaisquer resultados possíveis.

      Também vamos usar os serializers. Um serializador no Django REST Framework permite que as instâncias de modelos complexas e o QuerySets sejam convertidos em formato JSON para consumo de API. A classe de serializadores também pode funcionar na outra direção, fornecendo mecanismos para processar e desserializar dados em modelos Django e QuerySets.

      Nossos pontos de extremidade de API irão incluir:

      • api/customers: este ponto de extremidade é usado para criar clientes e retorna conjuntos de clientes paginados.
      • api/customers/<pk>: este ponto de extremidade é usado para obter, atualizar e excluir clientes únicos por chave ou ID primária.

      Também vamos criar URLs no arquivo urls.py do projeto para os pontos de extremidade correspondentes (ou seja, api/customers e <pk>).

      Vamos começar criando a classe de serializadores para nosso modelo Customer.

      Adicionando a Classe de Serializadores

      Criar uma classe de serializadores para nosso modelo Customer é necessário para transformar as instâncias de cliente e os QuerySets de e para JSON. Para criar a classe de serializadores, faça primeiro um arquivo serializers.py dentro do aplicativo customers:

      • cd ~/djangoreactproject/customers/
      • nano serializers.py

      Adicione o seguinte código para importar o API do serializador e o modelo Customer:

      ~/djangoreactproject/customers/serializers.py

      from rest_framework import serializers
      from .models import Customer
      

      A seguir, crie uma classe de serializadores que estende serializers.ModelSerializer e especifica os campos que serão serializados:

      ~/djangoreactproject/customers/serializers.py

      
      ...
      class CustomerSerializer(serializers.ModelSerializer):
      
          class Meta:
              model = Customer 
              fields = ('pk','first_name', 'last_name', 'email', 'phone','address','description')
      

      A classe Meta especifica o modelo e os campos para serializar: pk, first_name, last_name, email, phone, address, description.

      Este é o conteúdo completo do arquivo:

      ~/djangoreactproject/customers/serializers.py

      from rest_framework import serializers
      from .models import Customer
      
      class CustomerSerializer(serializers.ModelSerializer):
      
          class Meta:
              model = Customer 
              fields = ('pk','first_name', 'last_name', 'email', 'phone','address','description')
      

      Agora que criamos nossa classe de serializadores, podemos adicionar as visualizações da API.

      Adicionando as Visualizações da API

      Nesta seção, vamos criar as visualizações da API para nosso aplicativo que serão chamadas pelo Django quando o usuário visitar o ponto de extremidade correspondente à função da visualização.

      Abra ~/djangoreactproject/customers/views.py:

      • nano ~/djangoreactproject/customers/views.py

      Exclua o que estiver ali e adicione as seguintes importações:

      ~/djangoreactproject/customers/views.py

      from rest_framework.response import Response
      from rest_framework.decorators import api_view
      from rest_framework import status
      
      from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
      from .models import Customer 
      from .serializers import *
      

      Estamos importando o serializador que criamos, junto com o modelo Customer e as APIs com o Django e o Django REST Framework.

      Em seguida, adicione a visualização para processar os pedidos do POST e GET HTTP:

      ~/djangoreactproject/customers/views.py

      ...
      
      @api_view(['GET', 'POST'])
      def customers_list(request):
          """
       List  customers, or create a new customer.
       """
          if request.method == 'GET':
              data = []
              nextPage = 1
              previousPage = 1
              customers = Customer.objects.all()
              page = request.GET.get('page', 1)
              paginator = Paginator(customers, 10)
              try:
                  data = paginator.page(page)
              except PageNotAnInteger:
                  data = paginator.page(1)
              except EmptyPage:
                  data = paginator.page(paginator.num_pages)
      
              serializer = CustomerSerializer(data,context={'request': request} ,many=True)
              if data.has_next():
                  nextPage = data.next_page_number()
              if data.has_previous():
                  previousPage = data.previous_page_number()
      
              return Response({'data': serializer.data , 'count': paginator.count, 'numpages' : paginator.num_pages, 'nextlink': '/api/customers/?page=' + str(nextPage), 'prevlink': '/api/customers/?page=' + str(previousPage)})
      
          elif request.method == 'POST':
              serializer = CustomerSerializer(data=request.data)
              if serializer.is_valid():
                  serializer.save()
                  return Response(serializer.data, status=status.HTTP_201_CREATED)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      

      Primeiramente, usamos o decorador @api_view(['GET', 'POST']) para criar uma visualização de API que possa aceitar solicitações GET e POST. Um decorator é uma função que assume outra função e a amplia de maneira dinâmica.

      No corpo do método, utilizamos a variável request.method para verificar o método HTTP atual e executar a lógica correspondente, dependendo do tipo de solicitação:

      • Se for uma solicitação GET, o método pagina os dados utilizando o Paginator do Django e retorna a primeira página de dados após a serialização, a contagem de clientes disponíveis, o número de páginas disponíveis, e os links para as páginas anteriores e as páginas posteriores. O Paginator é uma classe integrada do Django que organiza uma lista de dados em páginas e proporciona métodos para acessar os itens de cada página.
      • Se for uma solicitação POST, o método serializa os dados recebidos do cliente e chama então o método save() do objeto serializador. Então, ele retorna um objeto de Resposta, uma instância do HttpResponse, com um código de status 201. Cada visualização que você cria é responsável por retornar um objeto HttpResponse. O método save() salva os dados serializados no banco de dados.

      Para saber mais sobre HttpResponse e visualizações, leia esta discussão: creating view functions.

      Agora, adicione a visualização de API que será responsável por processar os pedidos GET, PUT e DELETE para obter, atualizar e excluir clientes por pk (chave primária):

      ~/djangoreactproject/customers/views.py

      
      ...
      @api_view(['GET', 'PUT', 'DELETE'])
      def customers_detail(request, pk):
       """
       Retrieve, update or delete a customer by id/pk.
       """
          try:
              customer = Customer.objects.get(pk=pk)
          except Customer.DoesNotExist:
              return Response(status=status.HTTP_404_NOT_FOUND)
      
          if request.method == 'GET':
              serializer = CustomerSerializer(customer,context={'request': request})
              return Response(serializer.data)
      
          elif request.method == 'PUT':
              serializer = CustomerSerializer(customer, data=request.data,context={'request': request})
              if serializer.is_valid():
                  serializer.save()
                  return Response(serializer.data)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
          elif request.method == 'DELETE':
              customer.delete()
              return Response(status=status.HTTP_204_NO_CONTENT)
      

      O método é decorado com @api_view(['GET', 'PUT', 'DELETE']) para indicara que se trata de uma visualização de API que pode aceitar solicitações GET, PUT e DELETE.

      A checagem no campo request.method verifica o método de solicitação e, dependendo do seu valor, chama a lógica correta:

      • Se for um pedido GET, os dados do cliente são serializados e enviados utilizando um objeto de Resposta.
      • Se for um pedido PUT, o método cria um serializador para novos dados do cliente. Em seguida, ele chama o método save() do objeto serializador criado. Finalmente, ele envia um objeto de Resposta com o cliente atualizado.
      • Se for um pedido DELETE, o método chama o método delete() do objeto cliente a excluir; depois, retorna um objeto de Resposta sem dados.

      O arquivo final se parece com este:

      ~/djangoreactproject/customers/views.py

      from rest_framework.response import Response
      from rest_framework.decorators import api_view
      from rest_framework import status
      
      from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
      from .models import Customer 
      from .serializers import *
      
      
      @api_view(['GET', 'POST'])
      def customers_list(request):
          """
       List  customers, or create a new customer.
       """
          if request.method == 'GET':
              data = []
              nextPage = 1
              previousPage = 1
              customers = Customer.objects.all()
              page = request.GET.get('page', 1)
              paginator = Paginator(customers, 5)
              try:
                  data = paginator.page(page)
              except PageNotAnInteger:
                  data = paginator.page(1)
              except EmptyPage:
                  data = paginator.page(paginator.num_pages)
      
              serializer = CustomerSerializer(data,context={'request': request} ,many=True)
              if data.has_next():
                  nextPage = data.next_page_number()
              if data.has_previous():
                  previousPage = data.previous_page_number()
      
              return Response({'data': serializer.data , 'count': paginator.count, 'numpages' : paginator.num_pages, 'nextlink': '/api/customers/?page=' + str(nextPage), 'prevlink': '/api/customers/?page=' + str(previousPage)})
      
          elif request.method == 'POST':
              serializer = CustomerSerializer(data=request.data)
              if serializer.is_valid():
                  serializer.save()
                  return Response(serializer.data, status=status.HTTP_201_CREATED)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
      @api_view(['GET', 'PUT', 'DELETE'])
      def customers_detail(request, pk):
          """
       Retrieve, update or delete a customer by id/pk.
       """
          try:
              customer = Customer.objects.get(pk=pk)
          except Customer.DoesNotExist:
              return Response(status=status.HTTP_404_NOT_FOUND)
      
          if request.method == 'GET':
              serializer = CustomerSerializer(customer,context={'request': request})
              return Response(serializer.data)
      
          elif request.method == 'PUT':
              serializer = CustomerSerializer(customer, data=request.data,context={'request': request})
              if serializer.is_valid():
                  serializer.save()
                  return Response(serializer.data)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
          elif request.method == 'DELETE':
              customer.delete()
              return Response(status=status.HTTP_204_NO_CONTENT)
      

      Agora, podemos seguir em frente para criar nossos pontos de extremidade.

      Adicionando Pontos de extremidade de API

      Agora, vamos criar os pontos de extremidade de API: api/customers/, para consultar e criar clientes e api/customers/<pk>, para obter, atualizar ou excluir clientes únicos por pk.

      Abra ~/djangoreactproject/djangoreactproject/urls.py:

      • nano ~/djangoreactproject/djangoreactproject/urls.py

      Deixe o que está lá, mas adicione a importação às visualizações do customers no topo do arquivo:

      ~/djangoreactproject/djangoreactproject/urls.py

      from django.contrib import admin
      from django.urls import path
      from customers import views
      from django.conf.urls import url
      

      Em seguida, adicione os URLs api/customers/ e api/customers/<pk> à lista urlpatterns que contém os URLs do aplicativo:

      ~/djangoreactproject/djangoreactproject/urls.py

      ...
      
      urlpatterns = [
          path('admin/', admin.site.urls),
          url(r'^api/customers/$', views.customers_list),
          url(r'^api/customers/(?P<pk>[0-9]+)$', views.customers_detail),
      ]
      

      Com nossos pontos de extremidade de REST criados, vamos ver como podemos consumi-los.

      Neste passo, vamos instalar o Axios, o cliente HTTP que vamos usar para fazer chamadas de API. Também vamos criar uma classe para consumir os pontos de extremidade da API que criamos.

      Primeiramente, desative o seu ambiente virtual:

      Em seguida, navegue até sua pasta frontend:

      • cd ~/djangoreactproject/frontend

      Instale o axios a partir do npm utilizando:

      A opção --save adiciona a dependência do axios ao arquivo package.json do seu aplicativo.

      Em seguida, crie um arquivo JavaScript chamado CustomersService.js, que irá conter o código para chamar as APIs REST. Vamos fazer isso dentro da pasta src, onde o código do aplicativo para nosso projeto irá viver:

      • cd src
      • nano CustomersService.js

      Adicione o seguinte código, que contém métodos para se conectar à API REST do Django:

      ~/djangoreactproject/frontend/src/CustomersService.js

      import axios from 'axios';
      const API_URL = 'http://localhost:8000';
      
      export default class CustomersService{
      
          constructor(){}
      
      
          getCustomers() {
              const url = `${API_URL}/api/customers/`;
              return axios.get(url).then(response => response.data);
          }  
          getCustomersByURL(link){
              const url = `${API_URL}${link}`;
              return axios.get(url).then(response => response.data);
          }
          getCustomer(pk) {
              const url = `${API_URL}/api/customers/${pk}`;
              return axios.get(url).then(response => response.data);
          }
          deleteCustomer(customer){
              const url = `${API_URL}/api/customers/${customer.pk}`;
              return axios.delete(url);
          }
          createCustomer(customer){
              const url = `${API_URL}/api/customers/`;
              return axios.post(url,customer);
          }
          updateCustomer(customer){
              const url = `${API_URL}/api/customers/${customer.pk}`;
              return axios.put(url,customer);
          }
      }
      

      A classe CustomersService irá chamar os seguintes métodos do Axios:

      • getCustomers(): obtém a primeira página de clientes.
      • getCustomersByURL(): obtém clientes por URL. Isso possibilita obter-se as próximas páginas de clientes, atravessando-se links do tipo /api/customers/?page=2.
      • getCustomer(): obtém um cliente pela chave primária.
      • createCustomer(): cria um cliente.
      • updateCustomer(): atualiza um cliente.
      • deleteCustomer(): exclui um cliente.

      Agora, podemos exibir os dados de nossa API na nossa interface com a UI React criando um componente CustomersList.

      Passo 7 — Exibindo Dados da API no Aplicativo React

      Neste passo, vamos criar o componente do aplicativo React chamado CustomersList. Um componente do React representa uma parte da UI; ele também permite que você divida a UI em pedaços independentes e reutilizáveis.

      Inicie criando o CustomersList.js em frontend/src:

      • nano ~/djangoreactproject/frontend/src/CustomersList.js

      Inicie importando o React e o Component para criar um componente do React:

      ~/djangoreactproject/frontend/src/CustomersList.js

      import  React, { Component } from  'react';
      

      Em seguida, importe e crie a instância do módulo CustomersService que você criou no passo anterior, que proporciona métodos que interagem com o back-end da API REST:

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      import  CustomersService  from  './CustomersService';
      
      const  customersService  =  new  CustomersService();
      

      Em seguida, crie um componente CustomersList que estende o Component para chamar a API REST. Um componente React deve estender ou subclassificar a classe Component. Para saber mais sobre as classes E6 e herança, consulte nossos tutorial Understanding Classes in JavaScript.

      Adicione o seguinte código para criar um componente do React que estende o react.Component:

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      class  CustomersList  extends  Component {
      
          constructor(props) {
              super(props);
              this.state  = {
                  customers: [],
                  nextPageURL:  ''
              };
              this.nextPage  =  this.nextPage.bind(this);
              this.handleDelete  =  this.handleDelete.bind(this);
          }
      }
      export  default  CustomersList;
      

      Dentro do construtor, estamos inicializando o objeto de ](https://reactjs.org/docs/react-component.html#state)estado[. Isso mantém as variáveis de estado do nosso componente utilizando uma matriz de clientes vazia. Esta matriz conterá os clientes e um nextPageURL que irá reter o URL da próxima página a ser recuperada do back-end da API. Também estamos ligando os métodos nextPage() e o handleDelete() a este para que eles fiquem acessíveis a partir do código HTML.

      Em seguida, adicione o método componentDidMount() e uma chamada para o getCustomers() dentro da classe CustomersList, antes da chave de fechamento.

      O método componentDidMount() é um método de ciclo de vida do componente que é chamado quando o componente é criado e inserido no DOM. O getCustomers() chama o objeto Customers Service para obter a primeira página de dados e o link da página seguinte a partir do back-end do Django:

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      componentDidMount() {
          var  self  =  this;
          customersService.getCustomers().then(function (result) {
              self.setState({ customers:  result.data, nextPageURL:  result.nextlink})
          });
      }
      

      Agora, adicione o método handleDelete() que lida com a exclusão de um cliente, abaixo do componentDidMount():

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      handleDelete(e,pk){
          var  self  =  this;
          customersService.deleteCustomer({pk :  pk}).then(()=>{
              var  newArr  =  self.state.customers.filter(function(obj) {
                  return  obj.pk  !==  pk;
              });
              self.setState({customers:  newArr})
          });
      }
      

      O método handleDelete() chama o método deleteCustomer() para excluir um cliente utilizando sua pk (chave primária). Se a operação for bem-sucedida, a matriz de customers é filtrada em relação ao cliente removido.

      Em seguida, adicione um método nextPage() para obter os dados da próxima página e atualize o próximo link da página:

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      nextPage(){
          var  self  =  this;
          customersService.getCustomersByURL(this.state.nextPageURL).then((result) => {
              self.setState({ customers:  result.data, nextPageURL:  result.nextlink})
          });
      }
      

      O método nextPage() chama um método getCustomersByURL(), que recebe o próximo URL da página do objeto de estado, this.state.nextPageURL​​​ e atualiza a matriz de customers com os dados retornados.

      Finalmente, adicione o método render() do componente, que renderiza uma tabela de clientes a partir do estado do componente:

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      render() {
      
          return (
          <div  className="customers--list">
              <table  className="table">
                  <thead  key="thead">
                  <tr>
                      <th>#</th>
                      <th>First Name</th>
                      <th>Last Name</th>
                      <th>Phone</th>
                      <th>Email</th>
                      <th>Address</th>
                      <th>Description</th>
                      <th>Actions</th>
                  </tr>
                  </thead>
                  <tbody>
                      {this.state.customers.map( c  =>
                      <tr  key={c.pk}>
                          <td>{c.pk}  </td>
                          <td>{c.first_name}</td>
                          <td>{c.last_name}</td>
                          <td>{c.phone}</td>
                          <td>{c.email}</td>
                          <td>{c.address}</td>
                          <td>{c.description}</td>
                          <td>
                          <button  onClick={(e)=>  this.handleDelete(e,c.pk) }> Delete</button>
                          <a  href={"/customer/" + c.pk}> Update</a>
                          </td>
                      </tr>)}
                  </tbody>
              </table>
              <button  className="btn btn-primary"  onClick=  {  this.nextPage  }>Next</button>
          </div>
          );
      }
      

      Este é o conteúdo completo do arquivo:

      ~/djangoreactproject/frontend/src/CustomersList.js

      import  React, { Component } from  'react';
      import  CustomersService  from  './CustomersService';
      
      const  customersService  =  new  CustomersService();
      
      class  CustomersList  extends  Component {
      
      constructor(props) {
          super(props);
          this.state  = {
              customers: [],
              nextPageURL:  ''
          };
          this.nextPage  =  this.nextPage.bind(this);
          this.handleDelete  =  this.handleDelete.bind(this);
      }
      
      componentDidMount() {
          var  self  =  this;
          customersService.getCustomers().then(function (result) {
              console.log(result);
              self.setState({ customers:  result.data, nextPageURL:  result.nextlink})
          });
      }
      handleDelete(e,pk){
          var  self  =  this;
          customersService.deleteCustomer({pk :  pk}).then(()=>{
              var  newArr  =  self.state.customers.filter(function(obj) {
                  return  obj.pk  !==  pk;
              });
      
              self.setState({customers:  newArr})
          });
      }
      
      nextPage(){
          var  self  =  this;
          console.log(this.state.nextPageURL);        
          customersService.getCustomersByURL(this.state.nextPageURL).then((result) => {
              self.setState({ customers:  result.data, nextPageURL:  result.nextlink})
          });
      }
      render() {
      
          return (
              <div  className="customers--list">
                  <table  className="table">
                  <thead  key="thead">
                  <tr>
                      <th>#</th>
                      <th>First Name</th>
                      <th>Last Name</th>
                      <th>Phone</th>
                      <th>Email</th>
                      <th>Address</th>
                      <th>Description</th>
                      <th>Actions</th>
                  </tr>
                  </thead>
                  <tbody>
                  {this.state.customers.map( c  =>
                      <tr  key={c.pk}>
                      <td>{c.pk}  </td>
                      <td>{c.first_name}</td>
                      <td>{c.last_name}</td>
                      <td>{c.phone}</td>
                      <td>{c.email}</td>
                      <td>{c.address}</td>
                      <td>{c.description}</td>
                      <td>
                      <button  onClick={(e)=>  this.handleDelete(e,c.pk) }> Delete</button>
                      <a  href={"/customer/" + c.pk}> Update</a>
                      </td>
                  </tr>)}
                  </tbody>
                  </table>
                  <button  className="btn btn-primary"  onClick=  {  this.nextPage  }>Next</button>
              </div>
              );
        }
      }
      export  default  CustomersList;
      

      Agora que criamos o componente CustomersList para exibir a lista de clientes, podemos adicionar o componente que lida com a criação e as atualizações do cliente.

      Passo 8 — Adicionando os Componentes Customer Create e Update do React

      Neste passo, vamos criar o componente CustomerCreateUpdate que irá lidar com a criação e atualização dos clientes. Ele irá fazer isso fornecendo um formulário que os usuários podem usar para digitar dados sobre um novo cliente ou atualizar um item existente.

      No frontend/src, crie um arquivo CustomerCreateUpdate.js:

      • nano ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      Adicione o seguinte código para criar um componente do React, import o React e o Component:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      import  React, { Component } from  'react';
      

      Além disso, podemos importar e instanciar a classe CustomersService que criamos no passo anterior, a qual proporciona métodos que interagem com o back-end da API REST:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      ...
      import  CustomersService  from  './CustomersService';
      
      const  customersService  =  new  CustomersService();
      

      Em seguida, crie um componente CustomerCreateUpdate que estende o Component para criar e atualizar os clientes:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      class  CustomerCreateUpdate  extends  Component {
      
          constructor(props) {
              super(props);
          }
      
      }
      export default CustomerCreateUpdate;
      

      Dentro da definição da classe, adicione o método render() do componente, que renderiza uma forma HTML que recebe informações sobre o cliente:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      render() {
              return (
                <form onSubmit={this.handleSubmit}>
                <div className="form-group">
                  <label>
                    First Name:</label>
                    <input className="form-control" type="text" ref='firstName' />
      
                  <label>
                    Last Name:</label>
                    <input className="form-control" type="text" ref='lastName'/>
      
                  <label>
                    Phone:</label>
                    <input className="form-control" type="text" ref='phone' />
      
                  <label>
                    Email:</label>
                    <input className="form-control" type="text" ref='email' />
      
                  <label>
                    Address:</label>
                    <input className="form-control" type="text" ref='address' />
      
                  <label>
                    Description:</label>
                    <textarea className="form-control" ref='description' ></textarea>
      
      
                  <input className="btn btn-primary" type="submit" value="Submit" />
                  </div>
                </form>
              );
        }
      

      Para cada elemento form input, o método adiciona uma propriedade ref para acessar e definir o valor do elemento do formulário.

      Em seguida, acima do método render(), defina um método handleSubmit(event) para que você tenha a funcionalidade apropriada quando um usuário clicar no botão para enviar:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      handleSubmit(event) {
          const { match: { params } } =  this.props;
          if(params  &&  params.pk){
              this.handleUpdate(params.pk);
          }
          else
          {
              this.handleCreate();
          }
          event.preventDefault();
      }
      
      ...
      

      O método handleSubmit(event) cuida do envio do formulário e, dependendo da rota, chama o método handleUpdate(pk) para atualizar o cliente com o método pk aprovado ou o método handleCreate() para criar um novo cliente. Vamos definir esses métodos em breve.

      De volta ao construtor do componente, conecte o método handleSubmit() recém-adicionado a este, para que você possa acessá-lo em seu formulário:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      ...
      class CustomerCreateUpdate extends Component {
      
      constructor(props) {
          super(props);
          this.handleSubmit = this.handleSubmit.bind(this);
      }
      ...
      

      Em seguida, defina o método handleCreate() para criar um cliente a partir dos dados do formulário. Acima do método handleSubmit(event) adicione o seguinte código:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      handleCreate(){
          customersService.createCustomer(
              {
              "first_name":  this.refs.firstName.value,
              "last_name":  this.refs.lastName.value,
              "email":  this.refs.email.value,
              "phone":  this.refs.phone.value,
              "address":  this.refs.address.value,
              "description":  this.refs.description.value
              }).then((result)=>{
                      alert("Customer created!");
              }).catch(()=>{
                      alert('There was an error! Please re-check your form.');
              });
      }
      
      ...
      

      O método handleCreate() será usado para criar um cliente a partir dos dados inseridos. Ele chama o método CustomersService.createCustomer() correspondente que faz a API real chamar o back-end para criar um cliente.

      Depois, abaixo do método handleCreate(), defina o método handleUpdate(pk) para implementar as atualizações:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      handleUpdate(pk){
      customersService.updateCustomer(
          {
          "pk":  pk,
          "first_name":  this.refs.firstName.value,
          "last_name":  this.refs.lastName.value,
          "email":  this.refs.email.value,
          "phone":  this.refs.phone.value,
          "address":  this.refs.address.value,
          "description":  this.refs.description.value
          }
          ).then((result)=>{
      
              alert("Customer updated!");
          }).catch(()=>{
              alert('There was an error! Please re-check your form.');
          });
      }
      

      O método updateCustomer() irá atualizar um cliente através da pk utilizando as novas informações do formulário de informações do cliente. Ele chama o método customersService.updateCustomer().

      Em seguida, adicione um método componentDidMount(). Se o usuário visitar uma rota customer/:pk, queremos preencher o formulário com informações relacionadas ao cliente, utilizando a chave primária do URL. Para fazer isso, podemos adicionar o método getCustomer(pk) após o componente ser montado no evento do ciclo de vida do componentDidMount(). Adicione o seguinte código abaixo do construtor do componente para adicionar este método:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      componentDidMount(){
          const { match: { params } } =  this.props;
          if(params  &&  params.pk)
          {
              customersService.getCustomer(params.pk).then((c)=>{
                  this.refs.firstName.value  =  c.first_name;
                  this.refs.lastName.value  =  c.last_name;
                  this.refs.email.value  =  c.email;
                  this.refs.phone.value  =  c.phone;
                  this.refs.address.value  =  c.address;
                  this.refs.description.value  =  c.description;
              })
          }
      }
      

      Este é o conteúdo completo do arquivo:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      import React, { Component } from 'react';
      import CustomersService from './CustomersService';
      
      const customersService = new CustomersService();
      
      class CustomerCreateUpdate extends Component {
          constructor(props) {
              super(props);
      
              this.handleSubmit = this.handleSubmit.bind(this);
            }
      
            componentDidMount(){
              const { match: { params } } = this.props;
              if(params && params.pk)
              {
                customersService.getCustomer(params.pk).then((c)=>{
                  this.refs.firstName.value = c.first_name;
                  this.refs.lastName.value = c.last_name;
                  this.refs.email.value = c.email;
                  this.refs.phone.value = c.phone;
                  this.refs.address.value = c.address;
                  this.refs.description.value = c.description;
                })
              }
            }
      
            handleCreate(){
              customersService.createCustomer(
                {
                  "first_name": this.refs.firstName.value,
                  "last_name": this.refs.lastName.value,
                  "email": this.refs.email.value,
                  "phone": this.refs.phone.value,
                  "address": this.refs.address.value,
                  "description": this.refs.description.value
              }          
              ).then((result)=>{
                alert("Customer created!");
              }).catch(()=>{
                alert('There was an error! Please re-check your form.');
              });
            }
            handleUpdate(pk){
              customersService.updateCustomer(
                {
                  "pk": pk,
                  "first_name": this.refs.firstName.value,
                  "last_name": this.refs.lastName.value,
                  "email": this.refs.email.value,
                  "phone": this.refs.phone.value,
                  "address": this.refs.address.value,
                  "description": this.refs.description.value
              }          
              ).then((result)=>{
                console.log(result);
                alert("Customer updated!");
              }).catch(()=>{
                alert('There was an error! Please re-check your form.');
              });
            }
            handleSubmit(event) {
              const { match: { params } } = this.props;
      
              if(params && params.pk){
                this.handleUpdate(params.pk);
              }
              else
              {
                this.handleCreate();
              }
      
              event.preventDefault();
            }
      
            render() {
              return (
                <form onSubmit={this.handleSubmit}>
                <div className="form-group">
                  <label>
                    First Name:</label>
                    <input className="form-control" type="text" ref='firstName' />
      
                  <label>
                    Last Name:</label>
                    <input className="form-control" type="text" ref='lastName'/>
      
                  <label>
                    Phone:</label>
                    <input className="form-control" type="text" ref='phone' />
      
                  <label>
                    Email:</label>
                    <input className="form-control" type="text" ref='email' />
      
                  <label>
                    Address:</label>
                    <input className="form-control" type="text" ref='address' />
      
                  <label>
                    Description:</label>
                    <textarea className="form-control" ref='description' ></textarea>
      
      
                  <input className="btn btn-primary" type="submit" value="Submit" />
                  </div>
                </form>
              );
            }  
      }
      
      export default CustomerCreateUpdate;
      

      Com o componente CustomerCreateUpdate criado, podemos atualizar o componente principal do App para adicionar links aos diferentes componentes que criamos.

      Passo 9 — Atualizando o Componente Principal do App

      Nesta seção, atualizaremos o componente App do nosso aplicativo para criar links para os componentes que criamos nos passos anteriores.

      A partir da pasta frontend, execute o seguinte comando para instalar o React Router, que permite que você adicione roteamento e navegação entre vários componentes do React:

      • cd ~/djangoreactproject/frontend
      • npm install --save react-router-dom

      Em seguida, abra ~/djangoreactproject/frontend/src/App.js:

      • nano ~/djangoreactproject/frontend/src/App.js

      Exclua tudo o que está lá e adicione o seguinte código para importar as classes necessárias para adicionar roteamentos. Esses incluem o BrowserRouter, que cria um componente Router e o Route, que cria um componente de rota:

      ~/djangoreactproject/frontend/src/App.js

      import  React, { Component } from  'react';
      import { BrowserRouter } from  'react-router-dom'
      import { Route, Link } from  'react-router-dom'
      import  CustomersList  from  './CustomersList'
      import  CustomerCreateUpdate  from  './CustomerCreateUpdate'
      import  './App.css';
      

      O BrowserRoutermantém a UI em sincronia com o URL utilizando a API de histórico HTML5.

      Na sequência, crie um layout básico que fornece o componente base a ser encapsulado pelo componente BrowserRouter:

      ~/djangoreactproject/frontend/src/App.js

      ...
      
      const  BaseLayout  = () => (
      <div  className="container-fluid">
          <nav  className="navbar navbar-expand-lg navbar-light bg-light">
              <a  className="navbar-brand"  href="https://www.digitalocean.com/#">Django React Demo</a>
              <button  className="navbar-toggler"  type="button"  data-toggle="collapse"  data-target="#navbarNavAltMarkup"  aria-controls="navbarNavAltMarkup"  aria-expanded="false"  aria-label="Toggle navigation">
              <span  className="navbar-toggler-icon"></span>
          </button>
          <div  className="collapse navbar-collapse"  id="navbarNavAltMarkup">
              <div  className="navbar-nav">
                  <a  className="nav-item nav-link"  href="/">CUSTOMERS</a>
                  <a  className="nav-item nav-link"  href="http://www.digitalocean.com/customer">CREATE CUSTOMER</a>
              </div>
          </div>
          </nav>
          <div  className="content">
              <Route  path="/"  exact  component={CustomersList}  />
              <Route  path="/customer/:pk"  component={CustomerCreateUpdate}  />
              <Route  path="/customer/"  exact  component={CustomerCreateUpdate}  />
          </div>
      </div>
      )
      

      Usamos o componente Route para definir as rotas do nosso aplicativo; o componente que o roteador deve carregar tão logo encontre um que seja compatível. Cada rota precisa de um path para especificar o caminho compatível e de um component para especificar o componente a carregar. A propriedade exact diz ao roteador para corresponder ao caminho exato.

      Finalmente, crie o componente App, o componente root ou o componente de nível superior do nosso aplicativo React:

      ~/djangoreactproject/frontend/src/App.js

      ...
      
      class  App  extends  Component {
      
      render() {
          return (
          <BrowserRouter>
              <BaseLayout/>
          </BrowserRouter>
          );
      }
      }
      export  default  App;
      

      Nós juntamos o componente BaseLayout ao componente BrowserRouter, uma vez que o nosso app deverá ser executado no navegador.

      O arquivo final se parece com este:

      ~/djangoreactproject/frontend/src/App.js

      import React, { Component } from 'react';
      import { BrowserRouter } from 'react-router-dom'
      import { Route, Link } from 'react-router-dom'
      
      import  CustomersList from './CustomersList'
      import  CustomerCreateUpdate  from './CustomerCreateUpdate'
      import './App.css';
      
      const BaseLayout = () => (
        <div className="container-fluid">
      <nav className="navbar navbar-expand-lg navbar-light bg-light">
        <a className="navbar-brand" href="https://www.digitalocean.com/#">Django React Demo</a>
        <button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
          <span className="navbar-toggler-icon"></span>
        </button>
        <div className="collapse navbar-collapse" id="navbarNavAltMarkup">
          <div className="navbar-nav">
            <a className="nav-item nav-link" href="/">CUSTOMERS</a>
            <a className="nav-item nav-link" href="http://www.digitalocean.com/customer">CREATE CUSTOMER</a>
      
          </div>
        </div>
      </nav>  
      
          <div className="content">
            <Route path="/" exact component={CustomersList} />
            <Route path="/customer/:pk"  component={CustomerCreateUpdate} />
            <Route path="/customer/" exact component={CustomerCreateUpdate} />
      
          </div>
      
        </div>
      )
      
      class App extends Component {
        render() {
          return (
            <BrowserRouter>
              <BaseLayout/>
            </BrowserRouter>
          );
        }
      }
      
      export default App;
      

      Após adicionar o roteamento ao nosso aplicativo, estamos agora prontos para testar o aplicativo. Navegue até o endereço http://localhost:3000. Você deve ver a primeira página do aplicativo:

      Application Home Page

      Com este aplicativo em funcionamento, agora você tem a base de um aplicativo de CRM.

      Conclusão

      Neste tutorial, você criou um aplicativo de demonstração utilizando o Django e o React. Você usou Django REST framework para construir a API REST, o Axios para consumir a API, e o Bootstrap 4 para estilizar o seu CSS. Você pode encontrar o código-fonte deste projeto neste repositório do GitHub.

      Esta configuração de tutorial usou apps separados de front-end e back-end. Para obter uma abordagem diferente para integrar o React ao Django, verifique este tutorial e este tutorial.

      Para saber mais sobre a construção de um aplicativo com o Django, você pode seguir as séries de desenvolvimento do Django. Você também pode considerar os documentos oficiais do Django.



      Source link