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: