0% encontró este documento útil (0 votos)
16 vistas73 páginas

Taller Django, React y Redux

Este documento describe los pasos para crear una aplicación web utilizando Django, React y Redux. Explica cómo crear un proyecto de Django, agregar una aplicación, vistas, modelos y configurar Django para usar componentes de React.

Cargado por

Sebastián Emdef
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
16 vistas73 páginas

Taller Django, React y Redux

Este documento describe los pasos para crear una aplicación web utilizando Django, React y Redux. Explica cómo crear un proyecto de Django, agregar una aplicación, vistas, modelos y configurar Django para usar componentes de React.

Cargado por

Sebastián Emdef
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

Taller de Django + React + Redux

Este repositorio va a ser usado en la PyconAr2017 para dar el paso a paso del taller
"Django + React + Redux".

Estado

Heroku con la aplicación:web

Modalidades del repositorio


Para poder ir aprendiendo el repositorio hace el paso a paso para generar una proyecto
productivo con estas tecnologías. Cada paso esta en una rama aparte con los archivos
correspondiente del proyecto en ese paso determinado. Las ramas (pasos):

• Paso 1: Crear proyecto de Django


• Paso 2: Crear aplicación de Django
• Paso 3: Agregar una vista sin React
• Paso 4: Agregar modelo de Django
• Paso 5: Agregar django_webpack_loader
• Paso 6: Crear la primer componente de React
• Paso 7: Usar el paquete con React
• Paso 8: Recarga automática
• Paso 9: Python linter
• Paso 10: React linter
• Paso 11: Python testing
• Paso 12: React testing
• Paso 13: Enviar contexto de Django en React
• Paso 14: Api rest y fetch de datos
• Paso 15: Django channels y websockets
• Paso 16: Agregar Redux
• Paso 17: Entorno de producción

Cada rama tiene la documentación en español ( [Link]) y en ingles ([Link]).

Requirimientos del proyecto


Los requerimientos van variando a lo largo del proyecto, como este repositorio todavía
puede que alguno todavía no esten. Mi recomendación para hacer el curso rápido es que
te instales de antemano los mas posible. En mi caso me gusta usar docker como se puede
ver en cada paso pero también esta la opción sin docker para la gente que no lo usa.
Imagen de docker con todo el workshop

Esta imagen tiene el codigo, pip requirements (dev, docs y production requirements),
node dependencies (pruduction y dev dependencies), ...

docker pull [Link]/fedeg/django-react-workshop:latest

Instalación previa con Docker


# Clonar el repositorio
git clone [Link]
cd django-react-workshop

# Python y Django
docker run -d -it --name workshop -v $PWD:/src -p 8000:8000 --workdir
/src python:3.6 bash
docker exec -it workshop pip install -r [Link]
docker exec -it workshop pip install -r [Link]

# Node y React
docker run -d -it --name workshopjs -v $PWD:/src -p 3000:3000 --workdir
/src/workshop/front node:8 bash
docker exec -it workshopjs npm install yarn --global
docker exec -it workshopjs yarn install

Instalación previa sin Docker


# Clonar el repositorio
git clone [Link]
cd django-react-workshop

# Python y Django
## Instalar python 3 (3.5 o 3.6 idealmente)
pip install -r [Link]
pip install -r [Link]

# Node y React
curl -sL [Link] | sudo -E bash -
sudo apt-get install -y build-essential nodejs
npm install yarn --global
cd workshop/front
yarn install

Paso 1: Crear tu proyecto de Django


Volver a la rama maestra

Crear proyecto django


Como no tenemos un proyecto a mano, creamos uno nuevo:
docker run -d -it --name workshop -v $PWD:/src -p 8000:8000 --workdir
/src python:3.6 bash
docker exec -it workshop bash

# dentro del container de docker


pip install Django
django-admin startproject workshop
Si nunca has visto docker, deberías tomarte un tiempo y aprender primero sobre docker.
Opción sin docker (con mkvirtualenv):
mkvirtualenv djreact
pip install Django
django-admin startproject workshop
Si nunca viste el comando mkvirtualenv, podrías tomarte un tiempo y aprender
sobre virtualenvwrappler primero.

Esto crea un nuevo proyecto de Django en la carpeta raíz de su repositorio.

Agregar requirements
También vamos a crear un [Link]:
# con docker
docker exec -it workshop pip freeze > [Link]

# sin docker
pip freeze > [Link]
Si nunca usaste [Link], te recomiendo ver la documentación de pip.

Agregar gitignore
Y finalmente deberíamos crear un archivo .gitignore y
agregar *.pyc y db.sqlite3 para ignorarlos en el repositorio.

Resultado
En este punto, podes ejecutar el proyecto.

Ejecutar projecto
# con docker
docker exec -it workshop ./workshop/[Link] runserver [Link]:8000

# sin docker
./workshop/[Link] runserver
Debería ver la página de bienvenida de Django en el navegador
en [Link]

Paso 2: Crear aplicación de Django


Paso 2: Creamos nuestra aplicación de
Django
Volver al paso 1

Crear la aplicación django


Vamos a crear una nueva aplicación:

# con docker
docker exec -it workshop bash -c "cd workshop && ./[Link] startapp
links"

# sin docker
cd workshop
./[Link] startapp links

Esto crea una nueva aplicación Django en la carpeta del proyecto de Django
(./workshop/links).

Agregar nueva aplicación a INSTALLED_APPS:


Agregue el nombre de la aplicación (links)
a INSTALLED_APPS en ./workshop/workshop/[Link].

Añadir django admin


Crear base de datos con estado inicial (migrar modelos de autenticación
de django)

Vamos a migrar la base de datos:

# con docker
docker exec -it workshop ./workshop/[Link] migrate

# sin docker
./workshop/[Link] migrate

Agregar usuario administrador

Vamos a crear un súper usuario:

# con docker
docker exec -it workshop ./workshop/[Link] createsuperuser

# sin docker
./workshop/[Link] createsuperuser
Resultado
En este punto, puedes ejecutar un proyecto con el administrador de Django.

Ejecutar proyecto
# con docker
docker exec -it workshop ./workshop/[Link] runserver [Link]:8000

# sin docker
./workshop/[Link] runserver
Deberías ver la página de administración de Django en su navegador
en [Link]

Paso 3: Agregar vistas que no son de React

Paso 3: Agregar vistas sin react


Volver al paso 2

Agregar urls
Queremos mostrar que ReactJS se puede usar fácilmente con un proyecto existente, por
lo que agregaremos algunas "vistas heredadas" para simular que esta es una proyecto
existente de Django.

Agregué las siguientes líneas a workshop/[Link]:


from [Link] import include, url
from [Link] import admin

urlpatterns = [
path('admin/', [Link]),
path('links/', include('[Link]')),
]

Agregué las siguientes líneas a links/[Link]:


from [Link] import path
from [Link] import generic

urlpatterns = [
path('view2/',
[Link].as_view(template_name='[Link]')),
path('',
[Link].as_view(template_name='[Link]')),
]
Añadir carpeta de templates a la configuración de
Django
A continuación, agregué algunas templates a la carpeta templates y finalmente me
aseguré que Django conozca estas templates al poner esto en [Link]:
TEMPLATES = [
{
...
'DIRS': [[Link](BASE_DIR, 'links/templates')],
...
},
]

Agregar templates

La template base es el archivo links/templates/[Link]. Este importa Twitter


Bootstrap CSS Framework:

<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<link rel="stylesheet"
href="[Link]
[Link]" integrity="sha384-
1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7"
crossorigin="anonymous">
</head>
<body>
{% block main %}{% endblock %}
</body>
</html>

Las templates para las vistas son links/templates/[Link]:

{% extends "[Link]" %}

{% block main %}
<div class="container">
<h1>View 1</h1>
</div>
{% endblock %}

y links/templates/[Link]:

{% extends "[Link]" %}

{% block main %}
<div class="container">
<h1>View 2</h1>
</div>
{% endblock %}
Resultado
En este punto, puedes ejecutar un proyecto con el administrador de Django.

Ejecutar proyecto
# con docker
docker exec -it workshop ./workshop/[Link] runserver [Link]:8000

# sin docker
./workshop/[Link] runserver
Debería ver la página de "View 1" en su navegador en [Link]
Puedes cambiar la url a [Link] y debería ver la página
de "View 2".

Estoy importando Twitter Bootstrap aquí porque también quiero mostrar que ReactJS no
se interpondrá en tu camino incluso si ya estás usando un CSS complejo Más sobre esto
en futuros pasos.

Paso 4: Agregar modeles de Django

Paso 4: agregar modelos de Django


Volver al paso 3

Agregar modelos (Link y Tags)


Queremos mostrar que ReactJS se puede usar fácilmente con un proyecto existente, por
lo que agregaremos algunos modelos para simular que este es un projecto existente de
Django.

Agregué las siguientes líneas a workshop/links/[Link]:


from [Link] import models
from [Link] import User
from [Link] import ugettext as _

class Tag([Link]):
name = [Link](_('name'), max_length=30)
description = [Link](_('description'), blank=True,
null=True)
user = [Link](
User, verbose_name=_('user'), blank=True, null=True,
on_delete=[Link])

def __str__(self):
return [Link]
class Meta:
verbose_name = _('tag')
verbose_name_plural = _('tags')

class Link([Link]):
name = [Link](_('name'), max_length=30)
url = [Link](_('url'))
pending = [Link](_('pending'), default=False)
description = [Link](_('description'), blank=True,
null=True)
tags = [Link](Tag, through='LinkTag',
editable=True)
user = [Link](User, verbose_name=_('user'),
on_delete=[Link])

def __str__(self):
return [Link]

class Meta:
verbose_name = _('link')
verbose_name_plural = _('links')

class LinkTag([Link]):
link = [Link](Link, verbose_name=_('link'),
on_delete=[Link])

tag = [Link](Tag, verbose_name=_('tag'),


on_delete=[Link])

class Meta:
unique_together = (('link', 'tag'))
verbose_name = _('link x tag')
verbose_name_plural = _('link x tag')
Notas

• Si nunca viste gettext para las traducciones, te recomiendo que aprendas


primero sobre traducción.
• Si nunca viste ManyToManyField con through, podrías tomarte un tiempo y
aprender sobre [campos adicionales en relaciones muchos a muchos]
([Link]
on-many-to-many-relationships) primero.

Agregar modelos al admin de Django


Agregué las siguientes líneas a workshop/links/[Link]:
from [Link] import admin
from .models import Link, Tag, LinkTag

class LinkTagInline([Link]):
model = LinkTag
@[Link](Link)
class LinkAdmin([Link]):
inlines = [LinkTagInline]

@[Link](Tag)
class TagAdmin([Link]):
pass

Crear migraciones para crear nuevos modelos en la db

Generemos migraciones:

# con docker
docker exec -it workshop ./workshop/[Link] makemigrations

# sin docker
./workshop/[Link] makemigrations

Actualizar db con nuevos modelos

Actualicemos la db:

# con docker
docker exec -it workshop ./workshop/[Link] migrate

# sin docker
./workshop/[Link] migrate

Importar datos de ejemplo

A continuación, vamos a agregar algunos ejemplos a los modelos.

## Exportado con
## docker exec -it workshop ./workshop/[Link] dumpdata --indent 2 -
o [Link] links
## o
## ./workshop/[Link] dumpdata --indent 2 -o [Link] links

# con docker
docker exec -it workshop ./workshop/[Link] loaddata data/[Link]

# sin docker
./workshop/[Link] loaddata data/[Link]

Añadir traducciones
Si nunca vista traducciones de Django, podría tomarse un tiempo y aprender primero
sobre traducciones.

Agregar configuraciones para la traducción en la configuración de


Django
...
import os
+from [Link] import ugettext_lazy as _
...

...
MIDDLEWARE = [
...
'[Link]',
+ '[Link]',
'[Link]',
...
]

...

TEMPLATES = [
{
'BACKEND':
'[Link]',
'DIRS': [[Link](BASE_DIR, 'links/templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'[Link].context_processors.debug',
'[Link].context_processors.request',
'[Link].context_processors.auth',

'[Link].context_processors.messages',
+ '[Link].context_processors.i18n',
],
},
},
]

...

+LOCALE_PATHS = (
+ [Link](BASE_DIR, 'locale'),
+)
+LANGUAGES = [
+ ('es', _('Spanish')),
+ ('en', _('English')),
+]

