Introducción
Las personas utilizan diversos dispositivos para conectarse a Internet y navegar por la web. Por ello, las aplicaciones deben ser accesibles desde diversas ubicaciones. Para los sitios web tradicionales, una interfaz de usuario adaptable suele ser suficiente, pero las aplicaciones más complejas suelen requerir otras técnicas y arquitecturas. Estas incluyen aplicaciones REST back-end y front-end independientes que puedan ejecutarse como aplicaciones web del lado del cliente, aplicaciones web progresivas (PWA) o aplicaciones móviles nativas.
Algunas herramientas que puedes utilizar al crear aplicaciones más complejas incluyen:
- React es un marco de JavaScript que permite a los desarrolladores crear páginas web y nativas para sus backends de API REST.
- Django es un marco web Python gratuito y de código abierto que sigue el patrón de arquitectura de software Modelo-Vista-Controlador (MVC).
- Django REST Framework, un kit de herramientas potente y flexible para crear API REST en Django.
En este tutorial, crearás una aplicación web moderna con un backend y un frontend de API REST independientes usando React, Django y el framework REST de Django. Al usar React con Django, podrás aprovechar los últimos avances en JavaScript y desarrollo frontend. En lugar de crear una aplicación Django con un motor de plantillas integrado, usarás React como una biblioteca de interfaz de usuario que utiliza un Modelo de Objetos de Documento (DOM) virtual, un enfoque declarativo y componentes que procesan rápidamente los cambios en los datos.
La aplicación web que crearás almacenará los registros de clientes en una base de datos y podrás usarla como punto de partida para una aplicación CRM. Al finalizar, podrás leer, actualizar y eliminar datos mediante una interfaz React diseñada con Bootstrap 4.
Requisitos previos
Para completar este tutorial, necesitarás:
- Una máquina de desarrollo con Ubuntu 18.04.
- Python 3, pip y venv se instalan en su máquina siguiendo los pasos 1 y 2 de Cómo instalar Python 3 y configurar un entorno de desarrollo local en Ubuntu 18.04.
- Tiene instalados Node.js 6+ y npm 5.2 o superior en su equipo. Puede instalar ambos siguiendo las instrucciones de "Cómo instalar Node.js en Ubuntu 18.04" en "Instalar desde PPA".
Paso 1: crear un entorno virtual de Python e instalar dependencias
En este paso, creamos un entorno virtual e instalamos las dependencias necesarias para nuestra aplicación, incluido Django, el marco REST de Django y django-cors-headers.
Nuestra aplicación utilizará dos servidores de desarrollo diferentes para Django y React. Se ejecutarán en puertos diferentes y actuarán como dos dominios separados. Por ello, necesitamos habilitar el uso compartido de recursos entre servidores (CORS) para enviar solicitudes HTTP de React a Django sin que el navegador las bloquee.
Vaya a su directorio de inicio y cree un entorno virtual utilizando el módulo venv de Python 3:
cd ~
python3 -m venv ./envActiva el entorno virtual creado utilizando la fuente:
source env/bin/activateA continuación, instale las dependencias del proyecto con pip. Estas incluyen lo siguiente:
- Django: Framework web para el proyecto.
- Django REST Framework: una aplicación de terceros que crea API REST con Django.
- django-cors-headers: Un paquete que habilita CORS.
Instalar el framework Django:
pip install django djangorestframework django-cors-headersAl instalar las dependencias del proyecto, puedes crear un proyecto Django y un frontend React.
Paso 2: Crear un proyecto Django
En este paso, generamos el proyecto Django utilizando los siguientes comandos y herramientas:
django-admin startproject-name: django-admin es una herramienta de línea de comandos que permite realizar tareas con Django. El comando startproject crea un nuevo proyecto de Django.
python manager.py startapp myapp: manager.py es un script de utilidad que se añade automáticamente a cada proyecto de Django y realiza diversas tareas administrativas: crear nuevas aplicaciones, migrar bases de datos y servir el proyecto localmente. Su comando startapp crea una aplicación de Django dentro del proyecto. En Django, el término "aplicación" describe un paquete de Python que proporciona un conjunto de características a un proyecto.
Para empezar, crea un proyecto de Django con django-admin startproject. Lo llamaremos djangoreactproject.
django-admin startproject djangoreactprojectAntes de continuar, veamos la estructura de directorios del proyecto Django usando el comando tree.
Vaya a la carpeta djangoreactproject en la raíz de su proyecto y ejecute el comando tree:
cd ~/djangoreactproject
treeVerá el siguiente resultado:
Output
├── djangoreactproject
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.pyLa carpeta ~/djangoreactproject es la raíz del proyecto. En esta carpeta se encuentran varios archivos importantes para su trabajo:
- manager.py: un script de utilidad que realiza una serie de tareas administrativas.
- settings.py: El archivo de configuración principal de un proyecto de Django, donde se pueden modificar las opciones del proyecto. Estas opciones incluyen variables como INSTALLED_APPS, una lista de cadenas que determinan las aplicaciones activas del proyecto. La documentación de Django contiene más información sobre las opciones disponibles.
- urls.py: Este archivo contiene una lista de patrones de URL y vistas asociadas. Cada patrón relaciona una URL con la función que debe llamarse para dicha URL. Para más información sobre URL y vistas, consulta nuestro tutorial sobre cómo crear vistas de Django.
Nuestro primer paso para trabajar con el proyecto es configurar los paquetes que instalamos en el paso anterior, incluyendo el framework REST de Django y el paquete CORS de Django, añadiéndolos a settings.py. Abra el archivo con nano o su editor preferido:
nano ~/djangoreactproject/djangoreactproject/settings.pyVaya a la configuración de INSTALLED_APPS y agregue las aplicaciones rest_framework y corsheaders al final de la lista:
...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'corsheaders'
]A continuación, agregue el middleware corsheaders.middleware.CorsMiddleware del paquete CORS previamente instalado a la configuración de MIDDLEWARE. Esta configuración es una lista de middleware, una clase de Python que contiene el código que se procesa cada vez que su aplicación web realiza una solicitud o respuesta:
...
MIDDLEWARE = [
...
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware'
]A continuación, puede habilitar CORS. La opción CORS_ORIGIN_ALLOW_ALL especifica si desea habilitar CORS para todos los dominios, y CORS_ORIGIN_WHITELIST es una tupla de Python que contiene las URL permitidas. En nuestro caso, dado que el servidor de desarrollo de React se ejecutará en http://localhost:3000, agregaremos las nuevas opciones CORS_ORIGIN_ALLOW_ALL = False y CORS_ORIGIN_WHITELIST('localhost:3000',) a nuestro archivo settings.py. Agregue estas opciones en cualquier parte del archivo:
...
CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = (
'localhost:3000',
)
...Puede encontrar más opciones de configuración en la documentación de django-cors-headers.
Guarde el archivo y salga del editor cuando haya terminado.
Aún en el directorio ~/djangoreactproject, crea una nueva aplicación Django llamada clientes:
python manage.py startapp customersEsto incluye modelos y vistas de gestión de clientes. Los modelos definen los campos y comportamientos de los datos de nuestra aplicación, mientras que las vistas permiten que nuestra aplicación gestione correctamente las solicitudes web y devuelva las respuestas requeridas.
A continuación, añade esta aplicación a la lista de aplicaciones instaladas en el archivo settings.py de tu proyecto para que Django la reconozca como parte del proyecto. Vuelve a abrir settings.py:
nano ~/djangoreactproject/djangoreactproject/settings.pyAgregue la aplicación del cliente:
...
INSTALLED_APPS = [
...
'rest_framework',
'corsheaders',
'customers'
]
...Luego, migre la base de datos e inicie el servidor de desarrollo local. Las migraciones son la forma en que Django propaga los cambios que realiza en sus modelos al esquema de su base de datos. Estos cambios pueden incluir, por ejemplo, añadir un campo o eliminar un modelo. Para más información sobre modelos y migraciones, consulte Cómo crear modelos de Django.
Transferencia de base de datos:
python manage.py migrateInicie el servidor de desarrollo local:
python manage.py runserverVerá un resultado similar al siguiente:
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.Su aplicación web se ejecutará desde http://127.0.0.1:8000. Si accede a esta dirección en su navegador, debería ver la siguiente página:
En este punto, deja el programa en ejecución y abre una nueva terminal para continuar desarrollando el proyecto.
Paso 3: Crear una interfaz de React
En esta sección, vamos a crear la aplicación front-end de nuestro proyecto usando React.
React cuenta con una herramienta oficial que permite generar proyectos React rápidamente sin tener que configurar Webpack directamente. Webpack es un empaquetador de módulos que se utiliza para agrupar recursos web como código JavaScript, CSS e imágenes. Normalmente, se necesitarían varias opciones de configuración antes de poder usar Webpack, pero gracias a la herramienta Create-react-app, no es necesario lidiar con Webpack directamente a menos que se necesite más control. Para ejecutar Create-react-app, se puede usar npx, una herramienta que ejecuta binarios empaquetados en npm.
En tu segunda terminal, asegúrate de estar en el directorio de tu proyecto:
cd ~/djangoreactprojectCrea un proyecto React llamado frontend usando create-react-app y npx:
npx create-react-app frontendA continuación, navegue dentro de su aplicación React e inicie el servidor de desarrollo:
cd ~/djangoreactproject/frontend
npm startSu aplicación se ejecutará desde http://localhost:3000/:
Deje el servidor de desarrollo React en ejecución y abra otra ventana de terminal para continuar.
Para ver la estructura de directorios de todo el proyecto en este punto, vaya a la carpeta raíz y ejecute el árbol nuevamente:
cd ~/djangoreactproject
treeVerás una estructura como esta:
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.pyNuestra aplicación usa Bootstrap 4 para diseñar la interfaz de React, así que la incluiremos en el archivo frontend/src/App.css, que gestionará la configuración de CSS. Abra el archivo:
nano ~/djangoreactproject/frontend/src/App.cssAgregue la siguiente entrada al principio del archivo. Puede eliminar el contenido existente, aunque no es necesario:
@import 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';Aquí, @import es una directiva CSS utilizada para importar reglas de estilo de otras hojas de estilo.
Ahora que hemos creado las aplicaciones back-end y front-end, creemos el modelo de cliente y algunos datos de demostración.
Paso 4: Crear el modelo de cliente y los datos iniciales
Tras crear la aplicación Django y la instancia de React, el siguiente paso será crear el modelo de cliente, que representa la tabla de la base de datos que contiene la información del cliente. No se necesita SQL, ya que el Mapeador Relacional de Objetos (ORM) de Django realiza operaciones de base de datos mapeando clases y variables de Python a tablas y columnas de SQL. De esta forma, el ORM de Django abstrae las interacciones de SQL con la base de datos mediante una interfaz de Python.
Vuelva a habilitar su entorno virtual:
cd ~
source env/bin/activateVaya al directorio de clientes y abra models.py, un archivo de Python que contiene los modelos de su aplicación:
cd ~/djangoreactproject/customers/
nano models.pyEl archivo contendrá el siguiente contenido:
from django.db import models
# Create your models here.La API del modelo Customer ya está incluida en el archivo gracias a la instrucción import import models from django.db. Ahora agregará la clase Customer, que extiende models.Model. Cada modelo en Django es una clase de Python que extiende django.db.models.Model.
El modelo Cliente tendrá estos campos de base de datos:
nombre de pila– Nombre del cliente.apellido– Apellido del cliente.CORREO ELECTRÓNICO– Dirección de correo electrónico del cliente.teléfono– Número de teléfono del cliente.DIRECCIÓN– Dirección del cliente.DESCRIPCIÓN– Descripción del cliente.crearEn– La fecha en la que se agrega el cliente.
También añadimos la función __str__(), que especifica cómo se mostrará el modelo. En nuestro caso, será el nombre del cliente. Para más información sobre la creación de clases y la definición de objetos, consulta Cómo crear clases y definir objetos en Python 3.
Añade el siguiente código al archivo:
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_nameA continuación, migre la base de datos para crear las tablas. El comando makemigrations crea los archivos de migración donde se agregan los cambios del modelo y la función "migrar" aplica los cambios de los archivos de migración a la base de datos.
Regrese a la carpeta raíz del proyecto:
cd ~/djangoreactprojectPara crear archivos de migración, ejecute lo siguiente:
python manage.py makemigrationsObtendrás un resultado como este:
Output
customers/migrations/0001_initial.py
- Create model CustomerAplicar estos cambios a la base de datos:
python manage.py migrateVerá un resultado que indica que la transferencia fue exitosa:
Output
Operations to perform:
Apply all migrations: admin, auth, contenttypes, customers, sessions
Running migrations:
Applying customers.0001_initial... OKA continuación, usará un archivo de transferencia de datos para crear los datos iniciales del cliente. Un archivo de transferencia de datos es una migración que añade o modifica datos a la base de datos. Cree un archivo de transferencia de datos vacío para la aplicación Clientes:
python manage.py makemigrations --empty --name customers customersVerá la siguiente confirmación con el nombre de su archivo de migración:
Output
Migrations for 'customers':
customers/migrations/0002_customers.pyTenga en cuenta que el nombre de su archivo de migración es 0002_customers.py.
A continuación, navegue a la carpeta de migraciones de la aplicación cliente:
cd ~/djangoreactproject/customers/migrationsAbra el archivo de migración creado:
nano 0002_customers.pyEste es el contenido inicial del archivo:
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('customers', '0001_initial'),
]
operations = [
]La declaración de importación importa la API de Migraciones, una API de Django para crear migraciones, desde django.db, un paquete integrado que contiene clases para trabajar con bases de datos.
La clase Migration es una clase de Python que describe las operaciones que se realizan al migrar una base de datos. Extiende migrations.Migration y tiene dos listas:
dependencias:Incluye migraciones dependientes.operaciones:Contiene operaciones que se ejecutan mediante la aplicación de migraciones.
A continuación, agregue un método para crear los datos del cliente de prueba. Agregue el siguiente método antes de definir la clase Migración:
...
def create_data(apps, schema_editor):
Customer = apps.get_model('customers', 'Customer')
Customer(first_name="Customer 001", last_name="Customer 001", email="[email protected]", phone="00000000", address="Customer 000 Address", description= "Customer 001 description").save()
...En este método, tomamos la clase Cliente de nuestra aplicación de cliente y creamos un cliente de prueba para insertar en la base de datos.
Para obtener la clase Cliente, que nos permite crear nuevos clientes, usamos el método get_model() del objeto apps. Este objeto representa el registro de las aplicaciones instaladas y sus modelos de base de datos.
Al usar el método RunPython() para ejecutar create_data(), se pasa el objeto apps. Agregue el método migrations.RunPython() a la lista de operaciones vacía:
...
operations = [
migrations.RunPython(create_data),
]RunPython() forma parte de la API de Migraciones y permite ejecutar código Python personalizado durante una migración. Nuestra lista de operaciones especifica que este método se ejecutará al aplicar la migración.
Aquí está el archivo completo:
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="[email protected]", 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 obtener más información sobre la migración de datos, consulte la documentación de Migración de datos de Django.
Para mover su base de datos, primero regrese a la carpeta principal de su proyecto:
cd ~/djangoreactprojectMigre su base de datos para crear datos de prueba:
python manage.py migrateVerá un resultado que confirma la migración:
Output
Operations to perform:
Apply all migrations: admin, auth, contenttypes, customers, sessions
Running migrations:
Applying customers.0002_customers... OKCon el modelo de cliente y los datos de representación creados, podemos pasar a construir la API REST.
Paso 5: Crear API REST
En este paso, crearemos una API REST con el framework REST de Django. Crearemos varias vistas de API diferentes. Una vista de API es una función que gestiona una solicitud o llamada de API, mientras que un punto final de API es una URL única que representa un punto de contacto con el sistema REST. Por ejemplo, cuando un usuario envía una solicitud GET a un punto final de API, Django llamará a la función o vista de API correspondiente para gestionar la solicitud y devolver los posibles resultados.
También usaremos serializadores. Un serializador en el framework REST de Django permite convertir instancias de modelos complejos y QuerySets a formato JSON para su uso en la API. La clase serializadora también puede funcionar en sentido inverso, proporcionando mecanismos para analizar y separar datos en modelos y QuerySets de Django.
Nuestros puntos finales de API incluirán:
- api/customers: este punto final se utiliza para crear clientes y devolver colecciones paginadas de clientes.
- api/clientes/ :Este punto final se utiliza para obtener, actualizar y eliminar clientes individuales por clave principal o ID.
También creamos URL en el archivo urls.py del proyecto para los puntos finales relevantes (es decir, api/customers y api/customers/ ).
Comencemos creando la clase serializadora para nuestro modelo de cliente.
Agregar una clase serializadora
Crear una clase serializadora para nuestro modelo Cliente es esencial para convertir instancias de Cliente y QuerySets a y desde JSON. Para crear la clase serializadora, primero cree un archivo serializers.py dentro de la aplicación Clientes:
cd ~/djangoreactproject/customers/
nano serializers.pyAgregue el siguiente código para importar el modelo de cliente y serie de la API:
from rest_framework import serializers
from .models import CustomerA continuación, cree una clase serializadora que extienda Serializers.ModelSerializer y especifique los campos que se serializarán:
...
class CustomerSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = ('pk','first_name', 'last_name', 'email', 'phone','address','description')La clase Meta especifica el modelo y los campos para la serialización: pk, first_name, last_name, email, phone, address, description.
Aquí está el contenido completo del archivo:
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')Ahora que hemos creado nuestra clase serializadora, podemos agregar vistas API.
Agregar vistas de API
En esta sección, crearemos vistas API para nuestra aplicación que serán llamadas por Django cuando el usuario visite el punto final correspondiente a la función de vista.
~/djangoreactproject/clientes/vistas.py Abierto:
nano ~/djangoreactproject/customers/views.pyElimina los existentes y agrega las siguientes importaciones:
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 *Importamos la serialización que creamos, junto con el modelo de cliente y las API del marco REST de Django.
A continuación, agregue la vista para procesar solicitudes HTTP POST y GET:
...
@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)Primero, usamos el decorador @api_view(['GET', 'POST']) para crear una vista de API que acepte solicitudes GET y POST. Un decorador es una función que toma otra función y la extiende dinámicamente.
En el cuerpo del método, usamos la variable request.method para verificar el método HTTP actual y ejecutar la lógica adecuada según el tipo de solicitud:
- Si la solicitud es GET, este método pagina los datos mediante el Paginador de Django y devuelve la primera página de datos después de la serialización, el número de clientes disponibles, el número de páginas disponibles y enlaces a las páginas anterior y siguiente. El Paginador es una clase integrada de Django que pagina una lista de datos en páginas y proporciona métodos para acceder a los elementos de cada página.
- Si la solicitud es un POST, el método serializa los datos del cliente recibidos y luego llama al método save() del objeto serializador. A continuación, devuelve un objeto Response, una instancia de HttpResponse, con un código de estado de 201. Cada perfil creado es responsable de devolver un objeto HttpResponse. El método save() guarda los datos serializados en la base de datos.
Para obtener más información sobre HttpResponse y las vistas, consulte esta discusión sobre la creación de funciones de vista.
Ahora agregue la vista API que es responsable de procesar solicitudes GET, PUT y DELETE para obtener, actualizar y eliminar clientes por pk (clave principal):
...
@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)Este método está decorado con @api_view(['GET', 'PUT', 'DELETE']) para indicar que es una vista API que puede aceptar solicitudes GET, PUT y DELETE.
La verificación en el campo request.method verifica el método de solicitud e invoca la lógica correcta según su valor:
- Si la solicitud es GET, los datos del cliente se serializan y se envían utilizando un objeto Respuesta.
- Si se trata de una solicitud PUT, este método crea un serializador para los nuevos datos del cliente. A continuación, el método save() llama al objeto serializador creado. Finalmente, envía un objeto Response con el cliente actualizado.
- Si se trata de una solicitud DELETE, el método delete() llama al método delete() del objeto cliente para eliminarlo y luego devuelve un objeto Response sin datos.
El archivo completo se ve así:
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)Ahora podemos continuar creando nuestros puntos finales.
Agregar puntos finales de API
Ahora crearemos los puntos finales de la API: api/customers/, para consultar y crear clientes, y api/customers/ , para obtener, actualizar o eliminar clientes individuales por su pk.
Abra ~/djangoreactproject/djangoreactproject/urls.py:
nano ~/djangoreactproject/djangoreactproject/urls.pyDeje lo que está allí, pero agregue la importación a la vista Clientes en la parte superior del archivo:
from django.contrib import admin
from django.urls import path
from customers import views
from django.conf.urls import urlLuego, las URL api/customers/ y api/customers/ Agregar a la lista de patrones de URL que contienen las URL de la aplicación:
...
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^api/customers/$', views.customers_list),
url(r'^api/customers/(?P<pk>[0-9]+)$', views.customers_detail),
]Con los puntos finales REST creados, veamos cómo podemos consumirlos.
Paso 6: Consumo de API REST con Axios
En este paso, instalaremos Axios, el cliente HTTP que usaremos para realizar llamadas a la API. También crearemos una clase para consumir los puntos finales de la API que hemos creado.
Primero, desactive su entorno virtual:
deactivateLuego ve a tu carpeta frontend:
cd ~/djangoreactproject/frontendInstalar axios desde npm usando:
npm install axios --saveLa opción –save agrega la dependencia de axios al archivo package.json de su aplicación.
A continuación, cree un archivo JavaScript llamado CustomersService.js que contenga el código para llamar a las API REST. Lo crearemos en la carpeta src, donde se encuentra el código de la aplicación de nuestro proyecto:
cd src
nano CustomersService.jsAgregue el siguiente código, que contiene métodos para conectarse a la API REST de Django:
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);
}
}La clase CustomersService llama a los siguientes métodos de Axios:
- getCustomers(): Obtiene la primera página de clientes.
- getCustomersByURL(): Obtiene clientes por URL. Esto permite acceder a las páginas de clientes posteriores mediante enlaces como /api/customers/?page=2.
- getCustomer(): obtiene el cliente por clave principal.
- createCustomer(): crea un cliente.
- updateCustomer(): actualiza un cliente.
- deleteCustomer(): elimina un cliente.
Ahora podemos mostrar nuestros datos de API en la interfaz de usuario de React creando un componente CustomersList.
Paso 7: Visualización de datos de la API en la aplicación React
En este paso, crearemos el componente CustomersList de React. Un componente de React representa una parte de la interfaz de usuario. También permite dividir la interfaz en partes independientes y reutilizables.
Comience creando CustomersList.js en frontend/src:
nano ~/djangoreactproject/frontend/src/CustomersList.jsComience importando React y Component para crear un componente React:
import React, { Component } from 'react';A continuación, importe y cree una instancia del módulo CustomersService que creó en el paso anterior, que proporciona métodos que se comunican con la API REST del backend:
...
import CustomersService from './CustomersService';
const customersService = new CustomersService();A continuación, cree un componente CustomersList que extienda Component para llamar a la API REST. Un componente de React debe extender o subclasificar la clase Component. Para más información sobre las clases E6 y la herencia, consulte nuestro tutorial sobre cómo comprender las clases en JavaScript.
Agregue el siguiente código para crear un componente React que extienda react.Component:
...
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 del constructor, inicializamos el objeto de estado. Este contiene las variables de estado de nuestro componente mediante un array vacío de clientes. Este array contiene los clientes y nextPageURL, que contiene la URL de la siguiente página que se recuperará de la API de respaldo. También adjuntamos los métodos nextPage() y handleDelete() para que sean accesibles desde el código HTML.
A continuación, agregue un método componentDidMount() y una llamada a getCustomers() en la clase CustomersList, antes de la llave de cierre.
El método componentDidMount() es un método de ciclo de vida del componente que se llama cuando el componente se crea y se inserta en el DOM. getCustomers() llama al objeto Customer Service para obtener la primera página de datos y el enlace de la página siguiente desde el backend de Django:
...
componentDidMount() {
var self = this;
customersService.getCustomers().then(function (result) {
self.setState({ customers: result.data, nextPageURL: result.nextlink})
});
}Ahora agregue el método handleDelete(), que maneja la eliminación del cliente, debajo de componentDidMount():
...
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})
});
}El método handleDelete llama al método deleteCustomer() para eliminar al cliente mediante la clave primaria (pk). Si la operación se realiza correctamente, se filtra la matriz de clientes para el cliente eliminado.
A continuación, agregue un método nextPage() para obtener los datos de la página siguiente y actualizar el enlace de la página siguiente:
...
nextPage(){
var self = this;
customersService.getCustomersByURL(this.state.nextPageURL).then((result) => {
self.setState({ customers: result.data, nextPageURL: result.nextlink})
});
}El método nextPage() llama a un método getCustomersByURL() que obtiene la URL de la siguiente página del objeto de estado, this.state.nextPageURL, y actualiza la matriz de clientes con los datos devueltos.
Por último, agregue el método render() del componente, que representa una tabla de clientes a partir del estado del componente:
...
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>
);
}Aquí está el contenido completo del archivo:
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;Ahora que hemos creado el componente CustomersList para mostrar la lista de clientes, podemos agregar un componente que maneje la creación y actualización de clientes.
Paso 8: Agregar el componente React Create y actualizar el cliente
En este paso, creamos el componente CustomerCreateUpdate, que gestiona la creación y actualización de clientes. Para ello, proporciona un formulario que los usuarios pueden usar para introducir nuevos datos de clientes o actualizar las entradas existentes.
En frontend/src, cree un archivo CustomerCreateUpdate.js:
nano ~/djangoreactproject/frontend/src/CustomerCreateUpdate.jsAgregue el siguiente código para crear un componente React importando React y Component:
import React, { Component } from 'react';También podemos importar e instanciar la clase CustomersService que creamos en el paso anterior, que proporciona métodos que se comunican con la API REST del backend:
...
import CustomersService from './CustomersService';
const customersService = new CustomersService();A continuación, cree un componente CustomerCreateUpdate que extienda Component para crear y actualizar clientes:
...
class CustomerCreateUpdate extends Component {
constructor(props) {
super(props);
}
}
export default CustomerCreateUpdate;En la definición de clase, agregue el método render() del componente, que representa un formulario HTML que captura información del cliente:
...
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 de entrada del formulario, el método agrega un atributo ref para acceder y establecer el valor del elemento del formulario.
A continuación, defina un método handleSubmit(event) encima del método render() para tener el comportamiento apropiado cuando el usuario haga clic en el botón enviar:
...
handleSubmit(event) {
const { match: { params } } = this.props;
if(params && params.pk){
this.handleUpdate(params.pk);
}
else
{
this.handleCreate();
}
event.preventDefault();
}
...El método handleSubmit(event) gestiona el envío del formulario y, según la ruta, llama al método handleUpdate(pk) para actualizar el cliente con el pk enviado o al método handleCreate() para crear un nuevo cliente. Definiremos estos métodos en breve.
De vuelta en el constructor del componente, vincule el método handleSubmit() recién agregado a este para poder acceder a él en su formulario:
...
class CustomerCreateUpdate extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
...Luego, defina el método handleCreate() para crear el cliente a partir de los datos del formulario. Agregue el siguiente código sobre el método handleSubmit(event):
...
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.');
});
}
...El método handleCreate() se utiliza para crear un cliente a partir de los datos de entrada. Llama al método CustomersService.createCustomer() correspondiente, que a su vez llama a la API del backend para crear el cliente.
Luego, debajo del método handleCreate, defina el método handleUpdate(pk) para implementar actualizaciones:
...
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.');
});
}El método updateCustomer() actualiza un cliente con la nueva información del formulario de información del cliente. Llama al método customersService.updateCustomer().
A continuación, agregue el método componentDidMount(). Si el usuario visita la ruta customer/:pk, queremos rellenar el formulario con la información del cliente usando la clave principal de la URL. Para ello, podemos agregar el método getCustomer(pk) después de montar el componente en el evento del ciclo de vida componentDidMount(). Para agregar este método, agregue el siguiente código debajo del constructor del componente:
...
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;
})
}
}Aquí está el contenido completo del archivo:
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;Al crear el componente CustomerCreateUpdate, podemos actualizar el componente principal de la aplicación para agregar enlaces a los distintos componentes que hemos creado.
Paso 9 – Actualizar el componente principal de la aplicación
En esta sección, actualizaremos nuestro componente de aplicación para vincularlo a los componentes que creamos en los pasos anteriores.
Desde la carpeta frontend, ejecute el siguiente comando para instalar el enrutador React, que le permite agregar enrutamiento y navegación entre diferentes componentes React:
cd ~/djangoreactproject/frontend
npm install --save react-router-domA continuación, abra ~/djangoreactproject/frontend/src/App.js:
nano ~/djangoreactproject/frontend/src/App.jsElimine todos los elementos existentes y agregue el siguiente código para importar las clases necesarias para agregar el enrutamiento. Estas son BrowserRouter, que crea un componente Router, y Route, que crea un componente Route:
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';BrowserRouter mantiene la interfaz de usuario sincronizada con la URL mediante la API de historial HTML5.
A continuación, cree un diseño base que proporcione el componente base que será envuelto por el componente BrowserRouter:
...
const BaseLayout = () => (
<div className="container-fluid">
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<a className="navbar-brand" href="#">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="/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 el componente Ruta para definir las rutas de nuestra aplicación. Este componente debe ser cargado por el enrutador al encontrar una coincidencia. Cada ruta requiere una ruta para especificar la ruta que se va a buscar y un componente para especificar el componente que se va a buscar. El atributo exacto indica al enrutador que debe buscar la ruta con exactitud.
Por último, crea el componente App, el componente principal o de nivel superior de nuestra aplicación React:
...
class App extends Component {
render() {
return (
<BrowserRouter>
<BaseLayout/>
</BrowserRouter>
);
}
}
export default App;Hemos envuelto el componente BaseLayout con el componente BrowserRouter porque nuestra aplicación se ejecutará en el navegador.
El archivo completo se ve así:
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="#">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="/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;Después de añadir el enrutamiento a nuestra aplicación, estamos listos para probarla. Vaya a http://localhost:3000. Debería ver la página de inicio de la aplicación:
Con este programa, ahora tienes la base para un programa de CRM.
Resultado
En este tutorial, creaste una aplicación de demostración con Django y React. Usaste el framework REST de Django para crear la API REST, Axios para consumir la API y Bootstrap 4 para el estilo CSS. Puedes encontrar el código fuente de este proyecto en este repositorio de GitHub.












