0% encontró este documento útil (0 votos)
163 vistas78 páginas

Aplicaciones de una sola página con Django

El documento describe cómo crear aplicaciones web de una sola página usando JavaScript y Django. Explica que con JavaScript se puede manipular el DOM para cargar contenido dinámicamente sin necesidad de recargar la página completa. Luego, muestra cómo usar Django para enviar solicitudes AJAX al servidor y obtener datos específicos que se muestran en la página sin recargarla. Finalmente, sugiere mejorar la navegación mediante el uso de la historia del navegador para que las URLs reflejen el contenido actual.
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)
163 vistas78 páginas

Aplicaciones de una sola página con Django

El documento describe cómo crear aplicaciones web de una sola página usando JavaScript y Django. Explica que con JavaScript se puede manipular el DOM para cargar contenido dinámicamente sin necesidad de recargar la página completa. Luego, muestra cómo usar Django para enviar solicitudes AJAX al servidor y obtener datos específicos que se muestran en la página sin recargarla. Finalmente, sugiere mejorar la navegación mediante el uso de la historia del navegador para que las URLs reflejen el contenido actual.
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

PROGRAMACIÓN WEB

CON PYTHON Y JAVASCRIPT


Interfaces de Usuario

Anteriormente, si queríamos un sitio web con varias páginas, lo lograríamos usando


diferentes rutas en nuestra aplicación Django. Ahora, tenemos la capacidad de cargar una
sola página y luego usar JavaScript para manipular el DOM.

Una ventaja importante de hacer esto es que solo necesitamos modificar la parte de la
página que realmente está cambiando. Por ejemplo, si tenemos una barra de navegación
que no cambia en función de su página actual, no querríamos tener que volver a
renderizar esa barra de navegación cada vez que cambiamos a una nueva parte de la
página.
Aplicaciones de una sola página
Observa en el HTML de la derecha que tenemos tres <!DOCTYPE html>
<html lang="es">
botones y tres divs. <head>
<title>Una sola página</title>
<style>
Por el momento, los divs contienen solo una pequeña parte div {
de texto, pero podríamos imaginar que cada div contiene el }
display: none;

contenido de una página de nuestro sitio. </style>


<script src="unapagina.js"></script>
</head>
Ahora, agregaremos algo de JavaScript que nos permite <body>
usar los botones para alternar entre páginas. <button data-page="pagina1">Pagina 1</button>
<button data-page="pagina2">Pagina 2</button>
<button data-page="pagina3">Pagina 3</button>
<div id="pagina1">
<h1>Esta es la Pagina 1</h1>
</div>
<div id="pagina2">
<h1>Esta es la Pagina 2</h1>
</div>
<div id="pagina3">
<h1>Esta es la Pagina 3</h1>
</div>
</body>
</html>
Aplicaciones de una sola página
<!DOCTYPE html> // Muestra una página y oculta las otras dos.
<html lang="es"> function mostrarPagina(pagina) {
<head>
<title>Una sola página</title> // Oculta todos los divs:
<style> document.querySelectorAll('div').forEach(div => {
div { div.style.display = 'none';
display: none; });
}
</style> // Muestra el div provisto en el argumento
<script src="unapagina.js"></script> document.querySelector(`#${pagina}`).style.display = 'block';
</head> }
<body>
<button data-page="pagina1">Pagina 1</button> // Espera que la pagina cargue:
<button data-page="pagina2">Pagina 2</button> document.addEventListener('DOMContentLoaded', function() {
<button data-page="pagina3">Pagina 3</button>
<div id="pagina1"> // Selecciona todos los botones
<h1>Esta es la Pagina 1</h1> document.querySelectorAll('button').forEach(button => {
</div>
<div id="pagina2"> // Cuando se clickea un boton muestra la pagina
<h1>Esta es la Pagina 2</h1> button.onclick = function() {
</div> mostrarPagina(this.dataset.pagina);
<div id="pagina3"> }
<h1>Esta es la Pagina 3</h1> })
</div> });
</body>
</html>
Aplicaciones de una sola página

En muchos casos, será ineficaz cargar todo el contenido de cada página cuando visitemos un sitio por primera vez, por lo
que necesitaremos utilizar un servidor para acceder a nuevos datos.

Por ejemplo, cuando visitas un sitio de noticias, el sitio tardaría demasiado en cargar si tuviera que cargar todos los
artículos que tiene disponibles la primera vez que visita la página. Podemos evitar este problema usando una estrategia
similar a la que usamos mientras cargamos los tipos de cambio de moneda en la lección anterior.

Esta vez, analizaremos el uso de Django para enviar y recibir información desde nuestra aplicación de una sola página.
Para mostrar cómo funciona esto, echemos un vistazo a una aplicación simple de Django. Tiene dos patrones de URL en
urls.py:

urlpatterns = [
path("", views.index, name="index"),
path("secciones/<int:num>", views.seccion, name="seccion")
]
Aplicaciones de una sola página

Y dos rutas correspondientes en views.py. Observe que la ruta de la sección toma un número entero y luego devuelve
una cadena de texto basada en ese número entero como una respuesta HTTP.
from django.http import Http404, HttpResponse
from django.shortcuts import render

# Create your views here.


def index(request):
return render(request, "unapagina/index.html")

# Los textos aca son mas cortos para ahorrar espacio


textos = ["Texto 1", "Texto 2", "Texto 3"]

def seccion(request, numero):