Crear traducción para nuevos idiomas (es)


# con docker
docker exec -it workshop bash -c "apt update; apt --force-yes install -
y gettext"
mkdir workshop/locale
docker exec -it workshop ./workshop/[Link] makemessages --locale=es

# sin docker
apt update
apt --force-yes install -y gettext
mkdir workshop/locale
./workshop/[Link] makemessages --locale=es

Completar las traducciones del archivo: workshop/locale/es/LC_MESSAGES/[Link]


Compilar la traducción para el nuevo idioma (es)
# con docker
docker exec -it workshop ./workshop/[Link] compilemessages

# sin docker
./workshop/[Link] compilemessages

Añadir nuevas entradas al gitignore


Y finalmente debemos actualizar el archivo .gitignore, agregar *.mo.

Resultado
En este punto, pudes ejecutar el proyecto con el admin y traducciones.

Ejecutar proyecto
# con docker
docker exec -it workshop ./workshop/[Link] runserver [Link]:8000

# sin docker
./workshop/[Link] runserver
Deberías ver los "Links" en tu navegador
en [Link] Podes cambiar la url
a [Link] y deberías ver los "Tags".

Paso 5: Agregar Django webpack loader

Paso 5: Agregar django-webpack-loader


Volver al paso 4

Desafortunadamente, en este paso muchas cosas sucederán todas a la vez. Esto es el paso
donde la mayoría de la gente se rinde, porque Webpack es una herramienta muy grande
y difícil de entender y configurar.

Vamos paso a paso.

Antes que nada, necesitamos instalar webpack-


loader:
Instalar django-webpack-loader
# con docker
docker exec -it workshop pip install django-webpack-loader
# sin docker
pip install django-webpack-loader

Actualizamos requirements

También vamos a agregararlo al [Link]. Sugerencia: cada vez que instales


algo con pip, ejecuta pip freeze inmediatamente después y agrega los paquetes con su
número de versión a [Link].
Comando:
# con docker
docker exec -it workshop pip freeze > [Link]

# sin docker
pip freeze > [Link]

Agregar webpack_loader a INSTALLED_APPS en Django settings

A continuación, tenemos que agregar esta aplicación a la configuración


de INSTALLED_APPS en nuestro workshop/workshop/[Link]:
INSTALLED_APPS = [
...
'webpack_loader',
]

Agregar carpetas con archivos estáticos

Webpack se trata de crear "paquetes" (también conocidos como archivos minificados de


JavaScript). Estos paquetes se guardarán en nuestra carpeta static, como todos los
archivos css y js que siempre usamos en Django. Entonces, tenemos que hacer que Django
sea consciente de estos estáticos. Agregamos la carpeta
en workshop/workshop/[Link]:
STATICFILES_DIRS = [
[Link](BASE_DIR, 'links/static'),
]

Instalar ReactJs y sus dependencias

Luego tenemos que crear un archivo [Link] (en workshop/front/[Link]),


que es similar a el archivo [Link] de Python:
{
"name": "links",
"version": "0.0.1",
"scripts": {
"start": "node [Link]",
"react-devtools": "react-devtools"
},
"devDependencies": {
"babel": "^6.23.0",
"babel-core": "^6.26.0",
"babel-loader": "^6.4.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"react-test-renderer": "^16.0.0",
"webpack": "^1.12.13",
"webpack-bundle-tracker": "0.0.93",
"webpack-dev-server": "^1.14.1"
},
"dependencies": {
"babel-polyfill": "^6.26.0",
"es6-promise": "^4.1.1",
"isomorphic-fetch": "^2.2.1",
"js-cookie": "^2.1.4",
"lodash": "^4.17.4",
"prop-types": "^15.6.0",
"radium": "^0.19.4",
"raf": "^3.4.0",
"react": "^16.0.0",
"react-cookie": "^2.1.1",
"react-dom": "^16.0.0",
"react-websocket": "^1.2.0"
}
}

No explicaré en detalle para qué sirve cada paquete. Descubrir lo que realmente necesitas
es una de las partes realmente difíciles al comenzar con ReactJS. Describir las razones
detrás de cada uno de estos paquetes iría mucho más allá del alcance de este paso. Mucho
de esto tiene que ver con Babel, que es una herramienta que "transpila" la nueva sintaxis
de JavaScript en algo que admiten los navegadores actuales.

Instalar nodejs, npm y yarn


# con docker
docker run -d -it --name workshopjs -v $PWD:/src -p 3000:3000 --workdir
/src/workshop/front node:8 bash
docker exec -it workshopjs npm install yarn --global

# sin docker
curl -sL [Link] | sudo -E bash -
sudo apt-get install -y build-essential nodejs
npm install yarn --global
Si nunca has visto el comando yarn, te aconsejo que leas sobre yarn vs npm primero.

Instalat dependencias

Cuando haya creado el archivo [Link], podes instalar los paquetes:

# con docker
docker exec -it workshopjs yarn install

# sin docker
cd workshop/front
yarn install
Este comando creará un [Link] con todas las versiones de dependencias (similar
a [Link]) y crea una carpeta node_modules con las dependencias, por lo
que también deberíamos agregar esa carpeta a .gitignore.

Agregar configuración de webpack


Después de ejecutar yarn install, deberías poder utilizar webpack (en teoría). En la
práctica, primero necesitas crear una configuración. Voy a adelantar un poco y ya lo divido
en dos archivos porque esto será bastante útil más tarde. El primer archivo se
llama [Link] y tiene las siguientes lineas:
const path = require('path')
const webpack = require('webpack')

[Link] = {
context: __dirname,

entry: {
// Add as many entry points as you have container-react-components
here
vendors: ['react', 'babel-polyfill'],
},

output: {
path: [Link]('./workshop/static/bundles/local/'),
filename: '[name]-[hash].js'
},

externals: [
], // add all vendor libs

plugins: [
new [Link]('vendors', '[Link]'),
], // add all common plugins here

module: {
loaders: [] // add all common loaders here
},

resolve: {
modulesDirectories: ['node_modules', 'bower_components'],
extensions: ['', '.js', '.jsx']
},
}

Hace muchas cosas:

1. Define el punto de entrada. Ese es el archivo JS que debe cargarse primero.


2. Define la ruta de salida. Aquí es donde queremos guardar nuestro paquete.
3. Utiliza el CommonsChunksPlugin, esto asegura que ReactJS será guardado
como un archivo diferente ([Link]), para que nuestro paquete de
aplicaciones no se vuelve demasiado grande

El segundo archivo se llama [Link] y se ve así:


const webpack = require('webpack')
const BundleTracker = require('webpack-bundle-tracker')

const config = require('./[Link]')


const localSettings = require('./[Link]')

const port = 3000


const ip = [Link]

