Taller Django, React y Redux
Taller Django, React y Redux
Este repositorio va a ser usado en la PyconAr2017 para dar el paso a paso del taller
"Django + React + Redux".
Estado
Esta imagen tiene el codigo, pip requirements (dev, docs y production requirements),
node dependencies (pruduction y dev dependencies), ...
# 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
# 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
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]
# 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).
# con docker
docker exec -it workshop ./workshop/[Link] migrate
# sin docker
./workshop/[Link] migrate
# 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]
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.
urlpatterns = [
path('admin/', [Link]),
path('links/', include('[Link]')),
]
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
<!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>
{% 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.
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])
class Meta:
unique_together = (('link', 'tag'))
verbose_name = _('link x tag')
verbose_name_plural = _('link x tag')
Notas
class LinkTagInline([Link]):
model = LinkTag
@[Link](Link)
class LinkAdmin([Link]):
inlines = [LinkTagInline]
@[Link](Tag)
class TagAdmin([Link]):
pass
Generemos migraciones:
# con docker
docker exec -it workshop ./workshop/[Link] makemigrations
# sin docker
./workshop/[Link] makemigrations
Actualicemos la db:
# con docker
docker exec -it workshop ./workshop/[Link] migrate
# sin docker
./workshop/[Link] migrate
## 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.
...
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')),
+]
# sin docker
apt update
apt --force-yes install -y gettext
mkdir workshop/locale
./workshop/[Link] makemessages --locale=es
# sin docker
./workshop/[Link] compilemessages
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".
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.
Actualizamos requirements
# sin docker
pip freeze > [Link]
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.
# 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
# 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.
[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']
},
}
[Link] = "#eval-source-map"
[Link] = ip
])
[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).
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
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"
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"
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 %}.
# 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
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.
{% block main %}
<div id="app"></div>
{% render_bundle 'vendors' %}
{% render_bundle 'App' %}
{% endblock %}
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:
# sin docker
cd workshop/front
node_modules/.bin/webpack --config [Link]
# 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].
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}`);
})
-[Link] = `/static/bundles/local/`
+[Link] = `[Link]
# con docker
docker exec -it workshopjs npm start
# sin docker
cd workshop/front
npm start
# 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.
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.
# con docker
docker exec -it workshop pip install pylint pylint-django
# sin docker
pip install pylint pylint-django
Crear requirements-dev
# 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.
# sin docker
pylint --generate-rcfile > .pylintrc
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
------------------------------------------------------------------
Your code has been rated at 8.51/10 (previous run: 6.74/10, +1.76)
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
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]
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.
# sin docker
cd workshop/front
npm run eslint
Leer reporte en consola de eslint
/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
# 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
/src/workshop/front/src/components/Headline/[Link]
6:24 error 'children' is missing in props validation react/prop-
types
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
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.
Actualizar requirements-dev
# 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.
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
En workshop/links/[Link]:
"""
Utils module for link application
"""
import difflib
from .constant import SIMILAR_RATIO
En workshop/links/[Link]:
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:
• [Link]
• [Link].django_db
• django_dynamic_fixture
En workshop/links/tests/test_utils.py:
"""
Tests for utils module
"""
from [Link] import patch
import pytest
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')
En workshop/links/tests/test_tag.py:
"""
Tests for Tag model
"""
from [Link] import patch
from django_dynamic_fixture import G
import pytest
@[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)
[tool:pytest]
addopts = --ds=[Link]
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
links/tests/test_tag.py .....
links/tests/test_utils.py ...
# sin docker
cd workshop/front
yarn install
Crear tests
Crear tests para la componente Headline
En workshop/front/src/components/Headline/[Link]:
describe('props', () => {
})
describe('render', () => {
})
})
describe('#render', () => {
})
})
En workshop/front/src/containers/[Link]:
describe('#render', () => {
})
})
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
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)
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
-render(<App/>, [Link]('app'))
+render(<LinksDetail/>, [Link]('app'))
if ([Link]) [Link]();
En el contenedor
de LinksDetail (workshop/front/src/containers/LinksDetail/[Link]):
En la componente
de LinksDetail (workshop/front/src/components/LinksDetail/[Link]):
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]:
En workshop/links/templates/link_detail.html:
{% render_bundle 'vendors' %}
- {% render_bundle 'App' %}
+ {% render_bundle 'LinksDetail' %}
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 %}
-render(<LinksDetail/>, [Link]('app'))
+window.render_components = properties => {
+ [Link] = {...properties};
+ render(<LinksDetail links={[Link]}/>,
[Link]('app'));
+};
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
En workshop/front/src/containers/LinksDetail/[Link]:
En workshop/front/src/components/LinksDetail/[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]:
urlpatterns = [
path('view2/',
[Link].as_view(template_name='[Link]')),
- path('view2/',
- [Link].as_view(template_name='[Link]')),
+ path('', views.links_detail)
]
En workshop/links/templates/link_detail.html:
{% render_bundle 'vendors' %}
{% render_bundle 'LinksDetail' %}
+ <script>
+ window.render_components({
+ links: {{ links|safe }}
+ });
+ </script>
{% endblock %}
render() {
const { link } = [Link];
return (
<p>
{[Link]}: <a
href={[Link]}>{[Link]}</a>
</p>
)
}
}
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]
# sin docker
cd workshop/front
npm start
# 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
En este paso vamos a agregar una api rest del lado de Django
con djangorestframework y tomar los datos desde React con fetch
# con docker
docker exec -it workshop pip install djangorestframework markdown
django-filter
# sin docker
pip install djangorestframework markdown django-filter
Actualizar requirements
# 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]
+
+REST_FRAMEWORK = {
+ 'DEFAULT_PERMISSION_CLASSES': [
+
'rest_framework.[Link]'
+ ]
+}
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
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')
class LinkViewSet([Link]):
queryset = [Link]()
serializer_class = LinkSerializer
class TagViewSet([Link]):
queryset = [Link]()
serializer_class = TagSerializer
class UserViewSet([Link]):
queryset = [Link]()
serializer_class = UserSerializer
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]))
]
En workshop/front/src/components/Button/[Link]:
import React from 'react'
import PropTypes from 'prop-types';
render() {
const { label } = [Link];
return (
<button className='btn btn-success' type='button'
onClick={this._onClick}>
{ label }
</button>
)
}
}
En workshop/front/src/utils/[Link]:
export function getUrl(url){
return fetch(url).then(resp => [Link]())
}
En workshop/front/src/utils/[Link]:
export const API_URL = '/links/api/'
export const LINKS_API_URL = `${API_URL}links/`
En workshop/front/src/components/LinksDetail/[Link]:
import Headline from '../Headline'
import LinkDetail from '../LinkDetail'
+import Button from '../Button'
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';
+ 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.
# sin docker
cd workshop/front
npm start
# 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.
En este paso vamos a agregar data binding con django channels y tomar los datos desde
React con react-websocket
# sin docker
pip install channels
Actualizar requirements
# con docker
docker exec -it workshop pip freeze | grep channels >> [Link]
# sin docker
pip freeze | grep channels >> [Link]
+
+CHANNEL_LAYERS = {
+ "default": {
+ "BACKEND": "[Link]",
+ "ROUTING": "[Link].channel_routing",
+ },
+}
En el archivo workshop/links/[Link]:
"""
Bindings module
"""
# pylint: disable=missing-docstring
class LinkBinding(WebsocketBinding):
model = Link
stream = 'links'
fields = ['id', 'name', 'url', 'pending',
'description', 'tags', 'user']
@classmethod
def group_names(cls, instance):
return ['link-updates']
class LinkTagBinding(WebsocketBinding):
model = LinkTag
stream = 'linktags'
fields = ['id', 'link', 'tag']
@classmethod
def group_names(cls, instance):
return ['linktags-updates']
@classmethod
def group_names(cls, instance):
return ['tags-updates']
En el archivo workshop/workshop/[Link]:
"""
Routing Module with all Demultiplexers and channel_routing for
djnago-channels
"""
# pylint: disable=missing-docstring
class APIDemultiplexer(WebsocketDemultiplexer):
consumers = {
'links': [Link],
'tags': [Link],
'linktags': [Link],
}
class LinkTagDemultiplexer(WebsocketDemultiplexer):
consumers = {
'linktags': [Link]
}
class LinkDemultiplexer(WebsocketDemultiplexer):
consumers = {
'links': [Link]
}
class TagDemultiplexer(WebsocketDemultiplexer):
consumers = {
'tags': [Link]
}
# 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/$'),
]
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/`
Remplazar el
archivo workshop/front/src/containers/LinksDetail/[Link] con:
import React from 'react'
import PropTypes from 'prop-types';
import Websocket from 'react-websocket';
constructor(props) {
super(props);
const { links } = [Link];
[Link] = {
links: [...links]
}
}
_onRefresh = () => {
getUrl(LINKS_API_URL)
.then(newLinks => {
const links = [Link](link => [Link]([Link],
link));
[Link]({links});
})
}
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]
# sin docker
cd workshop/front
npm start
# 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.
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
Crear acciones
En workshop/front/src/actions/[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]:
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: []
}
Crear store
En workshop/front/src/store/[Link]:
En workshop/front/src/utils/[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);
})
}
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]
# sin docker
cd workshop/front
npm start
# 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.
Para producción solo vamos a tener a Django trabajando, ya que el js generado con React
se va a usar como archivos estaticos.
"""
Django production settings for workshop project.
import os
import socket
DEBUG = False
TEMPLATE_DEBUG = False
SECRET_KEY = [Link](
'SECRET_KEY',
'kl*@mt86$rdllg+$d633#ijwkkc49^k-hw5yxfsbtn*rdq1=l)')
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:
import os
[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.
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:
También para garantizar que los datos esten guardados vamos a usar volumenes, lo
cuales son:
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)
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
TIMEOUT=${TIMEOUT:-15}
STRICT=${STRICT:-0}
CHILD=${CHILD:-0}
QUIET=${QUIET:-0}
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
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.
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;
}
}
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.
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
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
docker-compsoe up --build -d
• --build: hace que las imagenes se generen de nuevo con el codigo actualizado
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.