if 1 <= numero <= 3:
return HttpResponse(textos[num - 1])
else:
raise Http404("No hay tal seccion")
<!DOCTYPE html>
<html lang="es">
<head>
<title>Una sola página</title>
<style>
Ahora, dentro de nuestro </style>
<script>
archivo index.html,
aprovecharemos AJAX, que // Muestra la sección dada
function mostrarSeccion(seccion) {
aprendimos sobre la última
lección, para hacer una solicitud // Encontrar el texto de la sección del servidor
fetch(`/secciones/${seccion}`)
al servidor para obtener el .then(response => response.texto())
.then(texto => {
texto de una sección en // Grabar texto y mostrarlo en la pagina
particular y mostrarlo en la console.log(texto);
document.querySelector('#contenido').innerHTML = texto;
pantalla: });
}
document.addEventListener('DOMContentLoaded', function() {
// Agregar funcionalidad al boton
document.querySelectorAll('button').forEach(button => {
button.onclick = function() {
mostrarSeccion(this.dataset.seccion);
};
});
});
</script>
</head>
<body>
<h1>¡Hola!</h1>
<button data-seccion="1">Seccion 1</button>
<button data-seccion="2">Seccion 2</button>
<button data-seccion="3">Seccion 3</button>
<div id="contenido">
</div>
</body>
</html>
Aplicaciones de una sola página
¡Ahora, hemos creado un sitio donde podemos // Cuando se hace clic en la flecha hacia atrás, muestra la sección anterior
window.onpopstate = function(event) {
cargar nuevos datos desde un servidor sin tener console.log(event.state.seccion);
que volver a cargar toda nuestra página HTML! }
mostrarSeccion(event.state.seccion);

Sin embargo, una desventaja de nuestro sitio es


que la URL ahora es menos informativa. Notarás function mostrarSeccion(seccion) {
fetch(`/secciones/${seccion}`)
en el video de arriba que la URL sigue siendo la .then(response => response.texto())
misma incluso cuando cambiamos de una sección .then(texto => {
console.log(texto);
a otra. document.querySelector('#contenido').innerHTML = texto;
});

Podemos resolver este problema utilizando la API }


de historial de JavaScript. Esta API nos permite document.addEventListener('DOMContentLoaded', function() {
enviar información al historial de nuestro document.querySelectorAll('button').forEach(button => {
navegador y actualizar la URL manualmente. button.onclick = function() {
const seccion = this.dataset.seccion;
Echemos un vistazo a cómo podemos utilizar esta
API. Imagina que tenemos un proyecto de Django // Agregar el estado actual al historial
history.pushState({seccion: seccion}, "", `seccion${seccion}`);
idéntico al anterior, pero esta vez deseamos mostrarSeccion(seccion);
alterar nuestro script para emplear la API de });
};

historial: });
Aplicaciones de una sola página
En la función mostrarSeccion, empleamos la función // Cuando se hace clic en la flecha hacia atrás, muestra la sección anterior
window.onpopstate = function(event) {
history.pushState. Esta función agrega un nuevo console.log(event.state.seccion);
elemento a nuestro historial de navegación basado en mostrarSeccion(event.state.seccion);
tres argumentos: }

function mostrarSeccion(seccion) {
 Cualquier dato asociado con el estado. fetch(`/seccions/${seccion}`)
 Un parámetro de título ignorado por la mayoría de .then(response => response.texto())
.then(texto => {
los navegadores web. console.log(texto);
 Qué se debe mostrar en la URL document.querySelector('#contenido').innerHTML = texto;
});
El otro cambio que hacemos en el JavaScript anterior es }
la configuración del parámetro onpopstate, que
especifica lo que debemos hacer cuando el usuario hace document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('button').forEach(button => {
clic en la flecha hacia atrás. En este caso, queremos button.onclick = function() {
mostrar la sección anterior cuando se presiona el const seccion = this.dataset.seccion;
botón.
// Agregar el estado actual al historial
history.pushState({seccion: seccion}, "", `seccion${seccion}`);
Ahora, el sitio parece un poco más fácil de usar. mostrarSeccion(seccion);
};
});
});
Scroll
Para actualizar y acceder al historial del navegador, utilizamos un objeto JavaScript importante conocido como ventana.
Hay algunas otras propiedades de la ventana que podemos usar para hacer que nuestros sitios se vean mejor:

 window.innerWidth: Ancho de la ventana en píxeles


 window.innerHeight: Altura de la ventana en píxeles
Scroll
Si bien la ventana representa lo que actualmente es visible para el usuario, el documento se refiere a la página web
completa, que a menudo es mucho más grande que la ventana, lo que obliga al usuario a desplazarse hacia arriba y hacia
abajo para ver el contenido de la página. Para trabajar con nuestro desplazamiento, tenemos acceso a otras variables:
 window.scrollY: cuántos píxeles hemos desplazado desde la parte superior de la página
 document.body.offsetHeight: la altura en píxeles de todo el documento.
<!DOCTYPE html>
<html lang="es">
Podemos usar estas medidas para <head>
<title>Scroll</title>
determinar si el usuario se ha desplazado o <script>
no al final de una página usando la
// Event listener para scrollear
comparación: window.onscroll = () => {
window.scrollY +
// Verificar si estamos al final
window.innerHeight> = if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
document.body.offsetHeight.
// Cambiar el color a verde
document.querySelector('body').style.background = 'green';
La siguiente página, por ejemplo, cambiará el } else {

color del fondo a verde cuando lleguemos al // Cambiar el color a blanco


final de una página: document.querySelector('body').style.background = 'white';
}

};

</script>
</head>
<body>
<p>1</p>
<p>2</p>
<!– No pongo mas para no llenar la pantalla -->
<p>99</p>
<p>100</p>
</body>
</html>
Scroll Infinito
Cambiar el color de fondo al final de la página probablemente no sea tan útil, pero es posible que queramos detectar
que estamos al final de la página si queremos implementar el desplazamiento infinito. Por ejemplo, si está en un sitio de
redes sociales, no desea tener que cargar todas las publicaciones a la vez, es posible que desee cargar las primeras diez
y luego, cuando el usuario llegue al final, cargue las diez siguientes. Echemos un vistazo a una aplicación de Django que
podría hacer esto. Esta aplicación tiene dos rutas en urls.py