const addDevVendors = (module) => [


`webpack-dev-server/client?[Link]
'webpack/hot/only-dev-server',
module
];

[Link] = "#eval-source-map"
[Link] = ip

// Use webpack dev server


[Link] = {
vendors: ['react', 'babel-polyfill'],
}

// override django's STATIC_URL for webpack bundles


[Link] = `[Link]

// Add HotModuleReplacementPlugin and BundleTracker plugins


[Link] = [Link]([
new [Link](),
new [Link](),
new BundleTracker({filename: './[Link]'}),
new [Link]({
'[Link]': {
'NODE_ENV': [Link]('development')
}
}),

])

// Add a loader for JSX files


[Link](
{ test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel'] }
)

[Link] = config
Esto esencialmente carga la configuración base y luego le agrega algunas cosas, lo mas
notable es un plugin llamado BundleTracker. Este modulo crea un archivo JSON cada
vez que generamos traspilados de ReactJs. Django puede entonces leer ese archivo JSON
y sabrá qué paquete pertenece a qué nombre en la aplicación (este tendrá más sentido
más adelante).

Agregar la configuración base para webpack local

Vamos a crear un archivo con configuración base para el webpack


local, workshop/front/[Link] y se ve así:
[Link] = {
ip: '[Link]',
}

Agregar configuración de babel

Usaremos la sintaxis de JavaScript de ES2015 para todos nuestro códifo JavaScript. Un


complemento llamado babel transpilara nuevamente el código avanzado a algo que los
navegadores pueden entender. Para que esto funcione, tenemos que crear el siguiente
archivo workshop/front/.babelrc:
{
"presets": ["es2015", "react", "stage-0"],
"plugins": [
["transform-decorators-legacy"],
]
}

Actualizar gitignore
Y finalmente tenemos que actualizar el archivo .gitignore y agregarle webpack-stats-
[Link].

Resultado
Ahora podríamos usar webpack para crear un compilado, pero no hemos escrito ningún
código JavaScript o ReactJS, aún. Vamos a agregar el primer componente React en el
próximo paso

Paso 6: Crear el primer componente de React

Paso 6: Crear la primer componente de


React
Volver al paso 5

Conceptos de React que vamos a usar en este paso


Si nunca has visto estos conceptos, puedes tomarse tu tiempo y leer sobre ellos
accediendo a los enlaces. Usaremos:

• Componentes de presentación y contenedores: presentational vs container


components
• Composición: composition vs inheritance
• Jsx: introducing jsx and jsx in depth
• Children prop: [Link]

Crear una vista


Primero, creamos una vista en la carpeta src/views (en workshop/front/src/views) y
agregamos el archivo [Link] adentro. Este va a ser uno de nuestros puntos de entrada
para webpack. webpack buscará ese archivo, luego seguirá todas sus importaciones y
las agregará al paquete, para que al final tengamos un gran archivo [Link] que pueda
ser utilizado por el navegador.
import React from "react"
import { render } from "react-dom"
import App from '../containers/App'
render(<App/>, [Link]('app'))

Crear un contenedor
Después, creamos una
carpeta src/containers (en workshop/front/src/containers) y agreguamos el
archivo [Link] adentro. Este es el primer componente de React, en su interior tendrá
una función de renderizado con el componente de presentación App.
import React from "react"

import AppComponent from "../components/App"

export default class App extends [Link] {


render() {
return (
<AppComponent />
)
}
}

Crear una componente


Como podes ver, App intenta importar otra componente llamada AppComponent. Ahora
vamos a crear el archivo que intenta
importar, workshop/front/src/components/App/[Link]:
import React from "react"

import Headline from "../Headline"

export default class App extends [Link] {


render() {
return (
<div className="container">
<div className="row">
<div className="col-sm-12">
<Headline>Sample App!</Headline>
</div>
</div>
</div>
)
}
}

Agregar sub-componente
Y otra vez, esa componente importa otra componente llamada Headline. Vamos a crearla
en workshop/front/src/components/Headline/[Link]:
import React from "react"

export default class Headline extends [Link] {


render() {
return (
<h1>{ [Link] }</h1>
)
}
}

Agregar la vista App a las entidades de webpack


En workshop/front/[Link]:
entry: {
// Add as many entry points as you have container-react-components
here
+ App: ['./src/views/App'],
vendors: ['react', 'babel-polyfill'],
},
En workshop/front/[Link]:
// Use webpack dev server
[Link] = {
+ App: ['./src/views/App'],
vendors: ['react', 'babel-polyfill'],
}

Actualizar gitignore
Y finalmente debemos actualizar el archivo .gitignore y agregarle el
archivo workshop/front/workshop/static/.

Resultado
Puede que te preguntes por qué estoy usando la componente App (container) y
otra AppComponent. Esto tendrá más sentido en posteriores pasos. Usaremos Redux para
administrar el estado de nuestra aplicación y veremos que Redux requiere varias
configuraciones para envolver su aplicación y manejar sus estados. Para mantener mis
archivos más limpios, me gusta tener un archivo contenedor, que luego importa el
componente real de ReactJS que quiero construir.
También notarás que separé mis componentes en una carpeta containers y en una
carpeta components. Puedes pensar en esto un poco como las vistas de Django. La
template principal es tu contenedor. Contiene la estructura general y dependencias para
tu página. En la carpeta componentes tendremos mucho componentes más pequeñas
que hacen una unica cosa. Estos componentes serán reutilizados y orquestados
por containers, por ende las componentes serían las equivalentes a las templates
parciales más pequeñas que usted importa en Django usando el etiqueta {% import %}.

En este punto puedes ejecutar el build:

# con docker
docker exec -it workshopjs ./node_modules/.bin/webpack --config
[Link]

# sin docker
cd front/src
./node_modules/.bin/webpack --config [Link]
Y esto debe generar algunos archivos
en workshop/front/workshop/static/bundles/.
Paso 7: Usar el paquete

Paso 7: Usar el paquete


Volver al paso 6

En el último paso, hemos creado nuestro primer paquete, pero no hemos visto el
resultado en el navegador. Ahora actualicemos nuestra plantilla para usar nuestra nueva
y elegante aplicación ReactJS.

Actualizar la template view1


Cambiamos workshop/links/templates/[Link] para que se vea así:
{% extends "[Link]" %}
{% load render_bundle from webpack_loader %}

{% block main %}
<div id="app"></div>

{% render_bundle 'vendors' %}
{% render_bundle 'App' %}
{% endblock %}

Actualizar la configuración de Django


Agregar la configuración de WEBPACK_LOADER

En workshop/workshop/[Link]:
WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': 'bundles/local/', # end with slash
'STATS_FILE': [Link](BASE_DIR, 'front/webpack-stats-
[Link]'),
}
}
Notas:

• BUNDLE_DIR_NAME le dice a Django en qué carpeta dentro de la


carpeta static puede encontrar nuestro paquete.
• STATS_FILE le dice a Django dónde puede encontrar el archivo JSON que mapea
los nombres de la componentes con los paquetes. Es por este archivo que
podemos usar {% render_bundle 'App'%} en nuestra template.

Añadir STATIC_ROOT y actualizar STATICFILES_DIRS


STATIC_URL = '/static/'
+STATIC_ROOT = [Link](BASE_DIR, 'static')
STATICFILES_DIRS = [
[Link](BASE_DIR, 'links/static'),
+ [Link](BASE_DIR, 'front/workshop/static'),
]

Cambiar el publicpath de webpack


Cambiar el publicpath en workshop/front/[Link]:

// override django's STATIC_URL for webpack bundles


-[Link] = `[Link]
+[Link] = `/static/bundles/local/`

Compilar los paquetes


# con docker
docker exec -it workshopjs node_modules/.bin/webpack --config
[Link]

# sin docker
cd workshop/front
node_modules/.bin/webpack --config [Link]

Obtener archivos estáticos en Django


mkdir workshop/links/static

# con docker
docker exec -it workshop ./workshop/[Link] collectstatic

# sin docker
./workshop/[Link] collectstatic

Resultado
En este punto, puedes correr el proyecto.

Correr el proyecto
# con docker
docker exec -it workshop ./workshop/[Link] runserver [Link]:8000

# sin docker
./workshop/[Link] runserver
Deberías ver la página de App en tu navegador en [Link]
Ahora intenta hacer un cambio en tu aplicación ReactJS. Cambia Sample
App! por Something New! en workshop/front/src/components/App/[Link].

A continuación, ejecutamos build y collectstatic nuevamente, asegurando de que


runserver aún se esté ejecutando y visitaremos el sitio en el navegador. Debería decir
"Something New!" ahora.

Increíble, ¿verdad? Añadiremos para este caso en el siguiente paso.


Paso 8: Recarga automatica

Paso 8: Recarga automática


Volver al paso 7

El paso 7 fue interesante e impresionante, pero no alucinante. Hagámoslo alucinante


ahora. Realmente no queremos ejecutar el comando webpack cada vez que cambiamos
código de ReactJS (y crear miles de paquetes locales en el proceso). Queremos ver los
cambios en el navegador de inmediato.

Agregar servidor de desarrollo para React


Primero, necesitamos un archivo [Link] (en workshop/front/[Link]) que
iniciará un webpack-dev-server para nosotros:
const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
const config = require('./[Link]')
const host = '[Link]'
const port = 3000

new WebpackDevServer(webpack(config), {
publicPath: [Link],
hot: true,
inline: true,
historyApiFallback: true,
headers: { 'Access-Control-Allow-Origin': '*' }
}).listen(port, host, (err) => {
if (err) [Link](err);
[Link](`Listening at ${host}:${port}`);
})

Convierte el webpack local a la configuración del


webpack de desarrollo
A continuación, debemos reemplazar lo siguiente en
nuestro [Link]:
- App: ['./src/views/App'],
+ App: addDevVendors('./src/views/App')

-[Link] = `/static/bundles/local/`
+[Link] = `[Link]

Aceptar la recarga automática a la vista


Añadiremos esta línea en workshop/front/src/views/[Link]:
if ([Link]) [Link]();
Resultado
Listo?

Ejecutar servidor desarrollo de webpack

En una terminal, iniciaremos el servidor de desarrollo de webpack con:

# npm start es igual a node [Link]

# con docker
docker exec -it workshopjs npm start

# sin docker
cd workshop/front
npm start

Ejecute el servidor Django Dev

En otra ventana de terminal, inicie el servidor de desarrollo de Django:

# con docker
docker exec -it workshop ./workshop/[Link] runserver [Link]:8000

# sin docker
./workshop/[Link] runserver
Asegúrate de que aún puedas ver "Something New!"
in [Link]
Y ahora cámbialo a Sample
App! en workshop/front/src/components/App/[Link] y vuelve a tu navegador.
Si eres muy rápido, puedes ver cómo se actualiza automática.
Hay otra cosa interesante: si abrís el sitio en Google Chrome, abrís las herramientas de
desarrollador con COMMAND + OPTION + i y luego abrir la pestaña Sources, puedes
ver webpack:// en la barra lateral. Tiene una carpeta llamada . donde estan las fuentes
originales de ReactJS. Incluso puedes poner puntos de interrupción aquí y depurar su
aplicación como un profesional. No más [Link]() en tu código JavaScript.

Paso 9: Python linter

Paso 9: Python linter


Volver al paso 8

Pylint es una herramienta que busca errores en el código Python, intenta aplicar un
estándar de codificación y busca code smells. También puede buscar ciertos errores de
tipo, puede recomendar sugerencias sobre cómo se pueden refactorizar determinados
bloques y puede ofrecerle detalles sobre la complejidad del código.
Otros proyectos similares son pychecker, pyflakes, flake8 y mypy. El estilo de
codificación utilizado por Pylint está cerca de PEP8.

Pylint mostrará una cantidad de mensajes mientras analiza el código y también se puede
usar para mostrar algunas estadísticas sobre el número de advertencias y errores
encontrados en diferentes archivos. Los mensajes se clasifican en varias categorías como
errores y advertencias.

Por último, pero no por ello menos importante, al código se le asigna un puntaje general,
según el número y la gravedad de las advertencias y los errores.

Instalar dependencias para pylint


Instalar pylint y pylint-django:

# con docker
docker exec -it workshop pip install pylint pylint-django

# sin docker
pip install pylint pylint-django

Crear requirements-dev

Vamos a usar requirements-dev porque estas dependencias son solo de desarrollo.

# con docker
docker exec -it workshop pip freeze | grep pylint > [Link]

# sin docker
pip freeze | grep pylint > [Link]

Crear .pylintrc
El archivo .pylintrc tiene todas las reglas para pytlint.

Generar archivo rc base


# con docker
docker exec -it workshop pylint --generate-rcfile > .pylintrc

# sin docker
pylint --generate-rcfile > .pylintrc

Personalizar nuestro archivo rc


# Add files or directories to the blacklist. They should be base names,
not
# paths.
-ignore=CVS
+ignore=[Link], [Link], [Link], migrations
# Add files or directories matching the regex patterns to the blacklist.
The
# regex matches against base names, not paths.
-ignore-patterns=
+ignore-patterns=migrations

Resultado
En esta punto, podes ejecutar pylint.

Ejecutar pylint
# con docker
docker exec -it workshop pylint --output-format=colorized --load-
plugins pylint_django workshop/workshop workshop/links

# sin docker
pylint --output-format=colorized --load-plugins pylint_django
workshop/workshop workshop/links

Leer el reporte de pylint

El comando pylint retorna:

************* Module [Link]


C: 11, 0: Missing class docstring (missing-docstring)
C: 25, 0: Missing class docstring (missing-docstring)
C: 41, 0: Missing class docstring (missing-docstring)
************* Module [Link]
C: 11, 0: Missing class docstring (missing-docstring)
C: 15, 0: Missing class docstring (missing-docstring)
C: 20, 0: Missing class docstring (missing-docstring)
************* Module [Link]
C: 1, 0: Missing module docstring (missing-docstring)
W: 1, 0: Unused render imported from [Link] (unused-import)
************* Module [Link]
C: 1, 0: Missing module docstring (missing-docstring)
C: 4, 0: Missing class docstring (missing-docstring)

------------------------------------------------------------------
Your code has been rated at 8.51/10 (previous run: 6.74/10, +1.76)

El tipo de mensaje puede ser:

• R: Refactor, una infracción de "buena práctica"


• C: Convenio para la codificación de violación estándar (PEP8)
• W: Advertencia de problemas de diseño o problemas menores de programación
• E: Error para problemas importantes de programación (es decir, probablemente
sea un error)
• F: Fatal para los errores que impidieron el procesamiento posterior

Y finalmente, podrías arreglar los pylints que el linter nos devuelve. Si quieres, podrías ver
cómo los soluciono en este commit: correcciones
Paso 10: React linter

Paso 10: React linter


Volver al paso 9

ESLint es una herramienta JavaScript de código abierto creada originalmente por


Nicholas C. Zakas en junio de 2013. Code linting es un tipo de análisis estático que se
utiliza con frecuencia para encontrar patrones problemáticos o códigos que no se
adhieren a ciertas pautas de estilo. Existen linters de código para la mayoría de los
lenguajes de programación.

JavaScript, al ser un lenguaje dinámico y poco definido, es especialmente propenso a


errores de desarrollador. Sin el beneficio de un proceso de compilación, el código
JavaScript se ejecuta en orden para encontrar la sintaxis u otros errores. Las herramientas
de linting como ESLint permiten a los desarrolladores descubrir problemas con su código
JavaScript sin ejecutarlo.

El motivo principal por el que se creó ESLint fue permitir a los desarrolladores crear sus
propias reglas de linting. ESLint está diseñado para tener todas las reglas completamente
configurables. Las reglas predeterminadas se escriben tal como lo haría cualquier otra
regla de linter. Si bien ESLint incluirá algunas reglas integradas para que sea útil desde el
principio, podrá cargar dinámicamente las reglas en cualquier momento.

ESLint está escrito con [Link] para proporcionar un entorno de ejecución rápido y una
instalación sencilla mediante npm o yarn.

Instalar eslint
Actualizar dependencias en [Link]

Vamos a usar devDependencies porque estas dependencias son de desarrollo.


Necesitamos agregar lo siguiente en nuestro workshop/front/[Link]:
"devDependencies": {
"babel": "^6.23.0",
"babel-core": "^6.26.0",
+ "babel-eslint": "^8.0.1",
"babel-loader": "^6.4.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"react-test-renderer": "^16.0.0",
+ "eslint": "^4.8.0",
+ "eslint-html-reporter": "^0.5.2",
+ "eslint-import-resolver-node": "^0.3.1",
+ "eslint-plugin-import": "^2.7.0",
+ "eslint-plugin-jest": "^21.2.0",
+ "eslint-plugin-react": "^7.4.0",
"webpack": "^1.12.13",
"webpack-bundle-tracker": "0.0.93",
"webpack-dev-server": "^1.14.1"
...

Actualizar scripts de [Link]

Necesitamos agregar lo siguiente en nuestro workshop/front/[Link]:


"scripts": {
"start": "node [Link]",
- "react-devtools": "react-devtools"
+ "react-devtools": "react-devtools",
+ "eslint": "./node_modules/.bin/eslint --ext .jsx --ext .js src",
+ "eslint-report": "./node_modules/.bin/eslint -f
node_modules/eslint-html-reporter/[Link] -o [Link] --ext .jsx
--ext .js src || true"
},

Instalar dependencias
# con docker
docker exec -it workshopjs yarn install

# sin docker
cd workshop/front
yarn install

Crear .[Link]
El archivo .[Link] tiene todas las reglas para eslint. Yo voy a subir
mi .[Link] (en workshop/front/.[Link]) pero vos podes crearte uno con:

• generador
• o crear .[Link] con el comando eslint:

# con docker
docker exec -it workshopjs ./node_modules/.bin/eslint --init

# sin docker
cd workshop/front
./node_modules/.bin/eslint --init

Actualizar gitignore
Y finalmente vamos a actualizar el archivo .gitignore y
agregar workshop/front/[Link].

Resultado
En este punto, podes ejecutar eslint.

Ejecutar eslint con reporte en consola


# con docker
docker exec -it workshopjs npm run eslint

# sin docker
cd workshop/front
npm run eslint
Leer reporte en consola de eslint

El comando eslint retorna:

/src/workshop/front/src/components/App/[Link]
1:19 error Strings must use singlequote quotes
3:22 error Strings must use singlequote quotes

/src/workshop/front/src/components/Headline/[Link]
1:19 error Strings must use singlequote quotes
6:24 error 'children' is missing in props validation react/prop-
types

/src/workshop/front/src/containers/[Link]
1:19 error Strings must use singlequote quotes
3:26 error Strings must use singlequote quotes

/src/workshop/front/src/views/[Link]
1:19 error Strings must use singlequote quotes
2:24 error Strings must use singlequote quotes

✖ 8 problems (8 errors, 0 warnings)


7 errors, 0 warnings potentially fixable with the `--fix` option.

Corrección automática de errores

Eslint tiene una función que arregle automaticamente algunos errores

# con docker
docker exec -it workshopjs ./node_modules/.bin/eslint --ext .jsx --ext
.js src --fix

# sin docker
cd workshop/front
./node_modules/.bin/eslint --ext .jsx --ext .js src --fix
Leer reporte en consola de eslint despues de --fix

El comando eslint retorna:

/src/workshop/front/src/components/Headline/[Link]
6:24 error 'children' is missing in props validation react/prop-
types

✖ 1 problems (1 errors, 0 warnings)

Ejecutar eslint con reporte html


# con docker
docker exec -it workshopjs npm run eslint-report
# sin docker
cd workshop/front
npm run eslint-report
Leer reporte html de eslint

Abri workshop/front/[Link] en tu navegador.

Y finalmente, podrías arreglar los eslints que el linter nos devuelve. Si quieres, podrías ver
cómo los soluciono en este commit: correcciones

Paso 11: Python testing

Paso 11: Python testing


Volver al paso 10

Tests framework:Pytest
El framework pytest hace que sea más fácil escribir pequeñas pruebas, pero puede
escalar para soportar pruebas funcionales complejas para aplicaciones y bibliotecas.

Instalar dependencias para pytest


Instalamos pytest, pytest-django, pytest-cov (para reporte de cobertura)
y django_dynamic_fixture (para testing con los modelos de Django e intereacción con la
db):

pip install pytest pytest-django pytest-cov django_dynamic_fixture

Actualizar requirements-dev

Usamos requirements-dev porque estas dependencias son dependencias de desarrollo.

# con docker
docker exec -it workshop pip freeze | grep pytest >> requirements-
[Link]
docker exec -it workshop pip freeze | grep django-dynamic-fixture >>
[Link]

# sin docker
pip freeze | grep pytest >> [Link]
pip freeze | grep django-dynamic-fixture >> [Link]

Crear .coveragerc
El archivo .coveragerc tiene la configuración para pytest-cov. Necesitamos agregar lo
siguiente en nuestro workshop/.coveragerc:
[run]
source =
links/
workshop/
omit =
links/migrations/*
links/test_*
links/tests/*
workshop/migrations/*
workshop/test_*
workshop/tests/*

Crear tests
Vamos a crear una nueva funcionalidad y crear tests para esa funcionalidad.

Crear nueva funcionalidad

Vamos a crear funciones para buscar Tags similares. Necesitamos agregar lo siguiente en
nuestro workshop/links/[Link]:

"""
Constants for link application
"""
SIMILAR_RATIO = 0.5

En workshop/links/[Link]:

"""
Utils module for link application
"""
import difflib
from .constant import SIMILAR_RATIO

def is_similar(source, target):


"""
Return if source string is similar to target string
"""
seq = [Link](a=source, b=target)
ratio = [Link]()
return ratio >= SIMILAR_RATIO

En workshop/links/[Link]:

"""
Utils module for link application
"""
import difflib
from .constant import SIMILAR_RATIO

def is_similar(source, target):


"""
Return if source string is similar to target string
"""
seq = [Link](a=source, b=target)
ratio = [Link]()
return ratio >= SIMILAR_RATIO

En workshop/links/[Link]:

from [Link] import models


from [Link] import User
from [Link] import ugettext as _
+from [Link] import is_similar

class Tag([Link]):
@@ -19,6 +20,14 @@ class Tag([Link]):
def __str__(self):
return [Link]

+ def get_similars(self):
+ """
+ Return similars links (search for name)
+ """
+ tags = [Link]().exclude(pk=[Link])
+ similars = [tag for tag in tags if is_similar([Link],
[Link])]
+ return similars
+

Crear tests:

Notas:

Estos conceptos son importantes para este paso:

• [Link]
• [Link].django_db
• django_dynamic_fixture

Crear carpeta de tests:

Vamos a crear la carpeta workshop/links/tests/ y el


archivo workshop/links/tests/[Link].

Crear tests para utils

En workshop/links/tests/test_utils.py:

"""
Tests for utils module
"""
from [Link] import patch
import pytest

from [Link] import is_similar


def test_is_similar_should_return_true_when_strings_are_similars():
assert is_similar('python', 'pytohn')

def
test_is_similar_should_return_false_when_strings_not_are_similars():
assert not is_similar('python', 'javascript')

@[Link].unit_tests
@patch('[Link]')
def
test_is_similar_should_call_SequenceMatcher(sequence_matcher_mock):
sequence_matcher_mock().ratio.return_value = 4
is_similar('python', 'pytohn')
sequence_matcher_mock.assert_called()
sequence_matcher_mock.assert_called_with(a='python', b='pytohn')

Crear tests para el modelo Tag

En workshop/links/tests/test_tag.py:

"""
Tests for Tag model
"""
from [Link] import patch
from django_dynamic_fixture import G

import pytest

from [Link] import Tag

@[Link].django_db
def test_get_similars_should_omit_self():
tag_python = G(Tag, name='python')
assert tag_python.get_similars() == []

@[Link].django_db
def test_get_similars_should_return_similars():
tag_python = G(Tag, name='python')
tag_pytohn = G(Tag, name='pytohn')
tag_pyton = G(Tag, name='pyton')
tag_ypthon = G(Tag, name='ypthon')
assert tag_python.get_similars() == [tag_pytohn, tag_pyton,
tag_ypthon]

@[Link].django_db
def test_get_similars_should_not_return_strings_with_differences():
tag_python = G(Tag, name='python')
G(Tag, name='react')
G(Tag, name='javascript')
assert tag_python.get_similars() == []

@[Link].django_db
@[Link].unit_tests
@patch('[Link].is_similar')
def test_get_similars_should_call_is_similar(similar_mock):
tag = G(Tag, name='python')
G(Tag, name='python2')
tag.get_similars()
similar_mock.assert_called()

@[Link].django_db
@[Link].unit_tests
@patch('[Link].is_similar')
def
test_get_similars_should_call_is_similar_will_all_other_tags(similar_m
ock):
tag_python = G(Tag, name='python')
tag_python2 = G(Tag, name='python2')
tag_python3 = G(Tag, name='python3')
tag_python.get_similars()
similar_mock.assert_called()
assert similar_mock.call_count == 2
similar_mock.assert_any_call(tag_python.name, tag_python2.name)
similar_mock.assert_any_call(tag_python.name, tag_python3.name)

Crear configuración de pytests y de django-dynamic-


fixture
Vamos a crear el archivo workshop/[Link] con este contenido:

[tool:pytest]
addopts = --ds=[Link]

Actualizar reglas de pylintrc


Vamos a agregar la carpeta de tests a la regla de ignore de pylint:

-ignore=[Link], [Link], [Link], migrations


+ignore=[Link], [Link], [Link], migrations, tests

Actualizar gitignore
Y finalmente tenemos que actualizar el archivo .gitignore y
agregarle .coverage, .cache y htmlcov/.

Resultado
En este punto, podemos ejecutar pytest y leer los reportes.

Ejecutar pytest
# con docker
docker exec -it workshop bash -c 'cd workshop; [Link] --cov-report
term-missing --cov-report html --cov'

# sin docker
cd workshop
[Link] --cov-report term-missing --cov-report html --cov

Leer reporte de pytest

El comando pytest retorna:

============================= test session starts


==============================
platform linux -- Python 3.6.3, pytest-3.2.3, py-1.4.34, pluggy-0.4.0
Django settings: [Link] (from command line option)
rootdir: /src/workshop, inifile: [Link]
plugins: django-3.1.2, cov-2.5.1
collected 8 items

links/tests/test_tag.py .....
links/tests/test_utils.py ...

----------- coverage: platform linux, python 3.6.3-final-0 -----------


Name Stmts Miss Cover Missing
----------------------------------------------------
links/__init__.py 0 0 100%
links/[Link] 10 0 100%
links/[Link] 3 3 0% 5-12
links/[Link] 1 0 100%
links/[Link] 36 2 94% 21, 48
links/[Link] 1 1 0% 1
links/[Link] 3 3 0% 1-4
links/[Link] 6 0 100%
links/[Link] 0 0 100%
workshop/__init__.py 0 0 100%
workshop/[Link] 24 0 100%
workshop/[Link] 3 3 0% 16-19
workshop/[Link] 4 4 0% 10-16
----------------------------------------------------
TOTAL 91 16 82%
Coverage HTML written to dir htmlcov

=========================== 8 passed in 2.88 seconds


===========================

Leer reporte html de pytest

Vamos a abrir workshop/htmlcov/[Link] en el navegador para ver el reporte html.

Paso 12: React testing

Paso 12: React testing


Volver al paso 11
Framework de tests:
• Jest.
• Enzyme.

Instalar jest y enzyme


Actualizar dependencias en el [Link]

Usamos devDependencies porque estas dependencias porque son dependencias de


desarrollo. Necesitamos agregar lo siguiente en
nuestro workshop/front/[Link]:
"devDependencies": {
"babel": "^6.23.0",
"babel-core": "^6.26.0",
"babel-eslint": "^8.0.1",
+ "babel-jest": "^21.2.0",
"babel-loader": "^6.4.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
+ "enzyme": "^3.1.0",
+ "enzyme-adapter-react-16": "^1.0.1",
"react-test-renderer": "^16.0.0",
"eslint": "^4.8.0",
"eslint-html-reporter": "^0.5.2",
...
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-jest": "^21.2.0",
"eslint-plugin-react": "^7.4.0",
+ "jest": "^21.2.1",
"webpack": "^1.12.13",
"webpack-bundle-tracker": "0.0.93",
"webpack-dev-server": "^1.14.1"
...
"babel-polyfill": "^6.26.0",
"es6-promise": "^4.1.1",
"isomorphic-fetch": "^2.2.1",
+ "jest-cli": "^21.2.1",
"js-cookie": "^2.1.4",
"lodash": "^4.17.4",
"prop-types": "^15.6.0",
...

Actualizar scripts del [Link]

Necesitamos agregar lo siguiente en nuestro workshop/front/[Link]:


"scripts": {
"start": "node [Link]",
- "react-devtools": "react-devtools"
+ "react-devtools": "react-devtools",
+ "eslint": "./node_modules/.bin/eslint --ext .jsx --ext .js src",
+ "eslint-report": "./node_modules/.bin/eslint -f
node_modules/eslint-html-reporter/[Link] -o [Link] --ext .jsx
--ext .js src || true"
},

Actualizar [Link] con la configuración de jest

Necesitamos agregar lo siguiente en nuestro workshop/front/[Link]:


+ "jest": {
+ "globals": {
+ "__PLATFORM__": "test"
+ },
+ "setupFiles": [
+ "raf/polyfill"
+ ],
+ "setupTestFrameworkScriptFile": "<rootDir>/[Link]",
+ "modulePaths": [
+ "node_modules",
+ "src"
+ ],
+ "clearMocks": true,
+ "verbose": true,
+ "coverageReporters": [
+ "html",
+ "text",
+ "text-summary"
+ ],
+ "transform": {
+ "^.+\\.jsx?$": "babel-jest"
+ },
+ "moduleFileExtensions": [
+ "js",
+ "jsx",
+ "json",
+ "es6"
+ ],
+ "unmockedModulePathPatterns": [
+ "<rootDir>/node_modules/timeout-transition-group",
+ "<rootDir>/node_modules/expect",
+ "<rootDir>/node_modules/classnames",
+ "<rootDir>/node_modules/sinon",
+ "<rootDir>/node_modules/redux",
+ "<rootDir>/node_modules/redux-thunk",
+ "<rootDir>/node_modules/react",
+ "<rootDir>/node_modules/react-tools",
+ "<rootDir>/node_modules/react-devtools",
+ "react",
+ "enzyme",
+ "jest-enzyme"
+ ],
+ "modulePathIgnorePatterns": [
+ "/node_modules/",
+ "[Link]"
+ ],
+ "collectCoverageFrom": [
+ "src/components/**/*.{js,jsx}",
+ "src/containers/**/*.{js,jsx}"
+ ]
+ }
Instalar las dependencias
# con docker
docker exec -it workshopjs yarn install

