El autor seleccionó Open Sourcing Mental Illness Ltd para recibir una donación como parte del programa Write for Donations.
Las personas utilizan diferentes tipos de dispositivos para conectarse a internet y navegar por la Web. Debido a esto, las aplicaciones deben ser accesibles desde varios lugares. Para los sitios web tradicionales, tener una IU receptiva suele ser suficiente, pero las aplicaciones más complejas suelen requerir el uso de otras técnicas y arquitecturas. Entre ellas se contempla tener aplicaciones back-end y front-end REST independientes que puedan implementarse como aplicaciones web para el cliente, aplicaciones web progresivas (PWA) o aplicaciones móviles nativas.
Algunas herramientas que puede utilizar al crear aplicaciones más complejas incluyen:
En este tutorial, usted creará una aplicación web moderna con un backend REST API independiente y un frontend utilizando React, Django y Django REST Framework. Al utilizar React con Django, podrá beneficiarse de los últimos avances en el desarrollo de JavaScript y front-end. En lugar de crear una aplicación de Django que utilice un motor de plantillas incorporado, usted utilizará React como biblioteca de IU, aprovechando su enfoque virtual de Modelo de objetos de documentos (DOM), enfoque declarativo y componentes que rápidamente reproduzcan cambios en los datos.
La aplicación web que usted creará almacena registros sobre clientes en una base de datos, y puede utilizarlo como punto de partida para una aplicación CRM. Cuando haya terminado, podrá crear, leer, actualizar y borrar registros utilizando una interfaz React de estilo Bootstrap 4.
Para completar este tutorial, necesitará lo siguiente:
pip
y venv
instalados en su equipo siguiendo los Pasos 1 y 2 de Cómo instalar Python 3 y Configurar un entorno de programación local en Ubuntu 18.04.npm
5.2 o superior instalados en su equipo. Puede instalar ambos siguiendo las instrucciones en Cómo instalar Node.js en Ubuntu 18.04 al instalar una PPA.En este paso, crearemos un entorno virtual e instalaremos las dependencias necesarias para nuestra aplicación, entre ellas, Django, Django REST framework y django-cors-headers
.
Nuestra aplicación utilizará dos servidores de desarrollo distintos para Django y React. Se ejecutarán en diferentes puertos y funcionarán como dos dominios separados. Debido a esto, debemos permitir el intercambio de recursos de origen cruzado (CORS) para enviar solicitudes HTTP desde React a Django sin que el navegador pueda bloquearlos.
Navegue a su directorio principal y cree un entorno virtual utilizando el módulo venv
Python 3:
- cd ~
- python3 -m venv ./env
Active el entorno virtual creado utilizando ``source:
- source env/bin/activate
A continuación, instale las dependencias del proyecto con pip
. Entre ellas se incluyen:
django-cors-headers
: un paquete que habilita CORS.Instalar la estructura de Django:
- pip install django djangorestframework django-cors-headers
Con las dependencias del proyecto instaladas, puede crear el proyecto Django y el frontend de React.
En este paso, generaremos el proyecto Django utilizando las siguientes comandos y utilidades:
**django-admin startproject project-name**
:django-admin
es una utilidad de línea de comandos que se utiliza para realizar tareas con Django. El comando startproject
crea un nuevo proyecto Django.
**python manage.py startapp myapp**
: manage.py
es un script de utilidad que se añade automáticamente a cada proyecto de Django, y que ejecuta varias tareas administrativas, como crear nuevas aplicaciones, migrar la base de datos y servir de forma local el proyecto de Django. Su comando startapp
crea una aplicación de Django dentro del proyecto Django. En Django, el término aplicación describe un paquete de Python que proporciona algunas características en un proyecto.
Para comenzar, cree el proyecto Django con django-admin startproject
. Hemos de nombrar a nuestro proyecto djangoreactproject
- django-admin startproject djangoreactproject
Antes de seguir, observemos la estructura del directorio de nuestro proyecto Django utilizando el comando tree
.
Nota: tree
es un comando útil para visualizar estructuras de archivos y directorio desde la línea de comandos. Puede instalarlo con el comando que se indica a continuación:
- sudo apt-get install tree
Para utilizarlo, use el comando cd
en el directorio que desee y escriba tree
o proporcione la ruta al punto de partida con tree /home/sammy/sammys-project
.
Navegue a la carpeta djangoreactproject
dentro del root de su proyecto y ejecute el comando tree
:
- cd ~/djangoreactproject
- tree
Verá lo siguiente:
Output├── djangoreactproject
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
La carpeta ~/djangoreactproject
es la root del proyecto. Dentro de esta carpeta, hay varios archivos que serán importantes para su labor:
manage.py
: el script de utilidad que realiza diversas tareas administrativas.settings.py
: el archivo de configuración principal para el proyecto de Django, donde puede modificar la configuración del proyecto. Estos ajustes incluyen variables como INSTALLED_APPS
, una lista de strings que designan las aplicaciones habilitadas para su proyecto. La documentación de Django contiene más información sobre los ajustes disponibles.urls.py
: este archivo contiene una lista de patrones de URL y vistas relacionadas. Cada patrón traza una conexión entre una URL y la función que se debe solicitar para esa URL. Para más información sobre URL y vistas, consulte nuestro tutorial sobre Cómo crear vistas de Django.Nuestro primer paso en el trabajo con el proyecto será configurar los paquetes que instalamos en el paso anterior, incluidos Django REST framework y el paquete Django CORS, al añadirlos a settings.py
. Abra el archivo con nano
o su editor favorito:
- nano ~/djangoreactproject/djangoreactproject/settings.py
Navegue al ajuste INSTALLED_APPS
y añada las aplicaciones rest_framework
y corsheaders
en la parte inferior 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, añada el middleware corsheaders.middleware.CorsMiddleware
desde el paquete CORS instalado previamente al ajuste MIDDLEWARE
. Este ajuste es una lista de middlewares, una clase de Python que contiene código que se procesa cada vez que su aplicación web gestiona una solicitud o respuesta:
...
MIDDLEWARE = [
...
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware'
]
A continuación, puede habilitar CORS. El ajuste CORS_ORIGIN_ALLOW_ALL
especifica si desea habilitar CORS para todos los dominios o no, y CORS_ORIGIN_WHITELIST
es una tupla de Python que contiene URL permitidas. En nuestro caso, dado que el servidor de desarrollo de React se ejecutará en http://localhost:3000
, añadiremos nuevos ajustes CORS_ORIGIN_ALLOW_ALL = False
y CORS_ORIGIN_WHITELIST('localhost:3000',)
a nuestro archivo settings.py
. Añade estos ajustes 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
, cree una nueva aplicación de Django llamada customers
:
- python manage.py startapp customers
Contendrá los modelos y las vistas para gestionar clientes. Los modelos definen los campos y los comportamientos de nuestros datos de aplicaciones, mientras que las vistas permiten a nuestras aplicaciones gestionar adecuadamente las solicitudes web y devolver las respuestas requeridas.
A continuación, añada esta aplicación a la lista de aplicaciones instaladas en el archivo settings.py
de su proyecto para que Django la reconozca como parte del proyecto. Abra settings.py
de nuevo:
- nano ~/djangoreactproject/djangoreactproject/settings.py
Añada la aplicación customers
:
...
INSTALLED_APPS = [
...
'rest_framework',
'corsheaders',
'customers'
]
...
A continuación, *migre *la base de datos e inicie el servidor de desarrollo local. Las migraciones son la manera en que Django propaga los cambios que usted realiza a sus modelos en su esquema de base de datos. Estos cambios pueden ser, por ejemplo, añadir un campo o eliminar un modelo. Para obtener más información sobre modelos y migraciones, consulte Cómo crear modelos de Django.
Migre la base de datos:
- python manage.py migrate
Inicie el servidor de desarrollo local:
- python manage.py runserver
El resultado debe ser similar a lo siguiente:
OutputPerforming 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 ejecuta desde http://127.0.0.1:8000
. Si se dirige a esta dirección en su navegador web, debería ver la siguiente página:
En este momento, deje la aplicación en ejecución y abra una nueva terminal para seguir desarrollando el proyecto.
En esta sección, vamos a crear la aplicación frontend de nuestro proyecto utilizando React.
React tiene una herramienta oficial que le permite generar proyectos de React de forma rápida sin tener que configurar Webpack directamente. Webpack es un empaquetador de módulos que se utiliza para agrupar recursos web, como código de JavaScript, CSS e imágenes. Normalmente, para poder utilizar Webpack, debe establecer varias opciones de configuración, pero, gracias a la herramienta create-react-app
, no tiene que lidiar con Webpack directamente hasta que decida que necesita más control. Para ejecutar create-react-app
puede utilizar npx, una herramienta que ejecuta binarios de paquetes npm
.
En su segunda terminal, asegúrese de que estar en el directorio de su proyecto:
- cd ~/djangoreactproject
Cree un proyecto de React llamado frontend
utilizando create-react-app
y npx
- npx create-react-app frontend
A continuación, navegue al interior de su aplicación de React e inicie el servidor de desarrollo:
- cd ~/djangoreactproject/frontend
- npm start
Su aplicación se ejecutará desde http://localhost:3000/
:
Deje el servidor de desarrollo de React en ejecución y abra otra ventana de terminal para proceder.
Para ver la estructura del directorio de todo el proyecto en este punto, navegue a la carpeta root y ejecute el comando tree
de nuevo:
- cd ~/djangoreactproject
- tree
Verá 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.py
Nuestra aplicación utilizará Bootstrap 4 para dar forma a la interfaz de React, por lo que lo incluiremos en el archivo frontend/src/App.css
que gestiona nuestros ajustes de CSS. Abra el archivo:
- nano ~/djangoreactproject/frontend/src/App.css
Añada la importación que se indica a continuación al comienzo del archivo. Puede eliminar el contenido existente del archivo, pero no es necesario hacerlo:
@import 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';
Aquí, @import
es una instrucción de CSS que se utiliza para importar reglas de estilo de otras hojas de estilo.
Ahora que hemos creado las aplicaciones back-end y front-end, vamos a crear el modelo de cliente y algunos datos de prueba.
Tras crear la aplicación de Django y el frontend de React, nuestro próximo paso será crear el modelo de cliente, que representa la tabla de base de datos que almacenará información sobre los clientes. No necesita nada de SQL, dado que el mapeo objeto-relacional (ORM) de Django se encargará de las operaciones de la base de datos al asignar las clases y variables de Python a tablas y columnas de SQL. De esta manera, el ORM de Django extrae las interacciones de SQL con la base de datos a través de una interfaz de Python.
Active su entorno virtual de nuevo:
- cd ~
- source env/bin/activate
Diríjase al directorio customers
y abra models.py
, un archivo de Python que contiene los modelos de su aplicación:
- cd ~/djangoreactproject/customers/
- nano models.py
El archivo incluirá el siguiente contenido:
from django.db import models
# Create your models here.
La API del modelo de cliente ya está importada en el archivo gracias a la instrucción import from django.db import models
. Ahora, añadirá la clase Customer
, que extiende models.Model
. Cada modelo de Django es una clase de Python que extiende django.db.models.Model
.
El modelo Customer
tendrá estos campos de base de datos:
first_name
: el nombre del cliente.last_name
: el apellido del cliente.email
: la dirección de correo electrónico del cliente.phone
: el número de teléfono del cliente.address
: la dirección del cliente.description
: la descripción del cliente.createdAt
: la fecha en que se añade el cliente.También añadiremos la función __str__()
, que define la manera en que se mostrará el modelo. En nuestro caso, será con el nombre del cliente. Para obtener más información sobre la creación de clases y la definición de objetos, consulte Cómo crear clases y definir objetos en Python 3.
Añada el código siguiente 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_name
A continuación, migre la base de datos para crear las tablas de la base de datos. El comando makemigrations
crea los archivos de migración en los que se añadirán los cambios al modelo, y migrate
aplica los cambios en los archivos de migraciones a la base de datos.
Vuelva a navegar a la carpeta root del proyecto:
- cd ~/djangoreactproject
Ejecute lo siguiente para crear los archivos de migración:
- python manage.py makemigrations
Verá algo similar a esto:
Outputcustomers/migrations/0001_initial.py
- Create model Customer
Aplique estos cambios en la base de datos:
- python manage.py migrate
Verá un resultado que indica que la migración se realizó correctamente:
OutputOperations to perform:
Apply all migrations: admin, auth, contenttypes, customers, sessions
Running migrations:
Applying customers.0001_initial... OK
A continuación, utilizará un archivo de migración de datos para crear datos iniciales de clientes. Un archivo de migración de datos es una migración que añade o altera datos en la base de datos. Cree un archivo de migración de datos vacío para la aplicación customers
:
- python manage.py makemigrations --empty --name customers customers
Visualizará la siguiente confirmación con el nombre de su archivo de migración:
OutputMigrations for 'customers':
customers/migrations/0002_customers.py
Tenga en cuenta que el nombre de su archivo de migración es 0002_customers.py
.
A continuación, navegue al interior de la carpeta de migraciones de la aplicación customers
:
- cd ~/djangoreactproject/customers/migrations
Abra el archivo de migración creado:
- nano 0002_customers.py
Este es el contenido inicial del archivo:
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('customers', '0001_initial'),
]
operations = [
]
La instrucción import importa la API de migraciones
, una API de Django para crear migraciones, desde django.db
, un paquete incorporado que contiene clases para trabajar con bases de datos.
La clase Migration
es una clase de Python que describe las operaciones que se ejecutan al migrar bases de datos. Esta clase extiende migrations.Migration
y tiene dos listas:
dependencies
: contiene las migraciones dependientes.operations
: contiene las operaciones que se ejecutarán al aplicar la migración.A continuación, añada un método para crear datos de clientes de prueba. Añada el método que se indica a continuación antes de la definición de la clase Migration
:
...
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()
...
En este método, estamos tomando la clase Customer
de nuestra aplicación customers
y creando un cliente de prueba para insertar en la base de datos.
Para obtener la clase Customer
, que permitirá la creación de nuevos clientes, usamos el método get_model()
del objeto apps
. El objeto apps
representa el registro de aplicaciones instaladas y sus modelos de base de datos.
El objeto apps
se pasará del método RunPython()
cuando lo usemos para ejecutar create_data()
. Añada el método migrations.RunPython()
a la lista operations
vacía:
...
operations = [
migrations.RunPython(create_data),
]
RunPython()
es parte de la API de Migrations que le permite ejecutar código de Python personalizado en una migración. Nuestra lista operations
especifica que este método se ejecutará al aplicar la migración.
Este es 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="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 obtener más información sobre migraciones de datos, consulte la documentación sobre migraciones de datos en Django
Para migrar su base de datos, primero, vuelva a navegar a la carpeta root de su proyecto:
- cd ~/djangoreactproject
Migre su base de datos para crear los datos de prueba:
- python manage.py migrate
Visualizará un resultado que confirma la migración:
OutputOperations to perform:
Apply all migrations: admin, auth, contenttypes, customers, sessions
Running migrations:
Applying customers.0002_customers... OK
Para obtener más detalles sobre este proceso, consulte Cómo crear modelos de Django.
Con el modelo Customer y los datos de prueba creados, podemos pasar a la creación de la API REST.
En este paso, vamos a crear la API REST utilizando Django REST Framework. 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 de final de API es una URL única que representa un punto de contacto con el sistema REST. Por ejemplo, cuando el usuario envía una solicitud de GET a un punto final de API, Django llama a la función o vista de API correspondiente para gestionar la solicitud y devolver los resultados posibles.
También utilizaremos serializadores. Un serializador de Django REST Framework permite que instancias de modelos complejas y QuerySets se conviertan en formato JSON para el consumo de API. La clase de serialización también puede funcionar en dirección inversa, proporcionando mecanismos para el análisis y la deserialización de datos en QuerySets y modelos de Django.
Nuestros puntos finales de API incluirán lo siguiente:
api/customers
: este punto final se utiliza para crear clientes y devuelve conjuntos paginados de clientes.api/clusters/<pk>
: este punto final se utiliza para obtener, actualizar y eliminar clientes individuales por clave primaria o id.También crearemos URL en el archivo urls.py
del proyecto para los puntos finales correspondientes (es decir, api/clusters
y api/clusters/<pk>
).
Comencemos por crear la clase de serialización para nuestro modelo Customer
.
Es necesario crear una clase de serialización para nuestro modelo Customer
para transformar las instancias de cliente y QuerySets hacia y desde JSON. Para crear la clase de serialización, primero, cree un archivo serializer.py
dentro de la aplicación customers
:
- cd ~/djangoreactproject/customers/
- nano serializers.py
Añada el código siguiente para importar el modelo Customer
y la API de serializadores:
from rest_framework import serializers
from .models import Customer
A continuación, cree una clase de serialización que extienda serializers.ModelSerializer
y especifique los campos que han de serializarse:
...
class CustomerSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = ('pk','first_name', 'last_name', 'email', 'phone','address','description')
La clase Meta
especifica qué campos y modelos se serializarán: pk
, first_name
, last_name
, email
, phone
, address
, description
.
Este es 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 de serialización, podemos añadir las vistas de API.
En esta sección, crearemos las vistas de API para nuestra aplicación, a las que llamará Django cuando el usuario visite el punto final correspondiente a la función de vista.
Abra ~/djangoreactproject/customers/views.py
:
- nano ~/djangoreactproject/customers/views.py
Elimine lo que haya allí y añada 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 *
Estamos importando el serializador que creamos, junto con el modelo Customer
y las API de Django y Django REST Framework.
A continuación, añada la vista para el procesamiento de POST y las solicitudes GET HTTP:
...
@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 pueda aceptar solicitudes GET y POST. Un decorador es una función que toma otra función y la extiende de forma dinámica.
En el cuerpo del método, usamos la variable request.method
para verificar el método HTTP actual y ejecutar la lógica correspondiente dependiendo del tipo de solicitud:
save()
del objeto serializador. A continuación, devuelve un objeto de Response, una instancia de HttpResponse, con el código de estado 201. Cada vista que crea se encarga de regresar 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 con respecto a la creación de funciones de vista.
Ahora, añada la vista de API que se encargará del procesamiento de las solicitudes GET, PUT y DELETE para obtener, actualizar y eliminar clientes por pk
(clave primaria):
...
@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)
El método está representado con @api_view(['GET', 'PUT', 'DELETE'])
para indicar que se trata de una vista de API que puede aceptar las solicitudes GET, PUT y DELETE.
La marca de revisión en el campo request.method
verifica el método de solicitud y, dependiendo de su valor, llama a la lógica correcta:
save()
del objeto serializador creado. Por último, envía un objeto Response con el cliente actualizado.delete()
del objeto de cliente para eliminarlo y, a continuación, devuelve un objeto Response sin datos.El archivo completo se ve de este modo:
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 pasar a crear nuestros puntos finales.
Ahora, crearemos los puntos finales de API: api/customers/
para consultar y crear clientes, y api/customers/ <pk>
para obtener, actualizar o eliminar clientes individuales por su pk
.
Abra ~/djangoreactproject/djangoreactproject/urls.py
:
- nano ~/djangoreactproject/djangoreactproject/urls.py
Deje lo que haya allí, pero añada la importación a las vistas customers
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 url
A continuación, añada las URL de api/customers/
y api/customers/<pk>
a la lista urlpatterns
que contiene 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 nuestros puntos finales REST creados, veamos cómo podemos consumirlos.
En este paso, instalaremos Axios, el cliente HTTP que utilizaremos para realizar llamadas de API. También crearemos una clase para consumir los puntos finales de API que hemos creado.
Primero, desactive su entorno virtual:
- deactivate
A continuación, navegue a su carpeta frontend
:
- cd ~/djangoreactproject/frontend
Instale axios
desde npm
utilizando:
- npm install axios --save
La opción --save
añade la dependencia axios
al archivo package.json
de su aplicación.
A continuación, cree un archivo de JavaScript denominado CustomersService.js
, que contendrá el código para llamar a las API REST. Realizaremos esto dentro de la carpeta src
, donde residirá el código de la aplicación de nuestro proyecto:
- cd src
- nano CustomersService.js
Añada el código siguiente, 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
llamará a los siguientes métodos de Axios:
getCustomers()
: obtiene la primera página de clientes.getCustomersByURL()
: obtiene clientes por URL. Esto permite obtener las siguientes páginas de clientes al pasar enlaces como /api/customers/?page=2
.get Customer()
: obtiene un cliente por clave primaria.createCustomer()
: crea un cliente.updateCustomer()
: actualiza un cliente.deleteCustomer()
: elimina un cliente.Ahora, podemos mostrar los datos de nuestra API en nuestra IU de React al crear un componente CustomersList
.
En este paso, crearemos el componente
CustomersList de React. Un componente de React representa una parte de la IU; también le permite dividir la IU en piezas independientes y reutilizables.
Comience por crear CustomersList.js
en frontend/src
:
- nano ~/djangoreactproject/frontend/src/CustomersList.js
Inicie la importación de React
y Component
para crear un componente de React:
import React, { Component } from 'react';
A continuación, importe e instancie el módulo CustomersService
que creó en el paso anterior, el cual proporciona métodos que interactúan con el backend de API REST:
...
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 debería extender o crear una subclase de la clase Component
. Para obtener más información acerca de las clases E6 y herencia, consulte nuestro tutorial sobre Comprensión de las clases de JavaScript.
Añada el siguiente código para crear un componente de 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, estamos inicializando el objeto state
. Este almacena las variables de estado de nuestro componente utilizando una matriz customers
vacía. Esta matriz almacenará clientes y una nextPageURL
que contendrá la URL de la siguiente página que se obtendrá del back-end de la API. También procedemos a vincular los métodos nextPage()
y handleDelete()
a this
para que sean accesibles desde el código HTML.
A continuación, añada el método componentDidMount()
y una llamada a getCustomers()
dentro de 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 al crear el componente y se inserta en el DOM. getCustomers()
llama al objeto Customers Service para obtener la primera página de datos y el enlace de la siguiente página del backend de Django:
...
componentDidMount() {
var self = this;
customersService.getCustomers().then(function (result) {
self.setState({ customers: result.data, nextPageURL: result.nextlink})
});
}
Ahora, añada el método handleDelete()
, que gestiona la eliminación de un 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 un cliente utilizando su pk
(clave primaria). Si la operación se realiza correctamente, la matriz customers
se filtra por el cliente eliminado.
A continuación, añada un método nextPage()
para obtener los datos de la siguiente página y actualice 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 página siguiente del objeto de estado, this.state.nextPageURL
, y actualiza la matriz customers
con los datos devueltos.
Por último, añada el método render()
del componente, que produce una tabla de clientes del estado de 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>
);
}
Este es 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 visualizar la lista de clientes, podemos añadir el componente que gestiona la creación y la actualización de clientes.
En este paso, crearemos el componente CustomerCreateUpdate
, que se encargará de crear y actualizar clientes. Lo hará al proporcionar una forma que los usuarios puedan utilizar para ingresar datos sobre un cliente nuevo o actualizar una entrada existente.
En frontend/src
, cree un archivo CustomerCreateUpdate.js
:
- nano ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
Añada el siguiente código para crear un componente de React, importando React
y Component
:
import React, { Component } from 'react';
A continuación, también podemos importar e instanciar la clase CustomersService
que creamos en el paso anterior, que proporciona métodos que interactúan con el backend de API REST:
...
import CustomersService from './CustomersService';
const customersService = new CustomersService();
Luego, cree un componente CustomerCreateUpdate
que extienda Component
para crear y actualizar clientes:
...
class CustomerCreateUpdate extends Component {
constructor(props) {
super(props);
}
}
export default CustomerCreateUpdate;
Dentro de la definición de clase, añada el método render()
del componente, que produce un formulario HTML que toma información sobre el 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 añade una propiedad ref
para acceder al valor del elemento del formulario y establecerlo.
A continuación, por encima del método render()
, defina un método handleSubmit(event)
de modo que tenga la funcionalidad correcta cuando un usuario haga clic en el botón de 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, dependiendo de la ruta, llama al método handleUpdate(pk)
para actualizar el cliente con la pk
correcta, o el método handleCreate()
para crear un nuevo cliente. Procederemos a definir estos métodos en breve.
De nuevo en el constructor del componente, vincule el método handleSubmit()
recientemente añadido a this
para poder acceder a él en su formulario:
...
class CustomerCreateUpdate extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
...
A continuación, defina el método handleCreate()
para crear un cliente a partir de los datos del formulario. Encima del método handleSubmit(event)
, añada el siguiente código:
...
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 utilizará para crear un cliente a partir de los datos ingresados. Llama al método CustomersService.createCustomer()
correspondiente que provoca que la API real llame al backend para crear un cliente.
A continuación, por 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()
actualizará un cliente por pk,
utilizando la nueva información del formulario de información de clientes. Llama al método customersService.updateCustomer()
.
A continuación, añada un método componentDidMount()
. Si el usuario visita una ruta customer/:pk
, queremos que el formulario se complete con información relacionada con el cliente utilizando la clave primaria de la URL. Para ello, podemos añadir el método getCustomer(pk)
una vez que el componente se monte en el evento de ciclo de vida de componentDidMount()
. Añada el siguiente código por debajo del constructor del componente para añadir este método:
...
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 es 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;
Con el componente CustomerCreateUpdate
creado, podemos actualizar el componente principal App
para añadir enlaces a los diferentes componentes que hemos creado.
En esta sección, actualizaremos el componente App
de nuestra aplicación para crear enlaces a los componentes que hemos creado en los pasos anteriores.
Desde la carpeta frontend
, ejecute el siguiente comando para instalar React Router, que le permite añadir enrutamiento y navegación entre varios componentes de React:
- cd ~/djangoreactproject/frontend
- npm install --save react-router-dom
A continuación, abra ~/djangoreactproject/frontend/src/App.js
:
- nano ~/djangoreactproject/frontend/src/App.js
Elimine todo lo que haya allí y añada el siguiente código para importar las clases necesarias para incorporar enrutamiento. Estas incluyen BrowserRouter
, que crea un componente Router, y Route
, que crea un componente de ruta:
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 IU sincronizada con la URL utilizando la API de historial HTML5.
A continuación, cree un diseño base que proporcione el componente básico que envolverá 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>
)
Utilizamos el componente Route
para definir las rutas de nuestra aplicación; el componente que el enrutador debe cargar cuando se encuentra una coincidencia. Cada ruta requiere un path
que especifique la ruta que se debe seguir y un component
que indique el componente que se debe cargar. La propiedad exact
le indica al enrutador que siga la trayectoria exacta.
Por último, cree el componente App
, el componente root o de nivel superior de nuestra aplicación de React:
...
class App extends Component {
render() {
return (
<BrowserRouter>
<BaseLayout/>
</BrowserRouter>
);
}
}
export default App;
Hemos envuelto el componente BaseLayout
con el componente BrowserRouter
, dado que nuestra aplicación está diseñada para ejecutarse en un navegador.
El archivo completo se ve de este modo:
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;
Ahora que añadimos enrutamiento a nuestra aplicación, estamos listos para probarla. Navegue a http://localhost:3000
. Debería ver la primera página de la aplicación:
Con la implementación de esta aplicación, ahora, cuenta con la base para una aplicación de CRM.
En este tutorial, usted creó una aplicación de prueba utilizando Django y React. Utilizó Django REST framework para crear la API REST, Axios para consumir la API y Bootstrap 4 para dar estilo a su CSS. Puede encontrar el código fuente de este proyecto en este repositorio de GitHub.
En la configuración de este tutorial, se utilizaron aplicaciones de front-end y back-end. Para obtener un enfoque diferente de la integración de React con Django, consulte este tutorial y este otro tutorial.
Para obtener más información sobre la creación de aplicaciones con Django, puede seguir la serie de desarrollo de Django. También puede consultar la documentación oficial de Django.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Sigo los pasos y en el APP de React en el http://localhost:3000 se me queda en blanco
Tengo un problema cuando tengo que hacer los endpoints, al agregar customers, me marca que no lo encuentra (lo subraya), y no se como hacer para que lo reconozca… alguna sugerencia? Gracias
Hola buen día, tengo una duda, ¿de donde se obtienen estas rutas?: “api/clusters” y “api/clusters/<pk>”
Hice una aplicación claro con otro nombre pero tengo duda de donde viene la parte de “api/” en las rutas.