Blog
Blockchain: Finalizando nuestra app e implementando Bootstrap
- Publicado por: Hebert
- Categoría: Blog
Todo lo que hemos programado hasta ahora ha sido para el contenedor amarillo pero nos faltan los otros dos contenedores, el contenedor azul y el contenedor verde.
Entonces los que vamos a hacer ahora es ir a nuestro archivo Blockchain_server.py y agregar las clases para ambos contenedores e instanciar el objeto y crear el bloque génesis para cada uno, ahora todo debería de verse así:
# Contenedor Amarillo
class ContenedorAmarillo():
dificultad = 2
def __init__(self):
self.transacciones_no_confirmadas = []
self.cadena = []
def crear_bloque_genesis(self):
bloque_genesis = Bloque(0, [], 0, "0")
bloque_genesis.hash = bloque_genesis.calcular_hash()
self.cadena.append(bloque_genesis)
@property
def ultimo_bloque(self):
return self.cadena[-1]
def prueba_de_trabajo(self, bloque):
bloque.nonce = 0
calcular_hash = bloque.calcular_hash()
while not calcular_hash.startswith('0' * ContenedorAmarillo.dificultad):
bloque.nonce += 1
calcular_hash = bloque.calcular_hash()
return calcular_hash
def agregar_bloque(self, bloque, prueba):
hash_bloque_anterior = self.ultimo_bloque.hash
if hash_bloque_anterior != bloque.hash_bloque_anterior:
return False
if not ContenedorAmarillo.prueba_valida(bloque, prueba):
return False
bloque.hash = prueba
self.cadena.append(bloque)
return True
def agregar_nueva_transaccion(self, transaccion):
self.transacciones_no_confirmadas.append(transaccion)
@classmethod
def prueba_valida(cls, bloque, hash_bloque):
return (hash_bloque.startswith('0' * ContenedorAmarillo.dificultad) and
hash_bloque == bloque.calcular_hash())
@classmethod
def chequear_validez_cadena(cls, cadena):
resultado = True
hash_bloque_anterior = "0"
for bloque in cadena:
hash_bloque = bloque.hash
delattr(bloque, "hash")
if not cls.prueba_valida(bloque, hash_bloque) or \
hash_bloque_anterior != bloque.hash_bloque_anterior:
resultado = False
break
bloque.hash, hash_bloque_anterior = hash_bloque, hash_bloque
return resultado
def minar(self):
if not self.transacciones_no_confirmadas:
return False
ultimo_bloque = self.ultimo_bloque
nuevo_bloque = Bloque(id=ultimo_bloque.id + 1,
transacciones=self.transacciones_no_confirmadas,
fecha=time.time(),
hash_bloque_anterior=ultimo_bloque.hash)
prueba = self.prueba_de_trabajo(nuevo_bloque)
self.agregar_bloque(nuevo_bloque, prueba)
self.transacciones_no_confirmadas = []
return True
# Fin del contenedor amarillo
# Contenedor Azul
class ContenedorAzul(ContenedorAmarillo):
pass
# Fin del contenedor azul
# Contenedor verde
class ContenedorVerde(ContenedorAmarillo):
pass
# Fin del contenedor verde
# codigo que sigue ...
El contenedor azul y el contenedor verde ambos heredan del contenedor amarillo, ya que los métodos que usará son los mismos y para no repetir el código lo que hace es simplemente decirle a python que estas clases deben heredar del contenedor amarillo.
También debemos añadir cada una de las rutas y funciones de dichos contenedores tanto en nuestra API como en nuestra app, comencemos por nuestra API, vamos a nuestro archivo Blockchain_server.py y añadimos el siguiente código:
# Codigo anterior del contenedor amarillo ...
# Backend contenedor Azul
@app.route('/nueva_transaccion_azul', methods=['POST'])
def nueva_transaccion_azul():
datos_transaccion = request.get_json()
campos_requeridos = ["nombre_azul", "codigo_azul"]
for campo in campos_requeridos:
if not datos_transaccion.get(campo):
return "Datos de transacción inválidos", 404
datos_transaccion["fecha"] = time.time()
contenedor_azul.agregar_nueva_transaccion(datos_transaccion)
return "Transacción agregar exitosamente", 201
@app.route('/cadena_azul', methods=['GET'])
def obtener_cadena_azul():
datos_cadena = []
for bloque in contenedor_azul.cadena:
datos_cadena.append(bloque.__dict__)
return json.dumps({"longitud": len(datos_cadena),
"cadena_azul": datos_cadena,
"compañeros": list(compañeros)})
@app.route('/minar_contenedor_azul', methods=['GET'])
def minar_transacciones_no_confirmadas_azules():
resultado = contenedor_azul.minar()
if not resultado:
print('Sin transacciones para minar')
return redirect('http://127.0.0.1:5000/contenedor_azul')
else:
longitud_cadena = len(contenedor_azul.cadena)
consenso_azul()
if longitud_cadena == len(contenedor_azul.cadena):
anunciar_nuevo_bloque_amarillo(contenedor_azul.ultimo_bloque)
print("Bloque #{} ha sido minado.".format(contenedor_azul.ultimo_bloque.id))
return redirect('http://127.0.0.1:5000/contenedor_azul')
@app.route('/agregar_bloque_azul', methods=['POST'])
def verificar_y_agregar_bloque_azul():
dato_bloque = request.get_json()
bloque = Bloque(dato_bloque["id"],
dato_bloque["transacciones"],
dato_bloque["fecha"],
dato_bloque["hash_bloque_anterior"],
dato_bloque["nonce"])
prueba = dato_bloque['hash']
agregar = contenedor_azul.agregar_bloque(bloque, prueba)
if not agregar:
return "El bloque fue descartado por el nodo", 400
return "El bloque ha sido agregado a la cadena", 201
@app.route('/transacciones_pendientes_azules')
def obtener_transacciones_pendientes_azules():
return json.dumps(contenedor_azul.transacciones_no_confirmadas)
def consenso_azul():
global contenedor_azul
cadena_mas_larga = None
longitud_actual = len(contenedor_azul.cadena)
for node in compañeros:
response = requests.get('{}cadena_azul'.format(node))
longitud = response.json()['longitud']
cadena = response.json()['cadena_azul']
if longitud > longitud_actual and contenedor_azul.chequear_validez_cadena(cadena):
longitud_actual = longitud
cadena_mas_larga = cadena
if cadena_mas_larga:
contenedor_azul = cadena_mas_larga
return True
return False
def anunciar_nuevo_bloque_azul(bloque):
for compañero in compañeros:
url = "{}agregar_bloque_azul".format(compañero)
headers = {'Content-Type': "application/json"}
requests.post(url,
data=json.dumps(bloque.__dict__, sort_keys=True),
headers=headers)
@app.route('/registrar_nodo_azul', methods=['POST'])
def registrar_nuevo_compañeros_azul():
direccion_nodo = request.get_json()["direccion_nodo"]
if not direccion_nodo:
return "Datos inválidos", 400
compañeros.add(direccion_nodo)
return obtener_cadena_azul()
@app.route('/registrar_con_azul', methods=['POST'])
def registrar_con_nodo_existente_azul():
direccion_nodo = request.get_json()["direccion_nodo"]
if not direccion_nodo:
return "Datos inválidos", 400
data = {"direccion_nodo": request.host_url}
headers = {'Content-Type': "application/json"}
response = requests.post(direccion_nodo + "/registrar_nodo_azul",
data=json.dumps(data), headers=headers)
if response.status_code == 200:
global contenedor_azul
global compañeros
volcar_cadena_azul = response.json()['cadena_azul']
contenedor_azul = crear_cadena_desde_volcado_azul(volcar_cadena_azul)
compañeros.update(response.json()['compañeros'])
return "Registro realizado correctamente", 200
else:
return response.content, response.status_code
def crear_cadena_desde_volcado_azul(volcar_cadena_azul):
generar_contenedor_azul = ContenedorAzul()
generar_contenedor_azul.crear_bloque_genesis()
for idx, dato_bloque in enumerate(volcar_cadena_azul):
if idx == 0:
continue
bloque = Bloque(dato_bloque["id"],
dato_bloque["transacciones"],
dato_bloque["fecha"],
dato_bloque["hash_bloque_anterior"],
dato_bloque["nonce"])
prueba = dato_bloque['hash']
agregar = generar_contenedor_azul.agregar_bloque(bloque, prueba)
if not agregar:
raise Exception("¡El volcado de cadena ha sido alterado!")
return generar_contenedor_azul
# Fin backend contenedor Azul
# Backend contenedor Verde
@app.route('/nueva_transaccion_verde', methods=['POST'])
def nueva_transaccion_verde():
datos_transaccion = request.get_json()
campos_requeridos = ["nombre_verde", "codigo_verde"]
for campo in campos_requeridos:
if not datos_transaccion.get(campo):
return "Datos de transacción inválidos", 404
datos_transaccion["fecha"] = time.time()
contenedor_verde.agregar_nueva_transaccion(datos_transaccion)
return "Transacción agregar exitosamente", 201
@app.route('/cadena_verde', methods=['GET'])
def obtener_cadena_verde():
datos_cadena = []
for bloque in contenedor_verde.cadena:
datos_cadena.append(bloque.__dict__)
return json.dumps({"longitud": len(datos_cadena),
"cadena_verde": datos_cadena,
"compañeros": list(compañeros)})
@app.route('/minar_contenedor_verde', methods=['GET'])
def minar_transacciones_no_confirmadas_verdes():
resultado = contenedor_verde.minar()
if not resultado:
print('Sin transacciones para minar')
return redirect('http://127.0.0.1:5000/contenedor_verde')
else:
longitud_cadena = len(contenedor_verde.cadena)
consenso_verde()
if longitud_cadena == len(contenedor_verde.cadena):
anunciar_nuevo_bloque_verde(contenedor_verde.ultimo_bloque)
print("Bloque #{} ha sido minado.".format(contenedor_verde.ultimo_bloque.id))
return redirect('http://127.0.0.1:5000/contenedor_verde')
@app.route('/agregar_bloque_verde', methods=['POST'])
def verificar_y_agregar_bloque_verde():
dato_bloque = request.get_json()
bloque = Bloque(dato_bloque["id"],
dato_bloque["transacciones"],
dato_bloque["fecha"],
dato_bloque["hash_bloque_anterior"],
dato_bloque["nonce"])
prueba = dato_bloque['hash']
agregar = contenedor_verde.agregar_bloque(bloque, prueba)
if not agregar:
return "El bloque fue descartado por el nodo", 400
return "El bloque ha sido agregado a la cadena", 201
@app.route('/transacciones_pendientes_verdes')
def obtener_transacciones_pendientes_verdes():
return json.dumps(contenedor_verde.transacciones_no_confirmadas)
def consenso_verde():
global contenedor_verde
cadena_mas_larga = None
longitud_actual = len(contenedor_verde.cadena)
for node in compañeros:
response = requests.get('{}cadena_verde'.format(node))
longitud = response.json()['longitud']
cadena = response.json()['cadena_verde']
if longitud > longitud_actual and contenedor_verde.chequear_validez_cadena(cadena):
longitud_actual = longitud
cadena_mas_larga = cadena
if cadena_mas_larga:
contenedor_verde = cadena_mas_larga
return True
return False
def anunciar_nuevo_bloque_verde(bloque):
for compañero in compañeros:
url = "{}agregar_bloque_verde".format(compañero)
headers = {'Content-Type': "application/json"}
requests.post(url,
data=json.dumps(bloque.__dict__, sort_keys=True),
headers=headers)
@app.route('/registrar_nodo_verde', methods=['POST'])
def registrar_nuevo_compañeros_verde():
direccion_nodo = request.get_json()["direccion_nodo"]
if not direccion_nodo:
return "Datos inválidos", 400
compañeros.add(direccion_nodo)
return obtener_cadena_verde()
@app.route('/registrar_con_verde', methods=['POST'])
def registrar_con_nodo_existente_verde():
direccion_nodo = request.get_json()["direccion_nodo"]
if not direccion_nodo:
return "Datos inválidos", 400
data = {"direccion_nodo": request.host_url}
headers = {'Content-Type': "application/json"}
response = requests.post(direccion_nodo + "/registrar_nodo_verde",
data=json.dumps(data), headers=headers)
if response.status_code == 200:
global contenedor_verde
global compañeros
volcar_cadena_verde = response.json()['cadena_verde']
contenedor_verde = crear_cadena_desde_volcado_verde(volcar_cadena_verde)
compañeros.update(response.json()['compañeros'])
return "Registro realizado correctamente", 200
else:
return response.content, response.status_code
def crear_cadena_desde_volcado_verde(volcar_cadena_verde):
generar_contenedor_verde = ContenedorVerde()
generar_contenedor_verde.crear_bloque_genesis()
for idx, dato_bloque in enumerate(volcar_cadena_verde):
if idx == 0:
continue
bloque = Bloque(dato_bloque["id"],
dato_bloque["transacciones"],
dato_bloque["fecha"],
dato_bloque["hash_bloque_anterior"],
dato_bloque["nonce"])
prueba = dato_bloque['hash']
agregar = generar_contenedor_verde.agregar_bloque(bloque, prueba)
if not agregar:
raise Exception("¡El volcado de cadena ha sido alterado!")
return generar_contenedor_verde
# Fin backend contenedor Verde
Básicamente lo que hemos hecho es crear, cada función y cada ruta para los contenedores faltantes que eran el contenedor azul y el contenedor verde.
Ahora vamos a agregar las rutas y las funciones en nuestra app, vamos a nuestro archivo vistas.py y agregamos lo siguiente:
# Codigo anterior ...
reciclaje_azul = []
reciclaje_verde = []
puntos = 0
def buscar_reciclaje_azul():
obtener_direccion_cadena = "{}/cadena_azul".format(DIRECCION_NODOS_CONECTADOS)
response = requests.get(obtener_direccion_cadena)
if response.status_code == 200:
contenido = []
cadena = json.loads(response.content)
for bloque in cadena["cadena_azul"]:
for tx in bloque["transacciones"]:
tx["id"] = bloque["id"]
tx["hash"] = bloque["hash_bloque_anterior"]
contenido.append(tx)
global reciclaje_azul
reciclaje_azul = sorted(contenido, key=lambda k: k['fecha'],
reverse=True)
def buscar_reciclaje_verde():
obtener_direccion_cadena = "{}/cadena_verde".format(DIRECCION_NODOS_CONECTADOS)
response = requests.get(obtener_direccion_cadena)
if response.status_code == 200:
contenido = []
cadena = json.loads(response.content)
for bloque in cadena["cadena_verde"]:
for tx in bloque["transacciones"]:
tx["id"] = bloque["id"]
tx["hash"] = bloque["hash_bloque_anterior"]
contenido.append(tx)
global reciclaje_verde
reciclaje_verde = sorted(contenido, key=lambda k: k['fecha'],
reverse=True)
@app.route('/contenedor_azul')
def contenedor_azul():
buscar_reciclaje_azul()
return render_template(
'contenedor-azul.html',
reciclaje_azul=reciclaje_azul,
direcciones_nodo=DIRECCION_NODOS_CONECTADOS,
tiempo_legible=fecha_hora_a_string
)
@app.route('/contenedor_verde')
def contenedor_verde():
buscar_reciclaje_verde()
return render_template(
'contenedor-verde.html',
reciclaje_verde=reciclaje_verde,
direcciones_nodo=DIRECCION_NODOS_CONECTADOS,
tiempo_legible=fecha_hora_a_string
)
@app.route('/enviar_azul', methods=['POST'])
def enviar_formulario_azul():
global puntos
puntos += 1
nombre = request.form["nombre_azul"]
codigo = request.form["codigo_azul"]
objeto_reciclado = {
'nombre_azul': nombre,
'codigo_azul': codigo,
}
nueva_direccion_de_transaccion = "{}/nueva_transaccion_azul".format(DIRECCION_NODOS_CONECTADOS)
requests.post(nueva_direccion_de_transaccion,
json=objeto_reciclado,
headers={'Content-type': 'application/json'})
return redirect('/index')
@app.route('/enviar_verde', methods=['POST'])
def enviar_formulario_verde():
global puntos
puntos += 1
nombre = request.form["nombre_verde"]
codigo = request.form["codigo_verde"]
objeto_reciclado = {
'nombre_verde': nombre,
'codigo_verde': codigo,
}
nueva_direccion_de_transaccion = "{}/nueva_transaccion_verde".format(DIRECCION_NODOS_CONECTADOS)
requests.post(nueva_direccion_de_transaccion,
json=objeto_reciclado,
headers={'Content-type': 'application/json'})
return redirect('/index')
@app.route('/ver_puntos')
def ver_puntos():
return render_template(
'puntaje.html',
puntos=puntos
)
Estilizando el front-end de nuestra app con Bootstrap:
Quiero destacar que este no es un curso de Bootstrap por lo cual no me detendré mucho a explicar el framework pero si te diré que es propiedad de Twitter y que es un framework de CSS que nos permite utilizar sus estilos para construir el front-end de sitios web de una manera rápida y sencilla y puedes ver su documentación en la su página oficial.
Entonces procedamos a hacerle los siguientes cambios a nuestra plantilla base.html y debe lucir así:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
</head>
<body>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#">Red descentralizada de reciclaje</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ml-auto">
<li class="nav-item {% if url_for(request.endpoint) == '/index' or url_for(request.endpoint) == '/' %} active {% endif %}">
<a class="nav-link" href="{{ url_for('index') }}">Inicio</a>
</li>
<li class="nav-item {% if url_for(request.endpoint) == '/ver_puntos' %}active{% endif %}">
<a href="{{ url_for('ver_puntos') }}" class="nav-link">Ver puntos</a>
</li>
</ul>
</div>
</nav>
{% block content %}
{% endblock %}
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
crossorigin="anonymous"></script>
<script>
$(document).ready(function(){
$('#registrar-amarillo').submit(function(){
alert('Has ganado 1 punto, gracias por reciclar.')
});
$('#registrar-azul').submit(function(){
alert('Has ganado 1 punto, gracias por reciclar.')
});
$('#registrar-verde').submit(function(){
alert('Has ganado 1 punto, gracias por reciclar.')
})
})
</script>
</body>
</body>
</html>
Lo que hemos hecho es agregar Bootstrap a nuestro proyecto a través de sus CDN y hemos agregado las clases de Bootstrap a nuestra barra de navegación para que luzca un poco mejor, ahora falta estilizar las otras 5 plantillas restantes con las clases de Bootstrap.
También he agregado al final un pequeño código Javascript haciendo uso de la librería Jquery, lo que hace este código es decirle a Jquery que cada vez que se envíe un formulario de manera correcta, nos diga a través de una alerta que se hemos recibido 1 punto. Este código se repite varias veces pero es porque es para cada formulario, es por eso que hay un rotal de 3 que representa a cada uno de los formularios que también son 3 en total.
Anexaré el código de cada plantilla a continuación.
index.html:
{% extends "base.html" %}
{% block content %}
<div class="row ml-2 mt-5 mr-2">
<div class="col-md-4">
<h4>Contenedor Amarillo</h4>
<form action="/enviar_amarillo" method="POST" id="registrar-amarillo">
<div class="form-group">
<label for="Nombre">Nombre del objeto a reciclar</label>
<input required type="text" class="form-control" id="nombre-amarillo" name="nombre_amarillo">
</div>
<div class="form-group">
<label for="Código de barras">Código de barras</label>
<input required type="text" class="form-control" id="codigo-amarillo" name="codigo_amarillo">
</div>
<button class="btn btn-primary btn-sm" type="submit">Registrar</button>
<a class="btn btn-primary btn-sm" href="{{ direcciones_nodo }}/minar_contenedor_amarillo"
target="_blank">Ver contenedor</a>
</form>
</div>
<div class="col-md-4">
<h4>Contenedor Azul</h4>
<form action="/enviar_azul" method="POST" id="registrar-azul">
<div class="form-group">
<label for="Nombre">Nombre del objeto a reciclar</label>
<input required type="text" class="form-control" id="nombre-azul" name="nombre_azul">
</div>
<div class="form-group">
<label for="Código de barras">Código de barras</label>
<input required type="text" class="form-control" id="codigo-azul" name="codigo_azul">
</div>
<button class="btn btn-primary btn-sm" type="submit">Registrar</button>
<a class="btn btn-primary btn-sm" href="{{ direcciones_nodo }}/minar_contenedor_azul"
target="_blank">Ver
contenedor</a>
</form>
</div>
<div class="col-md-4">
<h4>Contenedor Verde</h4>
<form action="/enviar_verde" method="POST" id="registrar-verde">
<div class="form-group">
<label for="Nombre">Nombre del objeto a reciclar</label>
<input required type="text" class="form-control" id="nombre-verde" name="nombre_verde">
</div>
<div class="form-group">
<label for="Código de barras">Código de barras</label>
<input type="text" class="form-control" id="codigo-verde" name="codigo_verde">
</div>
<button class="btn btn-primary btn-sm" type="submit">Registrar</button>
<a class="btn btn-primary btn-sm" href="{{ direcciones_nodo }}/minar_contenedor_verde"
target="_blank">Ver contenedor</a>
</form>
</div>
</div>
<div class="mt-5 text-center" style="padding: 40px;">
<h1 class="mb-5">Información acerca de cada contenedor</h1>
<div class="row">
<div class="col-md-4 mt-4">
<h3>Contenedor Amarillo</h3>
<img class="img-fluid rounded-circle" style="height: 200px;" src="{{ url_for('static', filename='images/contenedor-amarillo.jpg') }}" alt="">
<p class="lead">Envases de botellas de plásticos, latas, bebidas, bolsas.</p>
</div>
<div class="col-md-4 mt-4">
<h3>Contenedor Azul</h3>
<img class="img-fluid rounded-circle" style="height: 200px;" src="{{ url_for('static', filename='images/contenedor-azul.jpg') }}" alt="">
<p class="lead">Envases de papel y cartón</p>
</div>
<div class="col-md-4 mt-4">
<h3>Contenedor Verde</h3>
<img class="img-fluid rounded-circle" style="height: 200px;" src="{{ url_for('static', filename='images/contenedor-verde.jpg') }}" alt="">
<p class="lead">Los envases de botellas de vidrio y otros vidrios.</p>
</div>
</div>
</div>
<style>
body{
overflow-x: hidden;
}
</style>
{% endblock %}
contendor-amarillo.html:
{% extends 'base.html' %}
{% block content %}
<div class="container-fluid">
<h2 class="mt-4">Contenedor amarillo</h2>
{% if reciclaje_amarillo %}
{% for reciclaje in reciclaje_amarillo %}
<div class="card mt-4">
<div class="card-header">
<h4>{{ reciclaje.nombre_amarillo }}</h4>
</div>
<div class="card-body">
<blockquote class="blockquote mb-0">
<p>Codigo de barras: {{ reciclaje.codigo_amarillo }}</p>
<footer class="blockquote-footer text-success">Reciclado el {{ tiempo_legible(reciclaje.fecha) }}</footer>
</blockquote>
</div>
</div>
<hr>
{% endfor %}
{% else %}
<p class="lead">El contenedor está vacío</p>
{% endif %}
</div>
{% endblock %}
contendor-azul.html:
{% extends 'base.html' %}
{% block content %}
<div class="container-fluid">
<h2 class="mt-4">Contenedor azul</h2>
{% if reciclaje_azul %}
{% for reciclaje in reciclaje_azul %}
<div class="card mt-4">
<div class="card-header">
<h4>{{ reciclaje.nombre_azul }}</h4>
</div>
<div class="card-body">
<blockquote class="blockquote mb-0">
<p>Codigo de barras: {{ reciclaje.codigo_azul }}</p>
<footer class="blockquote-footer text-success">Reciclado el {{ tiempo_legible(reciclaje.fecha) }}</footer>
</blockquote>
</div>
</div>
<hr>
{% endfor %}
{% else %}
<p class="lead">El contenedor está vacío</p>
{% endif %}
</div>
{% endblock %}
contendor-verde.html:
{% extends 'base.html' %}
{% block content %}
<div class="container-fluid">
<h2 class="mt-4">Contenedor Verde</h2>
{% if reciclaje_verde %}
{% for reciclaje in reciclaje_verde %}
<div class="card mt-4">
<div class="card-header">
<h4>{{ reciclaje.nombre_verde }}</h4>
</div>
<div class="card-body">
<blockquote class="blockquote mb-0">
<p>Codigo de barras: {{ reciclaje.codigo_verde }}</p>
<footer class="blockquote-footer text-success">Reciclado el {{ tiempo_legible(reciclaje.fecha) }}</footer>
</blockquote>
</div>
</div>
<hr>
{% endfor %}
{% else %}
<p class="lead">El contenedor está vacío</p>
{% endif %}
</div>
{% endblock %}
puntaje.html:
{% extends 'base.html' %}
{% block content %}
<div class="background-image">
<div class="container">
<div class="content-center">
<div class="card text-center" style="background-color: rgba(255, 255, 255, .8);">
<div class="card-header">
<h3>Bienvenido</h3>
</div>
<div class="card-body">
<h3 class="card-title">Puntos totales: {{ puntos }}</h3>
<p class="card-text">¡Recicla más seguido para recibir más puntos!</p>
<a href="/" class="btn btn-primary">Ir a la página de inicio</a>
</div>
<div class="card-footer text-muted">
<cite class="blockquote-footer" style="font-size: 1rem;">Reciclar no es una obligación, es TU
responsabilidad.</cite>
</div>
</div>
</div>
</div>
</div>
<style>
body {
overflow-y: hidden;
}
.content-center {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.card {
margin-top: -3rem;
width: 800px;
}
.background-image {
background-image: url(../public/images/optimized_bottles-clean-close-up-cold-122803.jpg);
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
height: 100vh;
}
</style>
{% endblock %}
Ya tenemos agregado Bootstrap a nuestro proyecto, el cual le dará un diseño mucho más estético a nuestra aplicación.
➡ ¡Enhorabuena si has llegado hasta aquí! Continúa aprendiendo con nuestro Curso de Blockchain: