Blog
Hasta el momento, nuestra aplicación tiene un frontend en HTML puro y falta agregarle estilos. Si ya tienes algo de experiencia en el desarrollo web sabrás que estos estilos se agregan gracias al CSS, pero para este caso utilizaremos un framework de CSS llamado Bootstrap. Por fortuna tenemos una librería de python que nos permite integrar esto de manera sencilla y rápida, es importante destacar que si no conoces este framework investigues un poco acerca de él y de cómo usarlo, pero en esta parte de la lección te voy a explicar los aspectos más básicos.
Formas de instalar Bootstrap
A través de una CDN
De sus siglas en ingles “CDN” (Content Delivery Network) y que en español se traduce a Red de distribución de contenidos, su función a fin es la de maximizar el ancho de banda para el acceso a los datos de clientes por la red. En forma resumida una CDN es un enlace que copiamos en las plantillas html de nuestro proyecto y cuando una persona visite nuestra app, dicha CDN le dice al navegador que debe descargar ciertos recursos para poder visualizar la web correctamente y de una manera rápida y eficaz para que la carga del mismo sea más rápida.
Agregándolo a nuestros archivos estáticos
Esta es la forma que más recomiendo, ya que nos permite hacer que Bootstrap este dentro de nuestro proyecto. Y es como suelo hacerlo para la mayoría de mis trabajos, ya que si en algún momento no poseo acceso internet podré seguir contando con Bootstrap debido a que no requiero del internet para acceder a sus CDN como en el método de instalación anterior, pero este método es mucho más extenso de explicar. Existen muchos tutoriales en la red que puedes buscar para instalarlo de esta forma, por ejemplo puedes buscarlo de la siguiente manera “como instalar los archivos css y js de Bootstrap en mi proyecto”.
En este punto, haremos unos cambios en el frontend de nuestra aplicación. Vamos a llamarla Unipython Tweet para hacer referencia de una versión de Twitter, porque practicamente estamos haciendo un microblog. Por ende, vamos a ir cambiando algunos aspectos de nuestro HTML a lo largo del curso.
Para esta lección usaremos el método de las CDN, ya que lo importante es mostrarles como funciona el framework y que es lo que le hará a nuestro proyecto. Para esto iremos a la web oficial de Bootstrap, hacemos click en “Get Started” y buscamos las CDN CSS y las JS para luego dirigirnos a la plantilla base.html y le agregamos dichos enlaces CDN de la siguiente manera:
<html> <head> {% if titulo %} <title>{{ titulo }} - Unipython Tweet</title> {% else %} <title>Unipython Tweet</title> {% endif %} <!-- Bootstrap CDN CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> <link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}"> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-2"> <a class="navbar-brand" href="#"><img class="img-fluid" src="{{ url_for('static', filename='icons/unipython.png') }}" alt="icono-logo"></a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> <a class="nav-link" href="{{ url_for('index') }}">Inicio</a> </li> {% if current_user.is_anonymous %} {% else %} <li class="nav-item"> <a class="nav-link" href="{{ url_for('explorar') }}">Explorar</a> </li> {% endif %} </ul> </div> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav ml-auto"> {% if current_user.is_anonymous %} <li class="nav-item"> <a class="nav-link" href="{{ url_for('login') }}">Login</a> </li> {% else %} <li class="nav-item"> <a class="nav-link" href="{{ url_for('perfil_usuario', username=current_user.username) }}">Perfil</a> </li> <li class="nav-item"> <a class="nav-link" href="{{ url_for('logout') }}">Cerrar sesión</a> </li> {% endif %} </ul> </div> </nav> <div class="container-fluid mt-3"> {% with messages = get_flashed_messages() %} {% if messages %} {% for message in messages %} <div class="alert alert-primary" role="alert"> <span>{{ message }}</span> </div> {% endfor %} {% endif %} {% endwith %} {% block contenido %} {% endblock %} </div> <!-- Bootstrap CDN JS --> <script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" 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>
Agregando estilos de Boostrap a las plantillas de usuario invitado
Si hasta ahora te preguntas ¿a que me refiero con clases de Bootstrap?, la respuesta es que las clases es lo que usamos para enlazar a los estilos CSS que se creen, en este caso esos estilos ya están creando por Bootstrap, es por eso que anteriormente te he dicho que gracias a Bootstrap no es necesario escribir tanto CSS. Vamos a dirigirnos a nuestra plantilla base.html y agregaremos unas clases de Bootstrap:
<html> <head> {% if titulo %} <title>{{ titulo }} - Unipython Tweet</title> {% else %} <title>Unipython Tweet</title> {% endif %} <!-- Bootstrap CDN CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> <link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}"> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-2"> <a class="navbar-brand" href="#"><img class="img-fluid" src="{{ url_for('static', filename='icons/unipython.png') }}" alt="icono-logo"></a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> <a class="nav-link" href="{{ url_for('index') }}">Inicio</a> </li> {% if current_user.is_anonymous %} {% else %} <li class="nav-item"> <a class="nav-link" href="{{ url_for('explorar') }}">Explorar</a> </li> {% endif %} </ul> </div> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav ml-auto"> {% if current_user.is_anonymous %} <li class="nav-item"> <a class="nav-link" href="{{ url_for('login') }}">Login</a> </li> {% else %} <li class="nav-item"> <a class="nav-link" href="{{ url_for('perfil_usuario', username=current_user.username) }}">Perfil</a> </li> <li class="nav-item"> <a class="nav-link" href="{{ url_for('logout') }}">Cerrar sesión</a> </li> {% endif %} </ul> </div> </nav> <div class="container-fluid mt-3"> {% with messages = get_flashed_messages() %} {% if messages %} {% for message in messages %} <div class="alert alert-primary" role="alert"> <span>{{ message }}</span> </div> {% endfor %} {% endif %} {% endwith %} {% block contenido %} {% endblock %} </div> <!-- Bootstrap CDN JS --> <script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" 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>
Como puedes ver, ha cambiado bastante nuestro diseño ya que he agregado una navegación. Esta navegación se encuentra en la documentación de la web oficial de Bootstrap en el apartado de componentes dicho apartado ya tiene muchos diseños predefinidos que podemos usar si no queremos diseñar todo desde 0 como fue mi caso. Muy bien, ahora vamos a darle estilo a nuestro formulario para luego ver los cambios que hemos hecho. Vamos a dirigirnos a la plantilla iniciar_sesion.html para agregarle las clases de Bootstrap:
{% extends "base.html" %} {% block contenido %} <div class="container-fluid"> <h1>Iniciar Sesion</h1> <form action="" method="post" novalidate> {{ form.hidden_tag() }} <div class="form-group"> {{ form.nombre.label }} {{ form.nombre(size=32, class="form-control") }} {% for error in form.nombre.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </div> <div class="form-group"> {{ form.contraseña.label }} {{ form.contraseña(size=32, class="form-control") }} {% for error in form.contraseña.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </div> <div class="form-group form-check"> {{ form.recordar(class="form-check-input") }} {{ form.recordar.label(class="form-check-label") }} </div> {{ form.enviar(class="btn btn-primary btn-lg mb-3") }} <p><a href="{{ url_for('registro') }}">Registro</a></p> <p> Olvidaste tu contraseña? <a href="{{ url_for('recuperar_contraseña') }}">Click aquí</a> </p> </form> </div> {% endblock %}
Ahora, si vamos a nuestra página de inicio de nuestra aplicación deberíamos ver algo como esto:
Esto es gracias a Bootstrap, pero no solamente esto es lo que puedo hacer este framework. Esto es solo el inicio ya que Bootstrap puede hacer mucho más y es lo que veremos a continuación, pero lo primero que quiero decirte es que Bootstrap es un framework pensado para desarrollar web totalmente rápido ya que lo que realmente hace es permitirnos hacer uso de sus clases de CSS para modificar rápidamente nuestra web sin necesidad de escribir por nuestra cuenta el CSS. Si es verdad que en algunas ocasiones necesitaremos escribir un poco de CSS nosotros mismos pero Bootstrap ya nos minimiza mucho el hacerlo, al final de esta lección explicaré las clases de Bootstrap que hemos usado para darle el estilo a nuestra aplicación.
{% extends "base.html" %} {% block contenido %} <div class="container-fluid"> <h1>Registro</h1> <form action="" method="post"> {{ form.hidden_tag() }} <div class="form-group"> {{ form.username.label }}<br> {{ form.username(size=30, class="form-control") }}<br> {% for error in form.username.errors %} <span style="color:red;">{{ error }}</span> {% endfor %} </div> <div class="form-group"> {{ form.email.label }}<br> {{ form.email(size=60, class="form-control") }}<br> {% for error in form.email.errors %} <span style="color: red;">{{ error }}</span> {% endfor %} </div> <div class="form-group"> {{ form.contraseña.label }}<br> {{ form.contraseña(size=32, class="form-control") }}<br> {% for error in form.contraseña2.errors %} <span style="color: red;">{{ error }}</span> {% endfor %} </div> <div class="form-group"> {{ form.contraseña2.label }}<br> {{ form.contraseña2(size=30, class="form-control") }}<br> {% for error in form.contraseña2.errors %} <span style="color: red;">{{ error }}</span> {% endfor %} </div> {{ form.submit(class="btn btn-primary btn-lg") }} </form> </div> {% endblock %}
Ahora nuestro formulario de registro lucirá de la siguiente manera: Muy bien, para terminar con los formularios nos faltarían el de recuperar las contraseñas de los usuarios y el de resetear la contraseña (es el que se envía vía correo electrónico al usuario), agreguemos entonces las clases de Bootstrap en la plantilla recuperar_contraseña.html:
{% extends "base.html" %} {% block contenido %} <div class="container-fluid"> <h1>{{ titulo }}</h1> <form action="" method="post"> {{ form.hidden_tag() }} <div class="form-group"> {{ form.email.label }}<br> {{ form.email(size=64, class="form-control") }}<br> {% for error in form.email.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </div> {{ form.submit(class="btn btn-primary btn-lg") }} </form> </div> {% endblock %}
Ahora este formulario debe poder mirarse de la siguiente forma: Por último modificamos el formulario de la plantila resetear_contraseña.html:
{% extends "base.html" %} {% block contenido %} <div class="container-fluid"> <h1>Cambiar contraseña</h1> <form action="" method="post"> {{ form.hidden_tag() }} <div> {{ form.contraseña.label }}<br> {{ form.contraseña(size=32, class="form-control") }}<br> {% for error in form.contraseña.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </div> <div> {{ form.contraseña2.label }}<br> {{ form.contraseña2(size=32, class="form-control") }}<br> {% for error in form.contraseña2.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </div> {{ form.submit(class="btn btn-primary btn-lg") }} </form> </div> {% endblock %}
Listo, con esto hemos terminado de asignar las clases para los formularios que se encuentran fuera del inicio de sesión de nuestra app.
Agregando estilos de Bootstrap a las plantillas de usuario logueado
Vamos a comenzar modificando la subplantilla $post.html que creamos hace unas lecciones atrás, vamos a dirigirnos a dicha plantilla para agregar los cambios:
<table class="table table-hover"> <tr> <td width="70px"> <a href="{{ url_for('perfil_usuario', username=post.autor.username) }}"> <img src="{{ post.autor.imagen_perfil(70) }}" /> </a> </td> <td> <a href="{{ url_for('perfil_usuario', username=post.autor.username) }}"> {{ post.autor.username }} </a> Twitteó: <br> {{ post.cuerpo }} </td> </tr> </table>
Ya con esto, hemos colocado un estilo muy bonito para que los posts se muestren de una manera distinta. Pero aún nos falta agregar otras clases en una plantilla muy importante como lo es el index.html así que vamos a ese archivo y lo modificamos así:
{% extends "base.html" %} {% block contenido %} <div class="container-fluid"> <h1>Hola, {{ current_user.username }}!</h1> {% if form %} <form action="" method="post"> {{ form.hidden_tag() }} <div class="form-group mb-0"> {{ form.post.label }}<br> {{ form.post(cols=32, rows=4, class="form-control") }}<br> {% for error in form.post.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </div> {{ form.submit(class="btn btn-primary btn-sm") }} </form> {% endif %} {% for post in posts %} <p> {% include '$post.html' %} </p> {% endfor %} <nav aria-label="..."> <ul class="pagination pagination-md"> {% if pagina_ant %} <li class="page-item"> <a class="page-link" href="{{ pagina_ant }}">Página anterior</a> </li> {% endif %} {% if pagina_sig %} <li class="page-item"> <a class="page-link" href="{{ pagina_sig }}">Página siguiente</a> {% endif %} </li> </ul> </nav> </div> {% endblock %}
Si miramos nuestros cambios, ahora la vista que anteriormente teníamos en la ruta /index
debe verse de esta manera: Por otro lado también puedes revisar la ruta explorar, ya que si recuerdas ambas rutas utilizan la misma plantilla index.html así que con esto ya terminamos de darle los estilos a nuestra app con la ayuda de Bootstrap, más sin embargo quiero volver a repetirte que esto no es solo lo que hace este framework, sólo estamos dando una vista muy por encima de lo que es capaz de llegar a hacer.
Nota: Además también me gustaría destacarte que existen otros frameworks de CSS pero los más populares son Bootstrap el cual es propiedad actualmente de Twitter y Materialize el cual es propiedad de Google, estos son los más utilizados en las aplicaciones que se desarrollan al día de hoy.
Explicando un poco las clases de Bootstrap
A lo largo de esta lección utilizamos varías clases de este framework de css para darle una mejor “fachada” un mejor acabado en nuestro frontend. La mejor manera de entender Bootstrap es prácticando sus ejemplos que se encuentran en su documentación, a continuación voy a explicarte un poco para que funcionan algunas de las clases que usamos para mejorar nuestra web. En los formularios utilizamos una clase padre para agrupar tanto la etiqueta del formulario y el input del mismo estos dos se encuentran dentro de un div que contiene la clase form-group que lo que hace es agregar un margin bottom para alinear así los elementos que estén dentro de él, el input contiene una clase llamada form-control que su función es maquetar lo mejor posible la manera en como se ve el input. Lo que hicimos fue agregarle los estilos predefinidos que tiene Bootstrap dentro de esas clases, para darle un mejor acabado a nuestro formulario, el código que usamos luce de esta manera:
<div class="form-group"> {{ form.username.label }} {{ form.username(size=30, class="form-control") }}<br> {% for error in form.username.errors %} <span style="color:red;">{{ error }}</span> {% endfor %} </div>
También usamos un navbar en nuestra plantilla base.html, básicamente esta es una barra de navegación hecha por Bootstrap, de hecho si vamos aquí podemos encontrar todos los navbar que Bootstrap dispone (también podemos diseñar el nuestro propio, pero ya esto requiere que conozcas muy bien las clases de bootstrap), estos son estilos rápidos que bootstrap ya nos da para que los implementemos en nuestro sitio web y solo modifiquemos los textos e incluso agregarles más si queremos, lo mismo sucede con los formularios, tarjetas, alertas y entre otros componentes que puedes encontrarlos en esa sección de componentes como te lo explique al inicio de esta lección. Por último utilizamos las clases para maquetar botones, las cuales las agregamos con jinja ya que estamos haciendo uso de él para renderizar los inputs, a continuación voy a mostrarte un ejemplo de un input estático con Bootstrap y uno dinámico con Bootstrap (como los que tenemos en nuestros formularios) Input estático con Bootstrap:
<div class="form-group"> <label for="usuario">Usuario</label> <input class="form-control" type="text" id="nombre" required name="nombre" size="32"> </div>
Input dinámico con Bootstrap:
<div class="form-group"> {{ form.username.label }}<br> {{ form.username(size=30, class="form-control") }} {% for error in form.username.errors %} <span style="color:red;">{{ error }}</span> {% endfor %} </div>
Si te fijas, en el input dinámico form.username()
debemos pasarle la clase de bootstrap como argumento ya que así es como recibe la librería flask_wtf
los atributos para renderizarlos en la plantilla html y para este caso le decimos que queremos renderizar el atributo class con la clase de css form-control
Estos son algunos de las clases que usamos en nuestro proyecto, pero Bootstrap es un framework muy extenso y es necesarios aprenderlo bien para poder maquetar nuestros sitios de una manera profesional. Por ejemplo puedo anexarte un formulario que hice con Bootstrap de un proyecto pasado en el que trabajé y el resultado que obtuve fue el siguiente:
➡ Continúa aprendiendo con nuestro Curso de Flask – Python: