Todo app Django + Reactjs Parte 1 (Back-End)

Guía Básica para crear una todo app con django y reactjs.

Leonardo Esparis
6 min readSep 4, 2017

Nota: En el siguiente post, se asumirá que tiene el ambiente virtual de python listo, que ya se tiene experiencia previa en django(básica como mínimo) y ya tenga iniciado un paquete de nodejs en el mismo directorio.

Instalar Dependencias.

Python: instalaremos las dependencias que son necesarios para poder crear el Back-End.

pip install Django django-webpack-loader djangorestframework

Javascript: ahora instalaremos las dependencias del Front-End, puedes usar npm(node package manager) o yarn, para instalar dichas dependencias.

npm i --save-dev axios babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0 react react-hot-loader webpack webpack-bundle-tracker webpack-dev-server react-dom

Back-End.

Antes de iniciar con el Back-End, vamos a crear el proyecto llamado ptodo.

django-admin startporject ptodo

Ahora vamos a configurar el proyecto, para eso tenemos que abrir el archivo settings.py, que se encuentra en el directorio ptodo. Vamos a agregar el procesador de contexto csrf, que sirve para generar un token que se va a montar en el template home.html y será enviado por el cliente, solo cuando valla a agregar o actualizar una tarea. Nota: Dicha táctica es utilizada para evitar ataques Cross Site Request. Para mas información acerca del ataque CSRF apreté aquí y para saber como renderizar y utilizar el csrf token con django apreté aquí.

TEMPLATES = [
{
.
.
.
'OPTIONS': {
'context_processors': [
.
.
.
'django.template.context_processors.csrf',
],
},
},
]

Ahora, agregaremos la aplicación webpack_loader a la lista INSTALLED_APPS. Dicha aplicación será utilizada para poder usar Reactjs con django, tanto en desarrollo como en producción.

INSTALLED_APPS = [
.
.
.
'webpack_loader'
]

Después se va a agregar la variable WEBPACK_LOADER, para configurar la aplicación webpack_loader. En dicha variable, vamos a declarar el nombre del directorio, el cual va a contener los módulos de javascript compilados y la ruta a los stat de cada compilación.

WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': 'js/',
'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
}
}

Ahora vamos a declarar las rutas a los templates y archivos estáticos del proyecto.

TEMPLATES = [
{
.
.
.
'DIRS': [
os.path.join(BASE_DIR, 'ptodo', 'core', 'templates')
],
.
.
.
},
]
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'ptodo', 'core', 'static'),
)

Ya que hemos terminado de configurar el proyecto, vamos a crear el directorio core dentro del directorio que contiene el modulo settings.py y dentro de core, crearemos los siguientes módulos: views.py __init__.py. También crearemos los directorios templates y static; dentro del directorio templates, vamos a crear un template llamado home.html y dentro del directorio static, vamos a crear el directorio js(va a contener los módulos de javascript compilados). Después de eso, volvemos a la raíz del proyecto, se crea la aplicación tasks y se agrega la el nombre de la misma a la variable INSTALLED_APPS .

./manage.py startapp tasks# settings.py
INSTALLED_APPS = [
.
.
.
'tasks'
]

El árbol de directorios, va a quedar algo así:

ptodo/
|
| - ptodo/
| |
| | - core/
| | |
| | | - static/
| | | |
| | | | - js/
| | |
| | | - templates/
| | | |
| | | | - home.html
| | |
| | | - __init__.py
| | | - views.py
| |
| | - __init__.py
| | - settings.py
| | - urls.py
| | - wsgi.py
|
| - tasks/
| |
| | - __init__.py
| | - admin.py
| | - apps.py
| | - models.py
| | - tests.py
| | - views.py
| | - migrations/
| | |
| | | - __init__.py
|

Ahora vamos al modulo views.py, que se encuentra en ptodo/core/. En dicho modulo, vamos a crear una vista basada en clase de python(class based view). Dicha clase va a heredar del objeto TemplateView, ahora vamos a declarar un variable global, llamada template_name, el cual va a contener el nombre del template a renderizar.

from django.views.generic import TemplateViewclass HomeView(TemplateView):
template_name = "home.html"

Después, registramos dicha vista en el modulo urls.py, que se encuentra en el directorio padre del directorio actual.

.
.
.
from .core.views import HomeViewurlpatterns = [
.
.
.
url(r'^$', HomeView.as_view(), name='home'),
]

Ahora abrimos el template llamado home.html, ubicado en el directorio /ptodo/core/templates/home.html y agregamos lo siguiente:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Home - todoapp</title>
<base data-token="{{ csrf_token }}"/>
</head>
<body>
<div id="root"></div>
</body>
</html>

Si ahora corremos el servidor de desarrollo de django, navegas a la dirección http://localhost:8000/ o la dirección que le hallas asignado, podrás ver que el proyecto corre correctamente.

Ahora vamos a crear un modelo en la aplicación tasks, llamado TaskModel. Dicho modelo, va a contener el nombre de la tarea agregada y la fecha de su creación. Después de eso, vamos a mapear el modelo a la base de datos y vamos al modulo admin.py, donde vamos a registrar el modelo, para que sea visible en la vista del administrador.

# tasks/models.py
from django.db import models
class TaskModel(models.Model):
task = models.CharField(max_length=255, blank=False, null=False)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.task
class Meta:
verbose_name = 'Task'
verbose_name_plural = 'Tasks'
# shell
./manage.py makemigrations && ./manage.py migrate
# admin.py
from django.contrib import admin
from .models import TaskModel
@admin.register(TaskModel)
class TaskAdmin(admin.ModelAdmin):
pass

Ahora vamos a crear un modulo llamado serializers.py, en dicho modulo vamos a crear un objeto serializer, llamado TaskSerializer, el cual se va a encargar de serializar un objeto TaskModel o datos enviados por el cliente en formato json. En el serializer, se esta implementando los método create y update, el cual nos permite crear y actualizar tareas, respectivamente.

from rest_framework import serializersfrom .models import TaskModel# cambiando el formate de
# la fecha y hora.
FORMAT = {
'format': '%d/%m/%Y %H:%M:%S',
'input_formats': ['%d/%m/%Y %H:%M:%S']
}
class TaskSerializer(serializers.Serializer):
task_name = serializers.CharField(source='task', max_length=255)
task_id = serializers.IntegerField(source='id', read_only=True, required=False)
task_created_time = serializers.DateTimeField(source='created', read_only=True, required=False, **FORMAT)
def create(self, validated_data):
task_name = validated_data.pop('task')
task = TaskModel(task=task_name)
task.save()
return task
def update(self, instance, validated_data):
instance.task = validated_data.get('task', instance.task)
instance.save()
return instance

Ahora vamos a crear los API view or endpoints, que se van a encargar de servir solicitudes ajax(mostrar, agregar, actualizar y borrar tareas).

ListTasksApiView:

Se encarga de devolver todas la tareas, registradas en la base de datos.

# Muestra todas las tareas agregadas.
class ListTasksApiView(TaskApiMixin, APIView):
model = TaskModel
serializer = TaskSerializer
def get(self, request, format=None):
model = self.get_model
tmpTasks = model.objects.all().order_by('-id')
serializer = self.get_serializer(tmpTasks, many=True)
return Response({
'tasks': serializer.data,
'message': '',
'code': 200
})

AddTaskApiView:

Se encarga de agregar una tarea a la base de datos.

# Agregar una tarea.
class AddTaskApiView(TaskApiMixin, APIView):
serializer = TaskSerializer
def post(self, request, format=None):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({
'code': 201,
'message': '',
'task': serializer.data
})
return Response({
'code': 400,
'message': 'Cannot add task.',
'task': ''
})

DeleteTaskApiView:

Se encarga de eliminar una tarea del sistema.

class DeleteTaskApiView(TaskApiMixin, APIView):
model = TaskModel
serializer = TaskSerializer
def get(self, request, format=None):
model = self.get_model
task_id = request.query_params.get('task_id', None)
task = None
try:
task = model.objects.get(id=task_id)
except model.DoesNotExist:
pass
if task is None:
return Response({
'code': 404,
'message': 'task does not exists',
'task': ''
})
taskResponse = self.get_serializer(task).data
task.delete()
return Response({
'code': 200,
'message': '',
'task': taskResponse
})

UpdateTaskApiView:

Se encarga de actualizar una tarea(Solo el nombre de dicha tarea).

class UpdateTaskApiView(TaskApiMixin, APIView):
model = TaskModel
serializer = TaskSerializer
def put(self, request, format=None):
task = None
try:
task = self.get_model.objects.get(
id=request.data.get('task_id', -1))
except self.get_model.DoesNotExist:
pass
if task is None:
return Response({'code': 404, 'message': 'task not found'})
serializer = self.get_serializer(task,
data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response({'code': 202, 'message': ''})
return Response({'code': 404, 'message': 'cannot update task.'})

DeleteAllTasksApiView:

Se encarga de eliminar todas las tareas del sistema.

class DeleteAllTasksApiView(TaskApiMixin, APIView):
model = TaskModel
def get(self, request, format=None):
model = self.get_model
all_tasks = model.objects.all()
if len(all_tasks) == 0:
return Response({
'code': 404,
'message': 'There\'s no tasks to delete.'})
for task in all_tasks:
task.delete()
return Response({'code': 200, 'message': ''})

Ahora, en el directorio de la aplicacion tasks, vamos a crear un modulo, llamado urls.py, el cual, va a contener los endpoints para agregar, actualizar, borrar las tareas.

from django.conf.urls import url                                               from .views import (
ListTasksApiView,
AddTaskApiView,
DeleteTaskApiView,
DeleteAllTasksApiView,
UpdateTaskApiView
)
urlpatterns = [ url(r'^list/$', ListTasksApiView.as_view(), name='list_tasks'),
url(r'^add/$', AddTaskApiView.as_view(), name='add_task'),
url(r'^delete/$', DeleteTaskApiView.as_view(), name='delete_task'),
url(r'^update/$', UpdateTaskApiView.as_view(),name='update_task_name'),
url(r'^delete/all/$', DeleteAllTasksApiView.as_view(),
name='delete_all_task'),
]

Una vez agregado los endpoints, necesitamos registrar que la aplicación task esta declarando rutas a la api, para ello, tenemos que ir al modulo urls.py, que se encuentra en la ruta /ptodo/urls.py, dentro del mismo agregamos lo siguiente:

from django.conf.urls import url, include
from django.contrib import admin
from .core.views import HomeViewurlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', HomeView.as_view(), name='home'),
url(r'^task/', include('tasks.urls', namespace="TASKS")),
]

El backend esta listo, es muy sencillo de hacer y de leer, en caso de que tengan preguntas o se me he equivocado en algo, comenten =). Podrás encontrar la el codigo de la aplicación de ptodo en github.

El fuente del proyecto, ptodo se encuentra en el siguiente enlace:

Saludos.

--

--