urlpatterns = [
path("", views.index, name="index"),
path("posts", views.posts, name="posts")
]
Scroll Infinito
import time

from django.http import JsonResponse


from django.shortcuts import render

# Create your views here.


def index(request):
return render(request, "posts/index.html")

def posts(request):

# Obtener puntos de inicio (start) y fin (end)


start = int(request.GET.get("start") or 0)
end = int(request.GET.get("end") or (start + 9))

# Generar lista de posts


datos = []
for i in range(start, end + 1):
datos.append(f"Post #{i}")

# Demorar artificialmente el delay de la velocidad de respuesta


time.sleep(1)

# Retornar lista de posts


return JsonResponse({
"posts": datos
})
Scroll Infinito
Observa que la vista de publicaciones requiere dos argumentos: un punto de inicio (start) y un punto final (end).
En esta vista, hemos creado nuestra propia API, que podemos probar visitando la URL localhost:
8000/posts?start=10&end=15, que devuelve el siguiente JSON:

{
"posts": [
"Post #10",
"Post #11",
"Post #12",
"Post #13",
"Post #14",
"Post #15"
]
}
Scroll Infinito
Ahora, en el layout de index.html que carga el sitio, comenzamos con solo un div vacío en el body y algo de estilo.
Observa que cargamos nuestros archivos estáticos al principio y luego hacemos referencia a un archivo JavaScript
dentro de nuestra carpeta static. {% load static %}
<!DOCTYPE html>
<html>
<head>
<title>Mi sitio web</title>
<style>
.post {
background-color: #77dd11;
padding: 20px;
margin: 10px;
}

body {
padding-bottom: 50px;
}
</style>
<script scr="{% static 'posts/script.js' %}"></script>
</head>
<body>
<div id="posts">
</div>
</body>
</html>
Ahora, con JavaScript, esperaremos hasta que un usuario se desplace hasta el final de la página y luego cargaremos
más publicaciones usando nuestra API:

Scroll Infinito
// Comenzar con el primer post // Cargar siguiente conjunto de posts
let contador = 1; function cargar() {

// Cargar 20 post a la vez // Setear el inicio y el fin de los post, y actualizar


const cantidad = 20; contador
const start = contador;
// Cuando el DOM carga, renderizar los primeros 20 posts const end = start + cantidad - 1;
document.addEventListener('DOMContentLoaded', cargar); contador = end + 1;

// Si el scroll lega al fin, cargar los siguientes 20 // Obtener nuevos post y agregar posts
window.onscroll = () => { fetch(`/posts?start=${start}&end=${end}`)
if (window.innerHeight + window.scrollY >= .then(response => response.json())
document.body.offsetHeight) { .then(datos => {
cargar(); datos.posts.forEach(agregar_post);
} })
}; };

// Agregar nuevo post con los contenidos al DOM


function agregar_post(contenidos) {

// Crear nuevo post


const post = document.createElement('div');
post.className = 'post';
post.innerHTML = contenidos;

// Agregar post al DOM


document.querySelector('#posts').append(post);
};
React
En este punto, puedes imaginarte cuánto código JavaScript tendrías que ingresar en un sitio web más complejo.

Podemos mitigar la cantidad de código que realmente necesitamos escribir empleando un framework de JavaScript, al
igual que empleamos Bootstrap como un framework de CSS para reducir la cantidad de CSS que realmente tenemos
que escribir.

Uno de los framework de JavaScript más populares es una librería llamada React.

Hasta ahora en este curso, hemos estado usando métodos de programación imperativos, donde le damos a la
computadora un conjunto de declaraciones para ejecutar. Por ejemplo, para actualizar el contador en una página HTML,
podríamos tener un código que se ve así:

Vista: Lógica:
<h1>0</h1> let num = parseInt(document.querySelector("h1").innerHTML);
num += 1;
document.querySelector("h1").innerHTML = num;
React
React nos permite usar programación declarativa, lo que nos permitirá simplemente escribir código explicando lo que
deseamos mostrar y no preocuparnos por cómo lo estamos mostrando. En React, un contador podría verse un poco
más parecido a esto:

Vista: Lógica:
<h1>{num}</h1> num += 1;
React
El framework de React se basa en la idea de componentes, cada uno de los cuales puede tener un estado subyacente.

Un componente sería algo que puedes ver en una página web, como una publicación o una barra de navegación, y un
estado es un conjunto de variables asociadas con ese componente.

La belleza de React es que cuando cambia el estado, React cambiará automáticamente el DOM en consecuencia. Hay
varias formas de usar React (incluido el popular comando create-react-app publicado por Facebook), pero hoy nos
centraremos en comenzar directamente en un archivo HTML. Para hacer esto, tendremos que importar tres paquetes
de JavaScript:

 React: define componentes y su comportamiento.


 ReactDOM: toma los componentes de React y los inserta en el DOM
 Babel: traduce desde JSX, el lenguaje en el que escribiremos en React, hacia JavaScript plano que nuestros
navegadores pueden interpretar. JSX es muy similar a JavaScript, pero con algunas características adicionales,
incluida la capacidad de representar HTML dentro de nuestro código.
Construyamos nuestra primera aplicación React:
<!DOCTYPE html>
<html lang="es">
<head>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigen></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigen></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<title>Hola</title>
</head>
<body>
<div id="app" />
<script type="text/babel">

class App extends React.Component {

render() {
return (
<div>
<h1>Bienvenido</h1>
¡Hola!
</div>
);
}
}

ReactDOM.render(<App />, document.querySelector("#app"));


</script>
</body>
</html>
React
Dado que esta es nuestra primera aplicación React, echemos un vistazo detallado a lo que hace cada parte del código
anterior:
• En las tres líneas sobre el título, importamos las últimas versiones de React, ReactDom y Babel.
• En el cuerpo, incluimos un solo div con un id de app. Casi siempre queremos dejar esto vacío y completarlo con
nuestro código de reacción a continuación.
• Incluimos una etiqueta de script donde especificamos ese type="text / babel". Esto indica al navegador que el
siguiente script debe traducirse con Babel.
• A continuación, creamos un componente llamado App que extiende React.Component. Los componentes en
React se representan como clases de JavaScript, que son similares a las clases de Python que aprendimos
anteriormente. Esto nos permite comenzar a crear un componente sin tener que volver a escribir mucho código
incluido en la definición de la clase React.Component.
• Dentro de nuestro componente, incluimos una función de render. Se requiere que todos los componentes tengan
esta función, y lo que se devuelva dentro de la función se agregará al DOM, en este caso, simplemente estamos
agregando <div>Hola</div>.
• La última línea de nuestro script emplea la función ReactDOM.render, que toma dos argumentos:
1. Un componente para renderizar
2. Un elemento en el DOM dentro del cual se debe representar el componente.
React
Una característica útil de React es la capacidad de renderizar componentes dentro de otros componentes. Para
demostrar esto, creemos otro componente llamado Hola:
class Hola extends React.Component {
render() {
return (
<h1>Hola</h1>
);
} Y ahora, rendericemos tres componentes de Hola dentro de
} nuestro componente de App:
class App extends React.Component {
render() {
return (
<div>
<Hello />
<Hello />
<Hello />
</div>
);
}
}
React
Hasta ahora, los componentes no han sido tan interesantes, ya que todos son exactamente iguales. Podemos hacer que
estos componentes sean más flexibles agregando propiedades adicionales (props en términos de React) a ellos. Por
ejemplo, digamos que deseamos saludar a tres personas diferentes. Podemos proporcionar los nombres de esas
personas en un método similar a las etiquetas HTML:

class App extends React.Component {


render() {
return (
<div>
<Hola nombre="Harry" />
<Hola nombre="Ron" />
<Hola nombre="Hermione" />
</div>
);
}
}
React
Luego podemos acceder a esos accesorios usando this.props.PROP_NOMBRE, donde representa el objeto actual.
Luego podemos insertar esto en nuestro HTML usando llaves:

class Hola extends React.Component {


render() {
return (
<h1>Hola, {this.props.nombre}!</h1>
);
}
}
React
Ahora, veamos cómo podemos usar React para volver a implementar la página de contador que creamos cuando
trabajamos por primera vez con JavaScript. Nuestra estructura general seguirá siendo la misma, pero dentro de nuestra
clase App, incluiremos un método constructor, un método llamado cuando se crea el componente por primera vez.
Este constructor siempre tomará props como argumento, y la primera línea siempre será super(props); que
configura el objeto basado en la clase React.Component. A continuación, inicializamos el state del componente, que
es un objeto JavaScript que almacena información sobre el componente. Por el momento, solo estableceremos el
count en 0.
constructor(props) {
super(props);
this.state = {
count: 0
};
}

Ahora, podemos trabajar en la función de renderizado, donde especificaremos un encabezado y un botón. También
agregaremos un detector de eventos para cuando se haga clic en el botón, lo que React hace usando el atributo
onClick:
React
render() {
return (
<div>
<h1>{this.state.count}</h1>
<button onClick={this.count}>Contar</button>
</div>
);
}
Finalmente, definamos la función de conteo. Para hacer esto, usaremos la función this.setState, que puede tomar
como argumento una función del estado anterior al nuevo.
count = () => {
this.setState(state => ({
count: state.count + 1
}))
}
React
Ej.: SUMAR ELEMENTOS
Ahora que conocemos el framework de React, trabajemos en el uso de lo que hemos aprendido para crear un sitio
similar a un juego donde los usuarios resolverán problemas de suma. Comenzaremos creando un nuevo archivo con la
misma configuración que nuestras otras páginas de React. Para comenzar a crear esta aplicación, pensemos en lo que
podríamos querer realizar en el estado. Debemos incluir todo lo que pensamos que podría cambiar mientras un
usuario está en nuestra página. Establezcamos el estado para incluir:
• num1: el primer número que se agregará
• num2: el segundo número que se agregará
• respuesta: lo que el usuario ha escrito
• puntuacion: cuántas preguntas ha respondido correctamente el usuario.
React
Ej.: SUMAR ELEMENTOS
Ahora, nuestro constructor se verá así: Y usando los valores en el estado, creemos una función de render con
lo que deseamos mostrar.

constructor(props) { render() {
super(props); return (
this.state = { <div>
<div>{this.state.num1} + {this.state.num2}</div>
num1: 1, <input type="text" value={this.state.respuesta} />
num2: 1, <div> Punto: {this.state.puntaje}</div>
respuesta: '', </div>
puntaje: 0 );
}; }
}
React
Ej.: SUMAR ELEMENTOS
En este punto, el usuario no puede escribir nada en el cuadro de entrada porque su valor está fijo como this.state.response, que actualmente es
un string vacío. Para solucionar este problema, agreguemos un atributo onChange al cuadro de entrada y configurémoslo igual a una función llamada
updateRespuesta

onChange={this.updateRespuesta}

Ahora, tendremos que definir la función updateRespuesta, que toma el evento que activó la función y establece la respuesta al valor actual de la
entrada. Esta función permite al usuario escribir y almacenar lo que se haya escrito en el state.

updateRespuesta = (event) => {


this.setState({
respuesta: event.target.value
});
}

Ahora, agreguemos la posibilidad de que un usuario envíe un problema. Primero agregaremos otro detector de eventos
y lo vincularemos a una función que escribiremos a continuación:

onKeyPress={this.inputKeyPress}
React
Ej.: SUMAR ELEMENTOS inputKeyPress = (event) => {
// Verificar si la tecla Enter fue presionada
if (event.key === 'Enter') {
Ahora, definiremos la función inputKeyPress. En esta
// Extraer respuesta
función, primero verificaremos si se presionó la tecla const respuesta = parseInt(this.state.respuesta)
Intro y luego verificaremos si la respuesta es correcta.
Cuando el usuario tiene razón, queremos aumentar la // Verificar si la respuesta es correcta
if (respuesta === this.state.num1 + this.state.num2) {
puntuación en 1, elegir números aleatorios para el this.setState(state => ({
siguiente problema y borrar la respuesta. Si la respuesta puntaje: state.puntaje + 1,
es incorrecta, queremos disminuir la puntuación en 1 y num1: Math.ceil(Math.random() * 10),
num2: Math.ceil(Math.random() * 10),
borrar la respuesta.
respuesta: ''
}));
} else {
this.setState(state => ({
puntaje: state.puntaje - 1,
respuesta: ''
}));
}
}
}
React
Ej.: SUMAR ELEMENTOS
Para dar algunos toques finales a la aplicación, agreguemos un poco de estilo a la página. Centraremos todo en la
aplicación y luego agrandaremos el problema agregando una identificación del problema al div que contiene el problema
y luego agregando el siguiente CSS a una etiqueta de estilo:

#app {
text-align: center;
font-family: sans-serif;
}

#problema {
font-size: 72px;
}
React
Ej.: SUMAR ELEMENTOS
Finalmente, agreguemos la capacidad de ganar el juego después de ganar 10 puntos. Para hacer esto, agregaremos una
condición a la función de renderizado, devolviendo algo completamente diferente una vez que tengamos 10 puntos:
render() {

// Verificar si el puntaje es 10
if (this.state.puntaje === 10) {
return (
<div id="ganador">
¡Has ganado!
</div>
);
}

return (
<div>
<div id="problema">{this.state.num1} + {this.state.num2}</div>
<input onKeyPress={this.inputKeyPress} onChange={this.updateRespuesta} type="text" value={this.state.respuesta} />
<div> Puntaje: {this.state.puntaje}</div>
</div>
);
}
React
Ej.: SUMAR ELEMENTOS
Para que la victoria sea más emocionante, también agregaremos algo de estilo al div alternativo:

#ganador {
font-size: 72px;
color: green;
}
Testing

Una parte importante del proceso de desarrollo de software es el acto de probar


el código que hemos escrito para asegurarnos de que todo funcione como
esperamos. En esta conferencia, analizaremos varias formas en las que
podemos mejorar la forma en que probamos nuestro código.
Assert
Una de las formas más sencillas en las que podemos ejecutar pruebas en Python es mediante el comando
assert. Este comando va seguido de alguna expresión que debería ser True. Si la expresión es True, no
sucederá nada y si es False, se lanzará una excepción.

Veamos cómo podríamos incorporar un comando para probar la función cuadrado que escribimos cuando
aprendimos Python por primera vez. Cuando la función se escribe correctamente, no sucede nada ya que
assert es True

def cuadrado(x): """ Salida:


return x * x
"""
assert cuadrado(10) == 100
Assert
Y luego, cuando está escrito incorrectamente, se lanza una excepción.

def cuadrado(x): """ Salida:


return x + x Traceback (most recent call apellido):
File "assert.py", line 4, in <module>
assert cuadrado(10) == 100 assert square(10) == 100
AssertionError
"""
Desarrollo basado en pruebas
Test-driven development

A medida que comienzas a construir proyectos más grandes, es posible que desees considerar el uso del
desarrollo basado en pruebas, un estilo de desarrollo en el que cada vez que corrige un error, agregas una
prueba que verifica ese error a un conjunto creciente de pruebas que se ejecutan cada vez que haces
cambios.

Esto te ayudará a asegurarte de que las funciones adicionales que agregues a un proyecto no interfieran con
las funciones ya existentes. Ahora, veamos una función un poco más compleja y pensemos en cómo escribir
pruebas puede ayudarnos a encontrar errores.
Desarrollo basado en pruebas
Test-driven development

Escribiremos una función llamada es_primo que devuelve True si y solo si su entrada es un numero primo:
import math

def es_primo(n):

# Sabemos que numeros menores a 2 no son primos


if n < 2:
return False

# Verificando factores hasta sqrt(n)


for i in range(2, int(math.sqrt(n))):

# Si I es un factor, retornar false


if n % i == 0:
return False

# Si no se encontraron factores, retornar true


return True
Desarrollo basado en pruebas
Test-driven development
Ahora, echemos un vistazo a una función que hemos escrito para probar nuestra función principal:

from primos import es_primo

def test_primo(n, esperado):


if es_primo(n) != esperado:
print(f"ERROR en es_primo({n}), se esperaba {esperado}")

En este punto, podemos ir al intérprete de Python y probar algunos valores:

>>> import test


>>> test.test_primo(5, True)
>>> test.test_primo(10, False)
>>> test.test_primo(25, False)
ERROR en es_primo(25), se esperaba False
Desarrollo basado en pruebas
Test-driven development

Podemos ver en el resultado anterior que 5 y 10 se identificaron correctamente como primos y no primos,
pero 25 se identificó incorrectamente como primos, por lo que debe haber algo mal en nuestra función.

Sin embargo, antes de ver qué está mal con nuestra función, veamos una forma de automatizar nuestras
pruebas. Una forma en que podemos hacer esto es creando un script de shell, o algún script que se
pueda ejecutar dentro de nuestra terminal. Estos archivos requieren una extensión .sh, por lo que nuestro
archivo se llamará tests0.sh.

Cada una de las líneas siguientes consta de

 Un comando python para especificar que estamos ejecutando comandos de Python


 Un -c para indicar que deseamos ejecutar un comando
 Un comando para ejecutar en formato de cadena
Desarrollo basado en pruebas
Test-driven development
Pruebas de Unidad
Unit Testing
Aunque pudimos ejecutar pruebas automáticamente usando el método anterior, es posible
que deseemos evitar tener que escribir cada una de esas pruebas. Afortunadamente,
podemos usar la biblioteca unittest de Python para facilitar un poco este proceso.
Echemos un vistazo a cómo se vería un programa de prueba para nuestra función
es_primo.
# Importar la librería unittest y nuestra funcion
import unittest
from primos import es_primo

# Una clase que contiene todos nuestros tests


class Tests(unittest.TestCase):

def test_1(self):
"""Verificar que 1 no es un numero primo."""
self.assertFalse(es_primo(1))

def test_2(self):
"""Verificar que 2 no es un numero primo."""
self.assertTrue(es_primo(2))

def test_8(self):
"""Verificar que 8 no es un numero primo."""
self.assertFalse(es_primo(8))

def test_11(self):
"""Verificar que 11 no es un numero primo."""
self.assertTrue(es_primo(11))

def test_25(self):
"""Verificar que 25 no es un numero primo."""
self.assertFalse(es_primo(25))

def test_28(self):
"""Verificar que 28 no es un numero primo."""
self.assertFalse(es_primo(28))

# Correr cada una de las funciones de testing


if __name__ == "__main__":
unittest.main()
Pruebas de Unidad
Unit Testing
 Observe que cada una de las funciones dentro de nuestra clase Tests siguió un patrón: El
nombre de las funciones comienza con test_. Esto es necesario para que las funciones
se ejecuten automáticamente con la llamada a unittest.main().
 Cada prueba incluye el argumento self. Esto es estándar cuando se escriben métodos
dentro de las clases de Python.
 La primera línea de cada función contiene un docstring rodeado por tres comillas.
Estos no son solo para la legibilidad del código. Cuando se ejecutan las pruebas, el
comentario se mostrará como una descripción de la prueba si falla.
 La siguiente línea de cada una de las funciones contenía una afirmación con el formato
self.assertALGO. Hay muchas afirmaciones diferentes que puedes hacer, incluidas
assertTrue, assertFalse, assertEqual y assertGreater. Puedes encontrar
estos y más consultando la documentación.
Pruebas de Unidad
Unit Testing
1. Después de ejecutar las pruebas, unittest nos
proporciona información útil sobre lo que encontró.
En la primera línea, nos da una serie de puntos ( . )
para éxitos y efes ( F ) para fallas en el orden en que
se escribieron nuestras pruebas.
2. A continuación, para cada una de las pruebas que
fallaron, se nos da el nombre de la función que falló.
3. Luego figura el comentario descriptivo que
proporcionamos anteriormente mas un Traceback
de la excepción.
4. Y finalmente, se nos muestra cuántas pruebas se
ejecutaron, cuánto tiempo tomaron y cuántas fallaron.
Pruebas de Unidad
Unit Testing
Ahora echemos un vistazo para corregir el error en nuestra función es_primo(n).
Resulta que necesitamos probar un número adicional en nuestro ciclo for. Por ejemplo, cuando n es 25, la
raíz cuadrada es 5, pero cuando ese es un argumento en la función range, el ciclo for termina en el número
4. Por lo tanto, podemos simplemente cambiar el encabezado de nuestro ciclo for a:

for i in (2, (math.sqrt(n)) + 1):


Pruebas de Unidad
Unit Testing
Estas pruebas automatizadas serán aún más útiles a medida que trabajes para
optimizar esta función.
Siempre que realices cambios para mejorar esta función, querrás tener la
capacidad de ejecutar fácilmente las pruebas unitarias nuevamente para
asegurarte de que su función aún siga siendo correcta.
Django Testing
Ahora, veamos cómo podemos aplicar las ideas de las pruebas automatizadas al crear
aplicaciones Django.

Mientras trabajamos con esto, usaremos el proyecto de Vuelos que creamos cuando
conocimos los modelos de Django. Primero, agregaremos un método a nuestro modelo
de vuelo que verifica que un vuelo es válido al verificar dos condiciones:

1. El origen no es el mismo que el destino


2. La duración es superior a 0 minutos.
Django Testing
Ahora, nuestro modelo podría verse así:

class Vuelo(models.Model):
origen = models.ForeignKey(Aeropuerto, on_delete=models.CASCADE, related_name="salidas")
destino = models.ForeignKey(Aeropuerto, on_delete=models.CASCADE, related_name="arribos")
duracion = models.IntegerField()

def __str__(self):
return f"{self.id}: {self.origen} a {self.destino}"

def es_valido_vuelo(self):
return self.origen != self.destino or self.duracion > 0
Django Testing
Para asegurarnos de que nuestra aplicación funcione como se espera, cada vez que creamos una nueva
aplicación, automáticamente se nos da un archivo tests.py. Cuando abrimos este archivo por primera
vez, vemos que la biblioteca TestCase de Django se importa automáticamente:
from django.test import TestCase
Una ventaja de usar la biblioteca TestCase es que cuando ejecutamos nuestras pruebas, se creará una
base de datos completamente nueva solo con fines de prueba.

Esto es útil porque evitamos el riesgo de modificar o eliminar accidentalmente entradas existentes en
nuestra base de datos y no tenemos que preocuparnos por eliminar entradas ficticias que creamos solo
para pruebas.
Django Testing
Para comenzar a usar esta biblioteca, primero queremos importar todos nuestros modelos:
from .models import Vuelo, Aeropuerto, Pasajero
Y luego crearemos una nueva clase que amplíe la clase TestCase que acabamos de importar. Dentro de
esta clase, definiremos una función setUp que se ejecutará al inicio del proceso de prueba.
En esta función, probablemente querremos crear. Así es como se verá nuestra clase para comenzar:

class VueloTestCase(TestCase):

def setUp(self):

# Crear aeropuertos.
a1 = Aeropuerto.objects.create(codigo="AAA", ciudad="Ciudad A")
a2 = Aeropuerto.objects.create(codigo="BBB", ciudad="Ciudad B")

# Crear vuelos.
Vuelo.objects.create(origen=a1, destino=a2, duracion=100)
Vuelo.objects.create(origen=a1, destino=a1, duracion=200)
Vuelo.objects.create(origen=a1, destino=a2, duracion=-100)
Django Testing
Ahora que tenemos algunas entradas en nuestra base de datos de prueba, agreguemos algunas funciones a
esta clase para realizar algunas pruebas.
Primero, asegurémonos de que nuestros campos de salidas y arribos funcionen correctamente
intentando contar el número de salidas (que sabemos que deberían ser 3) y arribos (que deberían ser
1) desde el aeropuerto AAA:

def test_salidas_count(self):
a = Aeropuerto.objects.get(codigo="AAA")
self.assertEqual(a.salidas.count(), 3)

def test_arribos_count(self):
a = Aeropuerto.objects.get(codigo="AAA")
self.assertEqual(a.arribos.count(), 1)
Django Testing
También podemos probar la función es_valido_vuelo que agregamos a nuestro modelo
de vuelo. Comenzaremos afirmando que la función devuelve True cuando el vuelo es
válido:
def test_valido_vuelo(self):
a1 = Aeropuerto.objects.get(codigo="AAA")
a2 = Aeropuerto.objects.get(codigo="BBB")
f = Vuelo.objects.get(origen=a1, destino=a2, duracion=100)
self.assertTrue(f.es_valido_vuelo())
Django Testing
A continuación, asegurémonos de que los vuelos con destinos y duraciones no válidos
devuelvan falso:
def test_invalido_vuelo_destino(self):
a1 = Aeropuerto.objects.get(codigo="AAA")
f = Vuelo.objects.get(origen=a1, destino=a1)
self.assertFalse(f.es_valido_vuelo())

def test_invalida_vuelo_duracion(self):
a1 = Aeropuerto.objects.get(codigo="AAA")
a2 = Aeropuerto.objects.get(codigo="BBB")
f = Vuelo.objects.get(origen=a1, destino=a2, duracion=-100)
self.assertFalse(f.es_valido_vuelo())

Ahora, para ejecutar nuestras pruebas, ejecutaremos python manage.py test.


Django Testing
El resultado de esto es casi idéntico al resultado que vimos al usar la librería unittest
de Python, aunque también registra que está creando y destruyendo una base de datos
de prueba:
Client Testing

Al crear aplicaciones web, probablemente querremos comprobar no solo si funcionan o


no funciones específicas, sino también si las páginas web individuales se cargan según lo
previsto. Podemos hacer esto creando un objeto Client en nuestra clase de prueba de
Django y luego realizando solicitudes usando ese objeto. Para hacer esto, primero
tendremos que agregar Client a nuestras importaciones:

from django.test import Client, TestCase


Client Testing
Por ejemplo, agreguemos ahora una prueba que asegure que obtenemos un código de
respuesta HTTP de 200 y que los tres vuelos se agregan al contexto de una respuesta:
def test_index(self):

# Configurar el cliente para hacer las requests


c = Client()

# Enviar request get a la pagina index y almacenar la respuesta


response = c.get("/vuelos/")

# Nos aseguramos de que devuelva el codigo de estado 200


self.assertEqual(response.status_codigo, 200)

# Nos aseguramos que los tres vuelos hayan sido retornados en el contexto
self.assertEqual(response.context["vuelos"].count(), 3)
Client Testing
De manera similar, podemos verificar para asegurarnos de obtener un código de respuesta válido para una página de
vuelo válida y un código de respuesta no válido para una página de vuelo que no existe. (Tenga en cuenta que usamos la
función Max para encontrar el id máximo, al que tenemos acceso al incluir desde django.db.models import Max
en la parte superior de nuestro archivo)
def test_pagina_vuelo_valida(self):
a1 = Aeropuerto.objects.get(codigo="AAA")
f = Vuelo.objects.get(origin=a1, destination=a1)

c = Client()
response = c.get(f"/vuelos/{f.id}")
self.assertEqual(response.status_codigo, 200)

def test_pagina_vuelo_in0valida(self):
max_id = Vuelo.objects.all().aggregate(Max("id"))["id__max"]

c = Client()
response = c.get(f"/vuelos/{max_id + 1}")
self.assertEqual(response.status_codigo, 404)
Client Testing
Finalmente, agreguemos algunas pruebas para asegurarnos de que las listas de pasajeros y no pasajeros se
generen como se esperaba:
def test_pagina_vuelo_pasajeros(self):
f = Vuelo.objects.get(pk=1)
p = Pasajero.objects.create(nombre="Juan", apellido="Perez")
f.pasajeros.add(p)

c = Client()
response = c.get(f"/vuelos/{f.id}")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context["pasajeros"].count(), 1)

def test_pagina_vuelo_no_pasajeros(self):
f = Vuelo.objects.get(pk=1)
p = Pasajero.objects.create(nombre="Juan", apellido="Perez")

c = Client()
response = c.get(f"/vuelos/{f.id}")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context["no_pasajeros"].count(), 1)
Selenium
Hasta ahora, hemos podido probar el código del lado del servidor que hemos escrito
usando Python y Django, pero a medida que creamos nuestras aplicaciones, querremos
tener la capacidad de crear pruebas para nuestro código también del lado del cliente.

Por ejemplo, pensemos en nuestra página contador.html y trabajemos en escribir


algunas pruebas para ella.
Comenzaremos escribiendo una página de contador ligeramente diferente donde
incluimos un botón para disminuir el recuento:
<!DOCTYPE html>
<html lang="es">
<head>
<title>Contador</title>
<script>

// Esperar que cargue la pagina