# sin docker
cd workshop/front
yarn install

Crear el script de configuración de jest


[Link] (en workshop/front/[Link]) es el script de configuración de jest.

import Enzyme from 'enzyme'


import Adapter from 'enzyme-adapter-react-16'

[Link]({ adapter: new Adapter() })

[Link] = [Link](text => text)


global.$ = [Link]()

Crear tests
Crear tests para la componente Headline

En workshop/front/src/components/Headline/[Link]:

import React from 'react';


import Headline from './[Link]';
import { shallow } from 'enzyme';

describe('Headline Component', () => {

describe('props', () => {

it('should declare propsTypes', () => {


expect([Link]([Link])).toHaveLength(1);
expect([Link]).toHaveProperty('children');
})

})

describe('render', () => {

it('should render the component properly', () => {


const wrapper = shallow(<Headline>Sample App!</Headline>);
const componentInDOM = '<h1>Sample App!</h1>';
expect([Link]()).toBe(componentInDOM);
})

})

})

Crear tests para la componente App


En workshop/front/src/components/App/[Link]:

import React from 'react';


import App from './[Link]';
import { shallow } from 'enzyme';

describe('App Component', () => {

describe('#render', () => {

it('should render the component properly', () => {


const wrapper = shallow(<App/>);
const componentInDOM = '<div class="container"><div
class="row"><div class="col-sm-12"><h1>Sample
App!</h1></div></div></div>';
expect([Link]()).toBe(componentInDOM);
})

})

})

Crear tests para el container App

En workshop/front/src/containers/[Link]:

import React from 'react';


import App from './[Link]';
import { shallow } from 'enzyme';

describe('App Component', () => {

describe('#render', () => {

it('should render the component properly', () => {


const wrapper = shallow(<App/>);
const componentInDOM = '<div class="container"><div
class="row"><div class="col-sm-12"><h1>Sample
App!</h1></div></div></div>';
expect([Link]()).toBe(componentInDOM);
})

})

})

Actualizar gitignore
Y finalmente tenemos que actualizar el archivo .gitignore y
agregarle coverage/ y jest_*.

Resultado
En este punto, ya podemos ejecutar jest y leer el reporte de cobertura.
Ejectutar jest
# con docker
docker exec -it workshopjs npm test

# sin docker
cd workshop/front
npm test

Leer el reporte de jest

El comando jest nos retornar:

npm info it worked if it ends with ok


npm info using [email protected]
npm info using [email protected]
npm info lifecycle [email protected]~pretest: [email protected]
npm info lifecycle [email protected]~test: [email protected]

> [email protected] test /src/workshop/front


> jest --forceExit --ci --coverage

PASS src/components/Headline/[Link]
Headline Component
props
✓ should declare propsTypes (4ms)
render
✓ should render the component properly (7ms)

PASS src/components/App/[Link]
App Component
#render
✓ should render the component properly (12ms)

PASS src/containers/[Link]
App Component
#render
✓ should render the component properly (10ms)

Test Suites: 3 passed, 3 total


Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 2.148s
Ran all test suites.
---------------------|----------|----------|----------|----------|----
------------|
File | % Stmts | % Branch | % Funcs | % Lines
|Uncovered Lines |
---------------------|----------|----------|----------|----------|----
------------|
All files | 100 | 100 | 100 | 100 |
|
components/App | 100 | 100 | 100 | 100 |
|
[Link] | 100 | 100 | 100 | 100 |
|
components/Headline | 100 | 100 | 100 | 100 |
|
[Link] | 100 | 100 | 100 | 100 |
|
containers | 100 | 100 | 100 | 100 |
|
[Link] | 100 | 100 | 100 | 100 |
|
---------------------|----------|----------|----------|----------|----
------------|

=============================== Coverage summary


===============================
Statements : 100% ( 3/3 )
Branches : 100% ( 0/0 )
Functions : 100% ( 3/3 )
Lines : 100% ( 3/3 )
======================================================================
==========
npm info lifecycle [email protected]~posttest: [email protected]
npm info ok

Leer el reporte html de jest

Abri workshop/front/coverage/[Link] en tu navegador.

Paso 13: Agregar contexto de Django a React

Paso 13: Contexto de Django en react


Volver al paso 12

Detalles
En este paso vamos a hacer un cambio importante en los archivos que actualmente
tenemos. Lo primero es poner nombres mas intuitivos para las componentes, containers
y vistas de React. Lo segundo es agregar en [Link] en titulo y la
lista de los links que tenemos.

Cambio de nombres:
Vamos a cambiar App por LinksDetail en varias partes:

Mover archivos
# Componentes
mkdir workshop/front/src/components/LinksDetail
mv workshop/front/src/components/App/[Link]
workshop/front/src/components/LinksDetail/[Link]
mv workshop/front/src/components/App/[Link]
workshop/front/src/components/LinksDetail/[Link]
rm -r workshop/front/src/components/App

# Contenedores
mkdir workshop/front/src/containers/LinksDetail
mv workshop/front/src/containers/[Link]
workshop/front/src/containers/LinksDetail/[Link]
mv workshop/front/src/containers/[Link]
workshop/front/src/containers/LinksDetail/[Link]

# Vistas
mv workshop/front/src/views/[Link]
workshop/front/src/views/[Link]

# Templates de django
mv workshop/links/templates/[Link]
workshop/links/templates/link_detail.html

Cambiar el nombre en el codigo js

En la vista de LinksDetail (workshop/front/src/views/[Link]):

import React from 'react'


import { render } from 'react-dom'
-import App from '../containers/App'
+import LinksDetail from '../containers/LinksDetail'

-render(<App/>, [Link]('app'))
+render(<LinksDetail/>, [Link]('app'))

if ([Link]) [Link]();

En el contenedor
de LinksDetail (workshop/front/src/containers/LinksDetail/[Link]):

import React from 'react'

-import AppComponent from '../components/App'


+import LinksDetailComponent from '../../components/LinksDetail'