document.addEventListener('DOMContentLoaded', () => {

// Inicializar variable a 0
let counter = 0;

// Si se cliquea el botón de aumentar. Se aumenta el contador y se cambia el innerHMTL


document.querySelector('#incrementar').onclick = () => {
counter ++;
document.querySelector('h1').innerHTML = counter;
}

// Si se cliquea el botón de decrementar, dismunir el contador y cambiar el innerHTML


document.querySelector('#decrementar').onclick = () => {
counter --;
document.querySelector('h1').innerHTML = counter;
}
})
</script>
</head>
<body>
<h1>0</h1>
<button id="incrementar">+</button>
<button id="decrementar">-</button>
</body>
</html>
Selenium
Ahora, si deseamos probar este código, podríamos abrir nuestro navegador web, hacer
clic en los dos botones y observar lo que sucede.

Esto, sin embargo, se volvería muy tedioso a medida que escribe aplicaciones de una sola
página cada vez más grandes, por lo que se han creado varios frameworks que ayudan con
las pruebas en el navegador, uno de los cuales se llama Selenium.
Selenium
Con Selenium, podremos definir un archivo de prueba en Python donde podemos simular
que un usuario abre un navegador web, navega a nuestra página e interactúa con él.

Nuestra principal herramienta al hacer esto se conoce como Web Driver, que abrirá un
navegador web en tu computadora.

Veamos cómo podemos empezar a utilizar esta librería para comenzar a interactuar con
las páginas.

Ten en cuenta que a continuación usamos tanto selenium como ChromeDriver.


Selenium se puede instalar para python ejecutando pip install selenium, y
ChromeDriver se puede instalar ejecutando pip install chromedriver-py
Selenium
La configuración básica es la siguiente:

import os
import pathlib
import unittest

from selenium import webdriver

# Encuentar el URI (Uniform Resourse Identifier) de un archivo


def file_uri(filename):
return pathlib.Path(os.path.abspath(filename)).as_uri()

# Configura el webdriver usando Google chrome


driver = webdriver.Chrome()

Una nota sobre las primeras líneas es que para apuntar a una página específica, necesitamos el Identificador uniforme de
recursos (URI) de esa página, que es una cadena única que representa ese recurso.
Selenium
Usando los comandos en el interprete de Python:
# Encontrar la URI de nuestro archivo recientemente creado
>>> uri = file_uri("contador.html")

# Usar la URI para abrir la pagina web


>>> driver.get(uri)

# Acceder al titulo de la pagina actual


>>> driver.title

# Acceder al código fuente de la página


>>> driver.page_source

# Encontrar y almacenar los botones de incremento y decremento:


>>> incrementar = driver.find_element_by_id("incrementar")
>>> decrementar = driver.find_element_by_id("decrementar")

# Simular que el usuario hizo click en los dos botones


>>> incrementar.click()
>>> incrementar.click()
>>> decrementar.click()

# Podemos incluso incluir clicks dentro de otras estructuras de Python:


>>> for i in range(25):
... incrementar.click()
Ahora echemos un vistazo a cómo podemos usar esta simulación para crear pruebas automatizadas de
nuestra página:
# Marco estándar de una clase de testing
class WebpageTests(unittest.TestCase):

def test_titulo(self):
"""Asegurarse de que el titulo es correcto"""
driver.get(file_uri("contador.html"))
self.assertEqual(driver.title, "Contador")

def test_incrementar(self):
"""Asegurarse de que la cabecera se actualizó a 1 luego de 1 click del botón incrementar"""
driver.get(file_uri("contador.html"))
incrementar = driver.find_element_by_id("incrementar")
incrementar.click()
self.assertEqual(driver.find_element_by_tag_name("h1").text, "1")

def test_decrementar(self):
"""Asegurarse que la cabecera se actualice a -1 luego de 1 click del botón incrementar"""
driver.get(file_uri("contador.html"))
decrementar = driver.find_element_by_id("decrementar")
decrementar.click()
self.assertEqual(driver.find_element_by_tag_name("h1").text, "-1")

def test_multiples_incrementar(self):
"""Asegurarse de que la cabecera se actualice a 3 luego de 3 clicks del botón incrementar"""
driver.get(file_uri("contador.html"))
incrementar = driver.find_element_by_id("incrementar")
for i in range(3):
incrementar.click()
self.assertEqual(driver.find_element_by_tag_name("h1").text, "3")

if __name__ == "__main__":
unittest.main()
CI/CD
CI / CD es un conjunto de mejores prácticas de desarrollo de software que dictan cómo
un equipo de personas escribe el código y cómo ese código se entrega posteriormente a
los usuarios de la aplicación. Como su nombre lo indica, este método consta de dos partes
principales:

 Continuous Integration / Integración continua:


• Fusiones frecuentes a la rama principal
• Pruebas unitarias automatizadas con cada combinación

 Continuous Delivery / Entrega continua:


• Programas de lanzamiento cortos, lo que significa que las nuevas versiones de una
aplicación se lanzan con frecuencia.
CI/CD
CI / CD se ha vuelto cada vez más popular entre los equipos de desarrollo de software por varias razones:

• Cuando diferentes miembros del equipo están trabajando en diferentes funciones, pueden surgir muchos
problemas de compatibilidad cuando se combinan varias funcionalidades al mismo tiempo. La integración
continua permite a los equipos abordar los pequeños conflictos a medida que surgen.
• Debido a que las pruebas unitarias se ejecutan con cada combinación, cuando una prueba falla, es más fácil
aislar la parte del código que está causando el problema.
• La publicación frecuente de nuevas versiones de una aplicación permite a los desarrolladores aislar los
problemas si surgen después del lanzamiento.
• La publicación de cambios pequeños e incrementales permite a los usuarios acostumbrarse lentamente a
las nuevas funciones de la aplicación en lugar de sentirse abrumados con una versión completamente
diferente.
• No esperar a lanzar nuevas funciones permite a las empresas mantenerse a la vanguardia en un mercado
competitivo.
GRACIAS
programacionpolotic@gmail.com

PROGRAMACIÓN WEB
CON PYTHON Y JAVASCRIPT

También podría gustarte