-export default class App extends [Link] {


+export default class LinksDetail extends [Link] {
render() {
return (
- <AppComponent />
+ <LinksDetailComponent />
)
}
}

En la componente
de LinksDetail (workshop/front/src/components/LinksDetail/[Link]):

import React from 'react'

import Headline from '../Headline'

-export default class App extends [Link] {


+export default class LinksDetail extends [Link] {
render() {
return (
<div className="container">
<div className="row">
<div className="col-sm-12">
<Headline>Sample App!</Headline>
</div>
</div>
</div>

Actualizar configuración de webpack

En workshop/front/[Link]:

entry: {
// Add as many entry points as you have container-react-components
here
- App: ['./src/views/App'],
+ LinksDetail: ['./src/views/LinksDetail'],
vendors: ['react', 'babel-polyfill'],
},

En workshop/front/[Link]:

// Use webpack dev server


[Link] = {
- App: addDevVendors('./src/views/App'),
+ LinksDetail: addDevVendors('./src/views/LinksDetail'),
vendors: ['react', 'babel-polyfill'],
}

Actualizar la template de link_detail

En workshop/links/templates/link_detail.html:

{% render_bundle 'vendors' %}
- {% render_bundle 'App' %}
+ {% render_bundle 'LinksDetail' %}

Crear la pagina de links con la lista de links


Agregar el titulo a la pagina

En workshop/links/templates/[Link]:

<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
...
+ {% block head %}{% endblock %}
</head>

En workshop/links/templates/link_detail.html:
{% extends "[Link]" %}
{% load render_bundle from webpack_loader %}

+{% block head %}


+ <title>Links Detail</title>
+{% endblock %}
+
{% block main %}
<div id="app"></div>

Tomar contexto de Django en React


Vamos a crear una función (render_components) que nos va permitir pasarle parametros
a las componentes. La función render_components es llamada en la template de Django
con los parametros que queremos pasarle a React. Vamos a crearla
en workshop/front/src/views/[Link]:

import LinksDetail from '../containers/LinksDetail'

-render(<LinksDetail/>, [Link]('app'))
+window.render_components = properties => {
+ [Link] = {...properties};
+ render(<LinksDetail links={[Link]}/>,
[Link]('app'));
+};

-if ([Link]) [Link]();


+if ([Link]) {
+ if ([Link]) window.render_components([Link]);
+ [Link]();
+}

Nota: a la componente LinksDetail se le esta pasando el parametro links que viene en las
properties que se le mandaron a la función render_components

Agregar el parametro links al container y a la componente LinksDetail

En workshop/front/src/containers/LinksDetail/[Link]:

import React from 'react'


+import PropTypes from 'prop-types';

import LinksDetailComponent from '../../components/LinksDetail'

export default class LinksDetail extends [Link] {


+ static propTypes = {
+ links: [Link]
+ }
+
render() {
+ const { links } = [Link];
return (
- <LinksDetailComponent />
+ <LinksDetailComponent links={links} />
)
}
}

En workshop/front/src/components/LinksDetail/[Link]:

import React from 'react'


+import PropTypes from 'prop-types';

import Headline from '../Headline'

export default class LinksDetail extends [Link] {


+ static propTypes = {
+ links: [Link](
+ [Link]({
+ pk: [Link]
+ })
+ )
+ }
+
render() {
return (
<div className="container">
<div className="row">
<div className="col-sm-12">
- <Headline>Sample App!</Headline>
+ <Headline>Links</Headline>
</div>
</div>
</div>

Enviar contexto desde Django


Vista de Django

Vamos a agregar una vista de Django para enviar la lista de links.


En workshop/links/[Link]:

"""
Django views for link application
"""
from [Link] import render
from [Link] import serializers
from .models import Link

def links_detail(request):
"""
Links list
"""
links = [Link]()
links_json = [Link]('json', links)
return render(
request,
'link_detail.html',
context={
'links': links_json
})
Agregar la url para la vista que creamos

En workshop/links/[Link]:

from [Link] import path


from [Link] import generic
from . import views

urlpatterns = [
path('view2/',
[Link].as_view(template_name='[Link]')),
- path('view2/',
- [Link].as_view(template_name='[Link]')),
+ path('', views.links_detail)
]

Enviar desde la template el contexto a React

En workshop/links/templates/link_detail.html:

{% render_bundle 'vendors' %}
{% render_bundle 'LinksDetail' %}
+ <script>
+ window.render_components({
+ links: {{ links|safe }}
+ });
+ </script>
{% endblock %}

Mostrar los links en la pantalla de Links Detail


Vamos a crear una componente para mostrar el detalle de un link.
En workshop/front/src/components/LinkDetail/[Link]:

import React from 'react'


import PropTypes from 'prop-types';

export default class LinkDetail extends [Link] {


static propTypes = {
link: [Link]({
fields: [Link]({
url: [Link],
name: [Link],
})
})
}

render() {
const { link } = [Link];
return (
<p>
{[Link]}: <a
href={[Link]}>{[Link]}</a>
</p>
)
}
}

Agregar LinkDetail a LinksDetail

En workshop/front/src/components/LinksDetail/[Link]:

Importar LinkDetail
import Headline from '../Headline'
+import LinkDetail from '../LinkDetail'
Mostrar LinkDetail de cada link
render() {
+ const { links } = [Link];
+ const linksItems = [Link](link => <LinkDetail key={[Link]}
link={link} />);
return (
<div className="container">
<div className="row">
<div className="col-sm-12">
<Headline>Links</Headline>
+ { linksItems }
</div>
</div>
</div>

Actualizar test
Como esto es no tan impotante en este paso esta en otro archivo, si queres ver como se
actualizaron los tests podes verlo en Actualización de tests

Resultado
En este punto, ya podemos ejecutar el servidor y ver la lista de links
en [Link]

En una terminal corremos el servidor de React


# con docker
docker exec -it workshopjs npm start

# sin docker
cd workshop/front
npm start

En otra terminal corremos el servidor de Django


# con docker
docker exec -it workshop ./workshop/[Link] runserver [Link]:8000

# sin docker
./workshop/[Link] runserver
Deberías ver la página de links detail con los links que tengas cargados en el navegador
en [Link]
Paso 14: Api rest

Paso 14: Api rest


Volver al paso 13

En este paso vamos a agregar una api rest del lado de Django
con djangorestframework y tomar los datos desde React con fetch

Agregar api rest en Django


Instalar django rest framework

Si queres podes usar la documentación oficial para instalar: instalación

# con docker
docker exec -it workshop pip install djangorestframework markdown
django-filter

# sin docker
pip install djangorestframework markdown django-filter

Actualizar requirements

Usamos requirements porque estas dependencias son dependencias de producción.

# con docker
docker exec -it workshop pip freeze | grep rest >> [Link]
docker exec -it workshop pip freeze | grep filter >> [Link]

# sin docker
pip freeze | grep rest >> [Link]
pip freeze | grep filter >> [Link]

Agregar django-rest-framework a la configuración de Django

En workshop/workshop/[Link] lo agregamos a INSTALLED_APPS


INSTALLED_APPS = [
...
'[Link]',
'links',
'webpack_loader',
+ 'rest_framework',
]

Al final del archivo de settings, agregamos la configuración:

+
+REST_FRAMEWORK = {
+ 'DEFAULT_PERMISSION_CLASSES': [
+
'rest_framework.[Link]'
+ ]
+}

Agregar serializers y viewsets

El serializer define como se van a mostrar un modelo en la api y el Viewset define de donde
vamos a tomar los datos para mostrar. La documentación de Viewsets y serializers:

• Viewsets: [Link]
• Serializers: [Link]

En el archivo workshop/links/[Link]:
"""
Api module with serializers and viewsets for models
"""
# pylint: disable=too-many-ancestors
# pylint: disable=missing-docstring
from [Link] import User
from rest_framework import serializers, viewsets

from .models import Link, Tag, LinkTag

# Serializers define the API representation.


class LinkTagSerializer([Link]):
class Meta:
model = LinkTag
fields = ('url', 'link', 'tag')

class LinkSerializer([Link]):
class Meta:
model = Link
fields = ('id', 'url', 'name', 'url', 'pending',
'description', 'tags', 'user')

class TagSerializer([Link]):
class Meta:
model = Tag
fields = ('id', 'url', 'name', 'description', 'user')

class UserSerializer([Link]):
class Meta:
model = User
fields = ('url', 'username', 'email', 'is_staff')

# ViewSets define the view behavior.


class LinkTagViewSet([Link]):
queryset = [Link]()
serializer_class = LinkTagSerializer

class LinkViewSet([Link]):
queryset = [Link]()
serializer_class = LinkSerializer

class TagViewSet([Link]):
queryset = [Link]()
serializer_class = TagSerializer

class UserViewSet([Link]):
queryset = [Link]()
serializer_class = UserSerializer

Agregar api a las urls

En el archivo workshop/links/[Link]:
-from [Link] import url
+from [Link] import url, include
from [Link] import generic
+from rest_framework import routers
+
from . import views
+from .api import LinkTagViewSet, LinkViewSet, TagViewSet, UserViewSet
+
+
+# Routers provide a way of automatically determining the URL conf.
+router = [Link]()
+[Link](r'users', UserViewSet)
+[Link](r'links', LinkViewSet)
+[Link](r'tags', TagViewSet)
+[Link](r'linktags', LinkTagViewSet)
+

urlpatterns = [
path('view2/',
[Link].as_view(template_name='[Link]')),
path('', views.links_detail),
+ path('api/', include([Link]))
]

Crear boton para actualizar los datos


Crear la componente Button

En workshop/front/src/components/Button/[Link]:
import React from 'react'
import PropTypes from 'prop-types';

export default class Button extends [Link] {


static propTypes = {
onClick: [Link],
label: [Link]
}

_onClick = event => {


[Link]();
const { onClick } = [Link];
onClick();
}

render() {
const { label } = [Link];
return (
<button className='btn btn-success' type='button'
onClick={this._onClick}>
{ label }
</button>
)
}
}

Crear una utils para pedir data de laapi

En workshop/front/src/utils/[Link]:
export function getUrl(url){
return fetch(url).then(resp => [Link]())
}

Crear una utils con las urls de la api

En workshop/front/src/utils/[Link]:
export const API_URL = '/links/api/'
export const LINKS_API_URL = `${API_URL}links/`

Agregar el boton la pagina de links

En workshop/front/src/components/LinksDetail/[Link]:
import Headline from '../Headline'
import LinkDetail from '../LinkDetail'
+import Button from '../Button'

export default class LinksDetail extends [Link] {


static propTypes = {
+ onRefresh: [Link],
links: [Link](
...
}

render() {
- const { links } = [Link];
+ const { links, onRefresh } = [Link];
const linksItems = [Link](link => <LinkDetail key={[Link]}
link={link} />);
return (
<div className="container">
<div className="row">
<div className="col-sm-12">
<Headline>Links</Headline>
- { linksItems }
+ <Button onClick={onRefresh} label='Refresh'/>
+ <div style={{marginTop: 20}}>
+ { linksItems }
+ </div>
</div>
</div>
</div>
Agregar funcionalidad al boton para actualizar links

En workshop/front/src/containers/LinksDetail/[Link]:
import PropTypes from 'prop-types';

import LinksDetailComponent from '../../components/LinksDetail'


+ import { LINKS_API_URL } from '../../utils/urls'
+ import { getUrl } from '../../utils/api'
+

export default class LinksDetail extends [Link] {

+ constructor(props) {
+ super(props);
+ const { links } = [Link];
+ [Link] = {
+ links: [...links]
+ }
+ }
+
+
+ _onRefresh = () => {
+ getUrl(LINKS_API_URL)
+ .then(newLinks => {
+ const links = [Link](link => {
+ return {
+ pk: [Link],
+ fields: link
+ }
+ });
+ [Link]({links});
+ })
+ }

render() {
const { links } = [Link];
return (
- <LinksDetailComponent links={links} />
+ <LinksDetailComponent links={links} onRefresh={this._onRefresh}/>
)
}
}

Actualizar test
Como esto es no tan impotante en este paso, esta en otro archivo. Si queres ver como se
actualizaron los tests podes verlo en Actualización de tests

Resultado
En este punto, ya podemos ejecutar el servidor, ver la lista de links
en [Link] y usar el boton actualizar.

En una terminal corremos el servidor de React


# con docker
docker exec -it workshopjs npm start

# sin docker
cd workshop/front
npm start

En otra terminal corremos el servidor de Django


# con docker
docker exec -it workshop ./workshop/[Link] runserver [Link]:8000

# sin docker
./workshop/[Link] runserver
Deberías ver la página de links detail con los links que tengas cargados en el navegador
en [Link] y el boton para actualizar. Podes probar de
cambiar algo en el admin (en [Link] y tocar el
boton Refresh para actualizar la lista.

Paso 15: Django channels y websockets

Paso 15: Django channels y websockets


Volver al paso 14

En este paso vamos a agregar data binding con django channels y tomar los datos desde
React con react-websocket

Agregar data binding en Django (con channels)


Instalar django channels
# con docker
docker exec -it workshop pip install channels

# sin docker
pip install channels

Actualizar requirements

Usamos requirements porque estas dependencias son dependencias de producción.

# con docker
docker exec -it workshop pip freeze | grep channels >> [Link]

# sin docker
pip freeze | grep channels >> [Link]

Agregar channels a la configuración de Django


En workshop/workshop/[Link] lo agregamos a INSTALLED_APPS
INSTALLED_APPS = [
...
'[Link]',
'links',
'webpack_loader',
'rest_framework',
+ 'channels',
]

Al final del archivo de settings, agregamos la configuración:

+
+CHANNEL_LAYERS = {
+ "default": {
+ "BACKEND": "[Link]",
+ "ROUTING": "[Link].channel_routing",
+ },
+}

Agregar clases para binding de los modelos

En el archivo workshop/links/[Link]:
"""
Bindings module
"""
# pylint: disable=missing-docstring

from [Link] import WebsocketBinding

from .models import Link, Tag, LinkTag

class LinkBinding(WebsocketBinding):
model = Link
stream = 'links'
fields = ['id', 'name', 'url', 'pending',
'description', 'tags', 'user']

@classmethod
def group_names(cls, instance):
return ['link-updates']

def has_permission(self, user, action, pk):


return True

class LinkTagBinding(WebsocketBinding):
model = LinkTag
stream = 'linktags'
fields = ['id', 'link', 'tag']

@classmethod
def group_names(cls, instance):
return ['linktags-updates']

def has_permission(self, user, action, pk):


return True
class TagBinding(WebsocketBinding):
model = Tag
stream = 'tags'
fields = ['id', 'name', 'description', 'user']

@classmethod
def group_names(cls, instance):
return ['tags-updates']

def has_permission(self, user, action, pk):


return True

Agregar rutas para los bindings

En el archivo workshop/workshop/[Link]:
"""
Routing Module with all Demultiplexers and channel_routing for
djnago-channels
"""
# pylint: disable=missing-docstring

from [Link] import WebsocketDemultiplexer


from [Link] import route_class

from [Link] import LinkBinding, TagBinding, LinkTagBinding

class APIDemultiplexer(WebsocketDemultiplexer):
consumers = {
'links': [Link],
'tags': [Link],
'linktags': [Link],
}

def connection_groups(self, **kwargs):


return ['link-updates', 'tag-updates', 'linktag-updates']

class LinkTagDemultiplexer(WebsocketDemultiplexer):
consumers = {
'linktags': [Link]
}

def connection_groups(self, **kwargs):


return ['linktag-updates']

class LinkDemultiplexer(WebsocketDemultiplexer):
consumers = {
'links': [Link]
}

def connection_groups(self, **kwargs):


return ['link-updates']

class TagDemultiplexer(WebsocketDemultiplexer):
consumers = {
'tags': [Link]
}

def connection_groups(self, **kwargs):


return ['tag-updates']

# pylint: disable=invalid-name
channel_routing = [
route_class(APIDemultiplexer, path='^/updates/$'),
route_class(LinkTagDemultiplexer, path='^/updates/linktags/$'),
route_class(LinkDemultiplexer, path='^/updates/links/$'),
route_class(TagDemultiplexer, path='^/updates/tags/$'),
]

Agregar websockets a React


Agregar urls

En workshop/front/src/utils/[Link]:
export const API_URL = '/links/api/'
export const LINKS_API_URL = `${API_URL}links/`
+
+export const WS_URL = `[Link]
+export const LINKS_WS_URL = `${WS_URL}updates/links/`

Agregar react-websocket a la pagina de links

Remplazar el
archivo workshop/front/src/containers/LinksDetail/[Link] con:
import React from 'react'
import PropTypes from 'prop-types';
import Websocket from 'react-websocket';

import LinksDetailComponent from '../../components/LinksDetail'


import { LINKS_API_URL, LINKS_WS_URL } from '../../utils/urls'
import { getUrl } from '../../utils/api'

export default class LinksDetail extends [Link] {


static propTypes = {
links: [Link]
}

constructor(props) {
super(props);
const { links } = [Link];
[Link] = {
links: [...links]
}
}

getLink = (id, fields) => {return {id, fields}};

_onRefresh = () => {
getUrl(LINKS_API_URL)
.then(newLinks => {
const links = [Link](link => [Link]([Link],
link));
[Link]({links});
})
}

_onUpdate = event => {


const { links } = [Link];
const {payload: {action, data, pk}} = [Link](event);
let newLinks = [...links];
switch (action) {
case 'update':
newLinks = [Link](link => {
if ([Link] === pk) return [Link](pk, data);
return link;
})
break;
case 'create':
[Link]([Link](pk, data))
break;
case 'delete':
newLinks = [Link](link => [Link] !== pk);
break;
}
[Link]({links: newLinks});
}

render() {
const { links } = [Link];
return (
<div>
<LinksDetailComponent links={links}
onRefresh={this._onRefresh}/>
<Websocket url={LINKS_WS_URL} onMessage={this._onUpdate}/>
</div>
)
}
}

Actualizar test
Como esto es no tan impotante en este paso, esta en otro archivo. Si queres ver como se
actualizaron los tests podes verlo en Actualización de tests

Resultado
En este punto, ya podemos ejecutar el servidor, ver la lista de links
en [Link]

En una terminal corremos el servidor de React


# con docker
docker exec -it workshopjs npm start

# sin docker
cd workshop/front
npm start

En otra terminal corremos el servidor de Django


# con docker
docker exec -it workshop ./workshop/[Link] runserver [Link]:8000

# sin docker
./workshop/[Link] runserver
Deberías ver la página de links detail con los links que tengas cargados en el navegador
en [Link] Podes probar de cambiar algo en el admin
(en [Link] y automaticamente se va a cambiar
en el frontend. Podes probar creando, borrando o modificando links en el admin.

Paso 16: Agregar Redux

Paso 16: Agregar redux


Volver al paso 15

Este paso es solo una ventaja, de verdad. Es posible que desee utilizar alguna otra
implementación de flujo para administrar el estado de sus componentes, pero Redux es
realmente agradable de trabajar y el estándar de facto en este momento. La
documentación oficial de Redux es mucho mejor que cualquier cosa que pueda crear, así
que podrias leer eso pero en este caso vamos a armar un ejemplo para que veas el
funcionamiento de Redux. Documentación oficial (en
español): [Link]

Instalar redux
Instalar redux como dependencia
# con docker
docker exec -it workshopjs yarn add react-redux redux redux-thunk --
save
docker exec -it workshopjs yarn add redux-devtools --save-dev

# sin docker
cd workshop/front
yarn add react-redux redux redux-thunk --save
yarn add redux-devtools --save-dev

Reducers, actions y store de Redux

Concepto (en español): glosario

Crear acciones
En workshop/front/src/actions/[Link]:

export const SET_LINKS = 'SET_LINKS';


export const UPDATE_LINK = 'UPDATE_LINK';
export const DELETE_LINK = 'DELETE_LINK';
export const CREATE_LINK = 'CREATE_LINK';

export function setLinks(links){


return {
type: SET_LINKS,
links
}
}

export function updateLink(link){


return {
type: UPDATE_LINK,
link
}
}

export function deleteLink(link){


return {
type: DELETE_LINK,
link
}
}

export function createLink(link){


return {
type: CREATE_LINK,
link
}
}

const linksActions = {
setLinks,
createLink,
updateLink,
deleteLink,
DELETE_LINK,
CREATE_LINK,
UPDATE_LINK,
SET_LINKS
}
export default linksActions

Crear reducers

En workshop/front/src/reducers/[Link]:

import { combineReducers } from 'redux'


import links from './links'

export default combineReducers({


links
})

En workshop/front/src/reducers/[Link]:
import {
SET_LINKS,
UPDATE_LINK,
DELETE_LINK,
CREATE_LINK,
} from '../actions/linksActions'
import { getLink } from '../utils/links'

const initState = {
links: []
}

export default (state = initState, action) => {


const { links } = state;
switch ([Link]) {
case SET_LINKS:
return {
...state,
links: [Link]
};
case UPDATE_LINK:
return {
...state,
links: [Link](link => {
if ([Link] === [Link]) {
return getLink([Link], [Link]);
}
return link;
})
};
case CREATE_LINK:
return {
...state,
links: [...links, getLink([Link], [Link])]
};
case DELETE_LINK:
return {
...state,
links: [Link](link => [Link] !== [Link])
};
default:
return state;
}
}

Crear store

En workshop/front/src/store/[Link]:

import {compose, createStore, applyMiddleware} from 'redux'


import thunk from 'redux-thunk'
import reducer from '../reducers'

export default function linksStore() {


const middleware = compose(applyMiddleware(thunk));
const store = createStore(reducer, middleware);
return store;
}
Agregar una nueva utils con funciones para links

En workshop/front/src/utils/[Link]:

export function getLink(pk, fields){


return {pk, fields}
}

Agregar provider a la vista


En workshop/front/src/views/[Link]:

import React from 'react'


import { render } from 'react-dom'
import LinksDetail from '../containers/LinksDetail'
+import {Provider} from 'react-redux'
+import linksStore from '../store/linksStore'

+const store = linksStore();


window.render_components = properties => {
[Link] = {...properties};
- render(<LinksDetail links={[Link]}/>,
[Link]('app'));
+ render(
+ (<Provider store={store}>
+ <LinksDetail initialLinks={[Link]}/>
+ </Provider>), [Link]('app'));
};

Conectar contenedor a Redux


En el archivo workshop/front/src/containers/LinksDetail/[Link] ponemos:

import React from 'react'


import PropTypes from 'prop-types';
import Websocket from 'react-websocket';
import { connect } from 'react-redux'
import { setLinks, createLink, updateLink, deleteLink } from
'../../actions/linksActions'

import LinksDetailComponent from '../../components/LinksDetail'


import { LINKS_API_URL, LINKS_WS_URL } from '../../utils/urls'
import { getUrl } from '../../utils/api'

export class LinksDetail extends [Link] {


static propTypes = {
links: [Link],
setLinks: [Link],
createLink: [Link],
updateLink: [Link],
deleteLink: [Link],
initialLinks: [Link],
}

constructor(props) {
super(props);
const { initialLinks } = [Link];
[Link](initialLinks);
}

_onRefresh = () => {
getUrl(LINKS_API_URL)
.then(newLinks => {
const links = [Link](link => {
return {pk: [Link], fields: link}
});
[Link](links);
})
}

_onUpdate = event => {


const { payload } = [Link](event);
switch ([Link]) {
case 'update':
return [Link](payload);
case 'create':
return [Link](payload);
case 'delete':
return [Link](payload);
}
}

render() {
const { links } = [Link];
return (
<div>
<LinksDetailComponent links={links}
onRefresh={this._onRefresh}/>
<Websocket url={LINKS_WS_URL} onMessage={this._onUpdate}/>
</div>
)
}
}

function mapStateToProps(state) {
const { links } = [Link];
return { links };
}

const mapDispatchToProps = {
setLinks,
createLink,
updateLink,
deleteLink,
}
export default connect(mapStateToProps,
mapDispatchToProps)(LinksDetail)

Actualizar test
Como esto es no tan impotante en este paso, esta en otro archivo. Si queres ver como se
actualizaron los tests podes verlo en Actualización de tests
Resultado
En este punto, ya podemos ejecutar el servidor, ver la lista de links
en [Link]

En una terminal corremos el servidor de React


# con docker
docker exec -it workshopjs npm start

# sin docker
cd workshop/front
npm start

En otra terminal corremos el servidor de Django


# con docker
docker exec -it workshop ./workshop/[Link] runserver [Link]:8000

# sin docker
./workshop/[Link] runserver
Deberías ver la página de links detail con los links que tengas cargados en el navegador
en [Link] Podes probar de cambiar algo en el admin
(en [Link] y automaticamente se va a cambiar
en el frontend. Podes probar creando, borrando o modificando links en el admin. Ahora
todos los estados estan en Redux.

Paso 17: A producción

Paso 17: Producción


Volver al paso 16

Este paso es importante a la hora de poner en producción tu aplicación. Para enterder


este paso primero tenemos que tener en mente como funciona este proyecto con Django
y React.

Para producción solo vamos a tener a Django trabajando, ya que el js generado con React
se va a usar como archivos estaticos.

Configuración de produccion de Django


Nuevas dependencias

Vamos a agregar dos dependencias nuevas:

En el [Link] vamos a agregar:


asgi-redis==1.4.3
psycopg2-binary==[Link]
• asgi-redis: para poder usar redis para encolar tareas
• psycopg2-binary: para poder usar postgres como base de datos
Crear un nuevo settings para produción
Para esto vamos a crear un archivo de settings nuevo: settings_prod.py en la
carpeta workshop/workshop (la misma que el [Link] que usamos en desarrollo)
En ese archivos vamos a poner las configuraciones de producción a partir de las
configuraciones que ya tenemos, es decir importando el [Link]:
# pylint: disable=wildcard-import,unused-wildcard-import

"""
Django production settings for workshop project.

For more information on this file, see


[Link]

For the full list of settings and their values, see


[Link]
"""

import os
import socket

# Import dev settings


from [Link] import *

DEBUG = False
TEMPLATE_DEBUG = False

SECRET_KEY = [Link](
'SECRET_KEY',
'kl*@mt86$rdllg+$d633#ijwkkc49^k-hw5yxfsbtn*rdq1=l)')

ALLOWED_HOSTS = [[Link]('APP_DNS', 'localhost'),


[Link]()]

WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': 'bundles/prod/', # end with slash
'STATS_FILE': [Link](
BASE_DIR, 'front', '[Link]'),
}
}

# Database
# [Link]
DATABASES = {
'default': {
'ENGINE': '[Link].postgresql_psycopg2',
'NAME': [Link]('POSTGRES_DB', 'workshop'),
'USER': [Link]('POSTGRES_USER', 'workshop'),
'PASSWORD': [Link]('POSTGRES_PASSWORD', 'secret'),
'HOST': [Link]('POSTGRES_HOST', 'localhost'),
'PORT': [Link]('POSTGRES_PORT', '5432'),
'OPTIONS': {
'sslmode': [Link]("POSTGRES_OPTIONS_SSL",
"prefer"),
},
}
}

REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': (
'rest_framework.[Link]',
'rest_framework.[Link]',
'django_filters.rest_framework.DjangoFilterBackend',
),
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.[Link]'
],
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.[Link]',
)
}

CHANNEL_LAYERS = {
'default': {
'BACKEND': 'asgi_redis.RedisChannelLayer',
'CONFIG': {
'hosts': [(
[Link]('REDIS_HOST', 'redis'),
int([Link]('REDIS_PORT', '6379')),
)],
},
'ROUTING': '[Link].channel_routing',
}
}

LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'simple': {
'format': '%(levelname)s %(message)s'
},
'logservices': {
'format': '[%(asctime)s] [%(levelname)s] %(message)s'
}
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': '[Link]',
'formatter': 'simple'
},
'file': {
'level': 'DEBUG',
'class': '[Link]',
'filename': [Link](
'LOG_FILE',
'/var/log/workshop/[Link]'
),
'maxBytes': 1024*1024*10,
'backupCount': 10,
'formatter': 'logservices'
}
},
'loggers': {
'workshop': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': True
},
'[Link]': {
'handlers': ['file'],
'level': 'WARNING',
'propagate': True
},
'[Link]': {
'handlers': ['file'],
'level': 'WARNING',
'propagate': True
},
'django': {
'handlers': ['console'],
'level': 'WARNING',
'propagate': True
}
}
}
Cosas importantes a destacar de este archivo:

• DEBUG y DEBUG_TEMPLATES: van a estar en false ya que en producción no se


hace debug del codigo
• [Link]: usaremos variables de entorno para poder configurar medienta un
archivo .env o variables de nuestra consola
• ALLOWED_HOSTS: especificamos desde que dominio se puede acceder a esta
aplicación
• WEBPACK_LOADER: usamos el stats de producción de webpack
• DATABASES: usamos una base de datos (en este caso postgres)
• REST_FRAMEWORK: la api solo va a ser accesible en modo lectura para usuario
no logeados y va a responder JSON de forma predeterminada
• CHANNEL_LAYERS: para django-channels vamos a usar un redis
• LOGGING; usaremos una configuracion de logs acorde a producción

¿Como usar ese settings?


Para usar el settings_prod.py que armamos, primero tenemos que armar un
archivo [Link] ya que vamos a estar usando redis para poder disparar acciones y
encolar tareas.
En el archivo workshop/workshop/[Link] vamos a poner:
"""
ASGI config for workshop project.
"""

import os

from [Link] import get_channel_layer

[Link]("DJANGO_SETTINGS_MODULE", "[Link]")

# pylint: disable=invalid-name
channel_layer = get_channel_layer()
En este caso vamos a dejar como valor predeterminado
de DJANGO_SETTINGS_MODULE el [Link] original ya que podria usarse y cuando
pensemos usar la aplicación en modo producción solamente tenemos que configurar esa
variable de entorno con el valor workshop.settings_prod.

Generar los archivos js de React para usar en el


servidor productivo
Si vemos los archivos que tenemos en la carpeta workshop/front podemos ver uno que
es [Link]. Este archivo esta armado para generar los bundles finales
de js que se van a utilizar en producción.

Para crear estos bundles vamos a hacer:

cd workshop/front
webpack --config [Link]
Este comando igual lo hace el Dockerfile que ya tenemos, por ende no es necesario
correrlo a mano a menos de que no uses docker.

Servidor completo
Para empezar hay que comprender que si bien este taller da una forma, hay muchas
formas distintas de hacerlo. En este caso vamos a usar docker-compose que nos permite
controlar varios servicios y enlazarlos entre si.
Infraestructura

Antes de detallar el codigo y archivos, vamos a hablar sobre la infraestrutura que vamos
a usar:

• 2 containers con python, uno va a escuchar las peticiones de la web y otro va a


realizar las tareas (para esto vamos a usar una herramienta llamada daphne).
• 2 containers con nginx, podria simplemente usarse uno pero para hacer mas facil
la implementación del dominio y puertos, vamos a usar uno con un nginx
configurado por nosotros y otro con nginx-proxy que es una herramienta que
nos permite trabajar facilmente con el tema dominios y puertos.

• 1 container con postgres, esta es la base de datos que vamos a utilizar

• 1 container con redis

También para garantizar que los datos esten guardados vamos a usar volumenes, lo
cuales son:

• /srv/deploys/workshopdata/static: para los archivos estaticos de nuestra


aplicación

• /srv/deploys/workshopdata/postgres: para los archivos de la base de datos


Archivos base:
Vamos a crear archivos para el deploy dentro de la carpeta deploy/docker.
En esta carpeta vamos a crear dos carpetas: nginx y scripts
Scripts
Esta carpeta va a tener los scripts que vamos a utilizar en nuestro docker-compose.

Uno va a ser el script de arranque de la aplicación y otro va a ser un script que sirve para
esperar que la base de datos este andando antes de correr el otro script.

En el archivo deploy/docker/scripts/[Link]:
#!/usr/bin/env bash
# Use this script to test if a given TCP host/port are available

cmdname=$(basename $0)

echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }

usage()
{
cat << USAGE >&2
Usage:
$cmdname host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
Alternatively, you specify the host and
port as host:port
-s | --strict Only execute subcommand if the test
succeeds
-q | --quiet Don't output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test
finishes
USAGE
exit 1
}

wait_for()
{
if [[ $TIMEOUT -gt 0 ]]; then
echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT"
else
echoerr "$cmdname: waiting for $HOST:$PORT without a timeout"
fi
start_ts=$(date +%s)
while :
do
if [[ $ISBUSY -eq 1 ]]; then
nc -z $HOST $PORT
result=$?
else
(echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1
result=$?
fi
if [[ $result -eq 0 ]]; then
end_ts=$(date +%s)
echoerr "$cmdname: $HOST:$PORT is available after $((end_ts
- start_ts)) seconds"
break
fi
sleep 1
done
return $result
}

wait_for_wrapper()
{
# In order to support SIGINT during timeout:
[Link]
if [[ $QUIET -eq 1 ]]; then
timeout $BUSYTIMEFLAG $TIMEOUT $0 --quiet --child --host=$HOST
--port=$PORT --timeout=$TIMEOUT &
else
timeout $BUSYTIMEFLAG $TIMEOUT $0 --child --host=$HOST --
port=$PORT --timeout=$TIMEOUT &
fi
PID=$!
trap "kill -INT -$PID" INT
wait $PID
RESULT=$?
if [[ $RESULT -ne 0 ]]; then
echoerr "$cmdname: timeout occurred after waiting $TIMEOUT
seconds for $HOST:$PORT"
fi
return $RESULT
}

# process arguments
while [[ $# -gt 0 ]]
do
case "$1" in
*:* )
hostport=(${1//:/ })
HOST=${hostport[0]}
PORT=${hostport[1]}
shift 1
;;
--child)
CHILD=1
shift 1
;;
-q | --quiet)
QUIET=1
shift 1
;;
-s | --strict)
STRICT=1
shift 1
;;
-h)
HOST="$2"
if [[ $HOST == "" ]]; then break; fi
shift 2
;;
--host=*)
HOST="${1#*=}"
shift 1
;;
-p)
PORT="$2"
if [[ $PORT == "" ]]; then break; fi
shift 2
;;
--port=*)
PORT="${1#*=}"
shift 1
;;
-t)
TIMEOUT="$2"
if [[ $TIMEOUT == "" ]]; then break; fi
shift 2
;;
--timeout=*)
TIMEOUT="${1#*=}"
shift 1
;;
--)
shift
CLI="$@"
break
;;
--help)
usage
;;
*)
echoerr "Unknown argument: $1"
usage
;;
esac
done

if [[ "$HOST" == "" || "$PORT" == "" ]]; then


echoerr "Error: you need to provide a host and port to test."
usage
fi

TIMEOUT=${TIMEOUT:-15}
STRICT=${STRICT:-0}
CHILD=${CHILD:-0}
QUIET=${QUIET:-0}

# check to see if timeout is from busybox?


# check to see if timeout is from busybox?
TIMEOUT_PATH=$(realpath $(which timeout))
if [[ $TIMEOUT_PATH =~ "busybox" ]]; then
ISBUSY=1
BUSYTIMEFLAG="-t"
else
ISBUSY=0
BUSYTIMEFLAG=""
fi

if [[ $CHILD -gt 0 ]]; then


wait_for
RESULT=$?
exit $RESULT
else
if [[ $TIMEOUT -gt 0 ]]; then
wait_for_wrapper
RESULT=$?
else
wait_for
RESULT=$?
fi
fi

if [[ $CLI != "" ]]; then


if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then
echoerr "$cmdname: strict mode, refusing to execute subprocess"
exit $RESULT
fi
exec $CLI
else
exit $RESULT
fi

NOTA: este archivo si quieren pueden revisarlo para entenderlo en mas detalle, pero en
resumen lo que hace es esperar que en un host y puerto este corriendo algo para despues
ejecutar un script que se le envia por parametro

En el archivo deploy/docker/scripts/start_workshop.sh:
#!/bin/bash

set -e
set -x

if [ -e ./[Link] ] || [ "$LOAD_INITIAL_DATA" = 'false' ]


then
echo "Workshop is deployed"
else
cd /usr/src/app/front
webpack --config [Link]
cd -

python [Link] makemigrations links --noinput


python [Link] migrate --noinput
python [Link] collectstatic --noinput
python [Link] loaddata data/[Link]
python [Link] loaddata data/[Link]

touch ./[Link]
export LOAD_INITIAL_DATA=false
fi
python [Link] runworker

En este script vamos a poner los comandos que queremos que se corrar para iniciar el
container.

Configuración del nginx

Nginx es un proxy reverso que nos permite configurar los puntos de entrada de nuestra
aplicacíón y ademas en produción va a servir directamente los archivos estaticos de la
aplicación.

Para configurarlo vamos a usar una imagen de docker que se llama tutum/nginx en
donde con simplemente cambiar el sites-enabled podemos tener un nginx listo.
En el archivo deploy/docker/nginx/Dockerfile:
FROM tutum/nginx
RUN rm /etc/nginx/sites-enabled/default
ADD sites-enabled/workshop /etc/nginx/sites-enabled/workshop
En el archivo deploy/docker/nginx/sites-enabled/workshop:
server {
listen 80;
client_max_body_size 100M;

location /static {
alias /usr/src/app/static;
}

location / {
proxy_pass [Link]
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For
$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;

}
}

Docker compose y variables de entorno

Ya tenemos definido nuestro nginx y los scripts que tenemos que usar, por ende solo nos
falta tener el [Link] con todos los servicion que utilizaremos.
Variables de entorno
Para poder configurar la aplicación y la base de datos vamos a usar un archivo .env. Por
lo general no se sube un .env sino que se deja un .[Link] o .[Link] como
ejemplo para mostrar que variables tenemos para configurar.
En el archivo .[Link] vamos a poner:
COMPOSE_HTTP_TIMEOUT=500
SECRET_KEY='&er^y6-o09sxig_#pp7ezpt+i#mt!t^*(1z^^_-pa6j(twz_i5'
ALLOWED_HOSTS=["*"]
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_PASSWORD=secret
POSTGRES_USER=workshop
POSTGRES_DB=workshop
EXTERNAL_PORT=80
APP_DNS=localhost
LANGUAGE_CODE=en-US
TIME_ZONE=UTC
REDIS_HOST=redis
REDIS_PORT=6379
LOG_FILE=/var/log/workshop/[Link]
HOST=localhost
LOAD_INITIAL_DATA=true
Los nombres de las variables son bastante intuitivos por lo que no nos vamos a detener
en explicar cada uno.

NOTA: Para no subir el archivo .env vamos a agregar .env al final del
archivo .gitignore.

Actualizacion del Dockerfile

En el archivo Dockerfile, vamos a agregar estas lineas antes del la linea que dice EXPOSE
8000:
# Copy example data folder
COPY ./data /usr/src/app/data

# Copy production files and create production folders


COPY ./deploy/docker/scripts/start_workshop.sh
/usr/src/app/start_workshop.sh
COPY ./deploy/docker/scripts/[Link] /usr/src/app/wait-for-
[Link]
RUN mkdir -p /var/log/workshop/
RUN touch /var/log/workshop/[Link]

Con esto vamos a agregarlo los scripts que hicimos, vamos a crear los archivos de log y
vamos a tener los json de data de ejemplo que teniamos de antes.

Docker compose

Para finalizar los archivos, vamos a agregar el [Link] que va a unir todo lo
que armamos anteriormente.
En el archivo deploy/docker/[Link] vamos a poner:
version: '3.4'

services:
daphne:
restart: always
build: ../../.
depends_on:
- worker
- redis
env_file:
- .env
environment:
- DJANGO_SETTINGS_MODULE=workshop.settings_prod
command: bash -c 'daphne -b [Link] -p 8000
[Link]:channel_layer'

worker:
restart: always
build: ../../.
env_file:
- .env
volumes:
- type: bind
source: /srv/deploys/workshopdata/static
target: /usr/src/app/static
environment:
- DJANGO_SETTINGS_MODULE=workshop.settings_prod
command: ./[Link] -p 5432 -h postgres -t 40 --
./start_workshop.sh

nginx:
restart: always
build: ./nginx/
depends_on:
- daphne
- worker
volumes:
- type: bind
source: /srv/deploys/workshopdata/static
target: /usr/src/app/static
read_only: true
environment:
- VIRTUAL_HOST=${HOST}

postgres:
restart: always
image: postgres:9.6
env_file:
- ./.env
volumes:
- type: bind
source: /srv/deploys/workshopdata/postgres
target: /var/lib/postgresql/data

nginx-proxy:
restart: always
image: jwilder/nginx-proxy
depends_on:
- nginx
ports:
- '${EXTERNAL_PORT}:80'
volumes:
- type: bind
source: /var/run/[Link]
target: /tmp/[Link]
read_only: true

redis:
image: redis:4.0.2
restart: always

Detalles importantes del archivo:

• EXTERNAL_PORT: es el puerto que vamos a usar para acceder a la aplicación, esta


variable la tenemos que tener configurada en la terminal que ejecute los
comandos de docker-compose
• HOST: es el dominio que vamos a usar para acceder a la aplicación, esta variable
la tenemos que tener configurada en la terminal que ejecute los comandos
de docker-compose

Puesta en marcha del servidor


En este punto, ya podemos ejecutar el servidor productivo.
En una terminal corremos

# En este caso usa localhost pero cuando lo tengas productivo podrias


usar el dominio que tengas
export HOST=localhost
export EXTERNAL_PORT=80

docker-compsoe up --build -d

• --build: hace que las imagenes se generen de nuevo con el codigo actualizado

• -d: hace que corra en segundo plano

Ver logs de la aplicación y si todo esta andando


# Ver los logs hasta el momento
docker-compose logs

# Ver los logs y seguir viendo los nuevos


docker-compose logs -f

# Ver estado de los containers


docker-compose ps
Parar la aplicación
docker-compose stop
Parar la aplicación y borrar los containers

NOTA: Esto borra los containers y la red interna pero no los datos de la base

docker-compose down

Resultado final:
Deberías ver la página de links detail con los links que tengas cargados en el navegador
en [Link] Podes probar de cambiar algo en el admin
(en [Link] y automaticamente se va a cambiar en el
frontend.

También podría gustarte