miércoles, 1 de marzo de 2023

13.- Desarrollo práctico de una aplicación. Herencia Plantillas, Css y Bootstrap.

Formatear el sitio web con bootstrap.

Es un framework css que nos sirve para formatear un sitio web entero utilizando css. Es 'responsive' ya que nos sirve para que se vea bien en cualquier tipo de dispositivo y además es prácticamente compatible con todos los navegadores.

Volviendo a nuestro proyecto lo que perseguimos es dar a la página web un formato general para lo cual utilizaremos Bootstrap y también herencia de plantillas. Así conseguiremos que todas las páginas tengan unas zonas comunes y otras zonas específicas donde cargaremos el contenido correspondiente a cada página. En definitiva lo que queremos conseguir es mejorar la apariencia.

Link a bootstrap.

Para utilizar Bootstrap hay dos caminos:

1.- Linkar con los contenidos o librerías de Bootstrap de forma externa. 

2.- Descargarnos los contenidos para utilizarlo en local. En este caso utilizaremos django-bootstrap5 para integrar BootStrap en nuestro proyecto. Esta aplicación descarga los archivos que necesitaremos de Bootstrap, los coloca en los lugares apropiados dentro de nuestro proyecto y hace que las plantillas de estilo estén disponibles para uso. 

Para instalar Django-Bootstrap5 ejecutaremos la siguiente instrucción. Como comente al inicio del curso yo recomiendo hacerlo todo dentro de un entorno virtual. Se puede instalar a través de dos paquetes distintos. Elige uno u otro.

(entorno_virtual) $ pip install django-bootstrap5

* con este paquete tendrás que registrar la aplicación en settings 
  como 'django_bootstrap5'

o

(entorno_virtual) $ pip install django-bootstrap-v5

* con este paquete tendrás que registrar la aplicación en settings
  como 'bootstrap5'


A continuación tenemos que añadir el siguiente código para incluir Django-Bootstrap5 dentro de INSTALLED_APPS en el archivo settings.py del proyecto. Como he instalado el segundo paquete la registraré de la siguiente forma.


PracticaDjango/PracticaDjango/settings.py

INSTALLED_APPS = [
    # Mis aplicaciones.
    'Proyecto_web_app',
    # Aplicaciones de terceros.
    'bootstrap5',
    # Aplicaciones por defecto de Django
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',      
]

Asegúrate de que colocar esta aplicación 'bootstrap5' después de mis aplicaciones pero antes de las aplicaciones por defecto de Django.

A continuación vamos a ir al directorio de nuestra aplicación y crearemos una carpeta llamada static. Dentro de esta, crearemos otra con el nombre de la aplicación. Aquí irá todo el contenido estático, ya sean imágenes o código CSS. Su contenido no dependerá del contexto de la petición y será el mismo para todos los usuarios. Dentro de esta crearemos otras dos carpetas de momento. Una llamada css que como su nombre indica recogerá el código css de la aplicación y otra img donde guardaremos las imágenes que puedan utilizar las plantillas.

La estructura del directorio sería la siguiente:

/PracticaDjango -> 

    /Proyecto_web_app ->

       /static ->

                /Proyecto_web_app ->

                        /css

                        /img

Lo primero que tenemos que hacer es crear una plantilla que será el modelo para todas las demás plantillas que podamos crear. Se llamará base.html y de esta heredarán todas las demás. Como vamos a necesitar una barra de navegación crearé una muy sencilla que luego usare en la plantilla base. 

PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/nav.html

<nav>
    <ul>
        <li><a href="#">Home</a></li>
        <li><a href="#">Servicios</a></li>
        <li><a href="#">Blog</a></li>
        <li><a href="#">Tienda</a></li>
        <li><a href="#">Contacto</a></li>
    </ul>
</nav>

Y este sería el código de la plantilla base "base.html"

PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/base.html

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Página en Prueba</title>
</head>
<body>
    <header>
        <h1>UnikGAME TIENDA VIRTUAL</h1>
        <hr>
        <p>Barra de navegación insertada</p>
        {% include "Proyecto_web_app/nav.html" %}
    </header>
    <section>
        <p>Aquí irá la parte cambiante de la página</p>
    </section>
    <footer>
        <hr>
        <small>Politica de Privacidad . Aviso Legal . Cookies</small>
    </footer>
</body>
</html>


Herencia de Plantillas


La plantilla base.html va a ser nuestra plantilla madre. La vamos a utilizar para modificar las cinco plantillas hijas, que heredarán los elementos comunes de esta (header, barra de navegación y footer), y que se corresponderán con cada una de las páginas de nuestro sitio web. (Home, Servicios, Blog, Tienda y Contacto). Sin embargo, ten en cuenta que si una plantilla hija tiene algún elemento que también este definido en la plantilla padre, los elementos de esta última prevalecerán sobre las de la plantilla padre. 

Por ejemplo, en la plantilla padre has definido un determinado footer y en la hija otro distinto, el footer de la plantilla hija prevalece sobre la de la padre. No habrá herencia.

Empecemos modificando la plantilla base.html que ya tenemos para indicar las partes o bloques que las otras plantillas pueden escribir. Si tuviéramos hoja de estilos tendríamos que definirlas aquí. En este fichero incluiremos los bloques:

{% block title %} {% endblock %}
{% block content %} {% endblock %}

para definir el titulo y el contenido de las páginas hijas.

PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/base.html

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <header>
        <h1>UnikGAME TIENDA VIRTUAL</h1>
        <hr>
        <p>Barra de navegación insertada</p>
        {% include "Proyecto_web_app/nav.html" %}
    </header>
    <section>
        {% block content %}{% endblock %}
    </section>
    <footer>
        <hr>
        <small>Politica de Privacidad . Aviso Legal . Cookies</small>
    </footer>
</body>
</html>


Y AHORA VIENE LA GRACIA DEL ASUNTO....

Si te fijas la plantilla padre, base.html, tiene 24 líneas de código y sin contar las de la barra de navegación que están aparte en el archivo nav.html. Pues bien, si no usáramos herencia, tendríamos que copiar todo esto en cada una de las páginas que diseñemos, junto con las modificaciones necesarias para que mostrarán lo que queremos que aparezcan en cada una de ellas. Sin embargo usando la herencia:

PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/inicio.html

<!--Cargamos la plantilla base-->
{% extends "Proyecto_web_app/base.html" %}

<!-- Establecemos el titulo de la página -->
{% block title %}Home{% endblock %}

<!-- Definimos su contenido -->
{% block content %}
    <h2>Esta es la página de Inicio.</h2>
{% endblock %}
Esta primera plantilla hija la comenzamos diciendo que use la plantilla padre con la instrucción:

{% extends "Proyecto_web_app/base.html" %}

Para continuar reescribiendo los dos bloques, con lo que estamos diciendo al programa que busque estos bloques en la plantilla padre y que los sustituya por los que hemos puesto en esta plantilla.

El primero {% block title %} {% endblock %} modifica el título de la vista de inicio (HOME).

El segundo {% block content %} {% endblock %} mostrará el contenido propio de la página de inicio.

Este es el resultado.

Renderizado Página Home


Tenemos que modificar el resto de las cuatro plantillas exactamente de la misma forma. Una vez hecho lo anterior modificaremos el archivo nav.html que contiene el menú de navegación. Tiene que recoger la dirección de cada una de las páginas. Para obtener la URL de una vista en Django, podemos usar el siguiente código:

<a href="{% url 'nombre_de_la_vista' parámetros %}">Link a la vista</a>

donde el 'nombre_de_la_vista' vendrá definido como:

app_name:name  


Para ver de donde sale esto vamos a ir al archivo urls.py de nuestra aplicación Proyecto_web_app.


from django.urls import path
from . import views

app_name  = 'Proyecto_web_app'

urlpatterns = [
    path('', views.home, name='home'),
    path('servicios/', views.servicios, name='servicios'),
    path('tienda/', views.tienda, name='tienda'),
    path('blog/', views.blog, name='blog'),
    path('contacto/', views.contacto, name='contacto'),

]

Por ejemplo en mi ordenador al ejecutar el servidor, la dirección para la página de la tienda es:

http://127.0.0.1:8000/tienda/

Pues bien, al usar {% url 'Proyecto_web_app:tienda' %}, esta ya se construye automáticamente. Esto tiene la gran ventaja que si mas tarde queremos cambiar la url de alguna de las páginas no tendremos que tocar este archivo puesto que se construirán automáticamente.

Dicho lo cual el archivo nav.html quedaría de la siguiente forma.


PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/base.html

<nav>
    <ul>
        <li><a href="{% url 'Proyecto_web_app:home' %}">Home</a></li>
        <li><a href="{% url 'Proyecto_web_app:servicios' %}">Servicios</a></li>
        <li><a href="{% url 'Proyecto_web_app:blog' %}">Blog</a></li>
        <li><a href="{% url 'Proyecto_web_app:tienda' %}">Tienda</a></li>
        <li><a href="{% url 'Proyecto_web_app:contacto' %}">Contacto</a></li>
    </ul>
</nav>

También es posible pasar argumentos a la vista a través de la URL, agregando parámetros de la siguiente forma:

<a href="{% url 'nombre_de_la_vista' argumento1 argumento2 %}">Link a la vista con argumentos</a>


Dando formato a las plantillas con CSS y BootStrap.


Aunque el Css lo podemos aplicar tanto desde la propia etiqueta de HTML, como dentro del archivo HTML, lo normal es que construyas la hoja de estilo en un archivo externo ya que de esta forma te vale para todas las páginas html que crees, básicamente entre otras muchas ventajas.

Si construimos nosotros mismos las hojas de estilo, por ejemplo en un archivo llamado styles.css, tenemos que guardarlo, como ya vimos, dentro de una carpeta llamada "static". En Django esa carpeta "static" va a recoger todo lo relativo a contenido estático del proyecto (css, javascript, imagenes, pdf etc)

Mi archivo de ejemplo se encontrará en el siguiente directorio:

PracticaDjango / Proyecto_web_app/ static / Proyecto_web_app / css / styles.css

Y para cargarlo tendremos que utilizar la siguiente instrucción dentro de las etiquetas <head> de  la página HTML que queramos aplicarlo. 

                    {% load static %}

                    <link rel="stylesheet" href="{% static 'Proyecto_web_app/css/styles.css' %}">

Ahora bien, en vez de hacerlo tu mismo puedes utilizar el framework Bootstrap. Bootstrap te permite crear interfaces web con CSS y Javascript, visualmente más agradables, con elementos que ya están prediseñados y que además se adaptan al tamaño del dispositivo en el que se visualicen. Para poder usarlo tenemos que iniciar el archivo base.html con:

                    {% load bootstrap5 %}

* o {% load django_bootstrap5 %} dependiendo del paquete utilizado.

y dentro de la etiqueta <head> usar el cargador:

                    {% bootstrap_css %} para usar las clases de bootstrap

                    {% bootstrap_javascript %} para usar java

                    {% bootstrap_messages %} para usar las alertas de Django.

De esta forma la cabecera del archivo base.html quedaría de la siguiente forma:

PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/base.html

{% load bootstrap5%}
{% load static %}
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}{% endblock %}</title>
    
    <!-- Add Bootstrap CSS -->
    {% bootstrap_css %}
    {% bootstrap_javascript %}
    {% bootstrap_messages %}
<!-- Add additional CSS in static file --> <link rel="stylesheet" href="{% static 'Proyecto_web_app/css/styles.css' %}"> </head> ...

Rediseño del Header y de la barra de navegación.


Comenzamos con el header de la plantilla base.html.

PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/base.html

<header>
        <h1 class="container-fluid text-center text-white bg-dark py-5">
            UnikGAME
        </h1>
        {% include "Proyecto_web_app/nav.html" %}        
</header>
 A la etiqueta <h1> le aplicamos la clase "container-fluid" que es es un contenedor que ocupa todo el ancho de la ventana. También centramos el texto con text-center, le aplicamos un color blanco (text-white) y un fondo oscuro al contenedor con bg-dark. Para que el texto este centrado le aplicamos un padding tanto arriba como abajo de nivel 5. (py-5). Puedes encontrar información sobre estos elementos aquí.

Como incluimos la barra de navegación a través del archivo nav.htlm vamos a modificar el mismo.  Dentro de la documentación de Bootstrap si entramos en la sección de documentación y bajamos a Components encontramos ejemplos de muchos elementos. Dentro de estos, esta Navs&tabs donde tenemos un ejemplo sencillo de barra de navegación que vamos a aplicar.

PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/nav.html

<ul class="nav justify-content-center my-2">

  <li class="nav-item px-lg-4">
    <a class="nav-link text-uppercase text-expanded" href="{% url 'Proyecto_web_app:home' %}">
    Home
    </a>
  </li>

  <li class="nav-item px-lg-4">
    <a class="nav-link text-uppercase text-expanded" href="{% url 'Proyecto_web_app:servicios' %}">
    Servicios
    </a>
  </li>

  <li class="nav-item px-lg-4">
    <a class="nav-link text-uppercase text-expanded" href="{% url 'Proyecto_web_app:blog' %}">
    Blog
    </a>
  </li>

  <li class="nav-item px-lg-4">
    <a class="nav-link text-uppercase text-expanded" href="{% url 'Proyecto_web_app:tienda' %}">
    Tienda
    </a>

  </li>
  <li class="nav-item px-lg-4">
    <a class="nav-link text-uppercase text-expanded" href="{% url 'Proyecto_web_app:contacto' %}">
    Contacto
    </a>
  </li>
  
</ul>


Con estos pocos cambios conseguimos que la base de las plantillas tenga ahora este aspecto.

sección header modificada con bootstrap

Para acabar de mejorar el diseño añadiremos un fondo a la página. 

PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/base.html

...
    <!-- Parte cambiante de las plantillas -->
    <div class="fondo">
    <section>
        {% block content %}{% endblock %}
    </section>
    </div>
...

PracticaDjango/Proyecto_web_app/static/Proyecto_web_app/css/styles.css

.fondo {
    background-image: url(../img/bg_main.jpg);
    height: 58vh;
    background-position: center;
    background-repeat: no-repeat;
    background-size: cover;
}
Retocaremos un poco tambien el footer:
... 
<div class="fondo">
        <section>
            {% block content%}{% endblock %}
        </section>
    </div>

    <footer class="footer bg-dark text-center text-white">
        <hr>
        <small>
            <a href="#">Política de privacidad</a> ·
            <a href="#">Aviso legal</a> ·
            <a href="#">Cookies</a>
        </small>
        <p class="py-2"><small>(c) UnikGAME 2023</small></p>
    </footer>
</body>
</html>

Quedaría una apariencia parecida a esta, común para todas las páginas.


pagina base finalizada


Parar finalizar vamos a añadir código para resaltar la página activa, cambiando el color azul del enlace por el negro.

{% if request.path == '/' %}text-black{% endif %}">

request.path nos dará la url de la página en la que nos encontramos. De esta forma si la url definida coincide con la que nos encontramos, se aplicará la clase text-black. El archivo nav.html quedaría de la siguiente forma:

PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/nav.html

<ul class="nav justify-content-center my-2">

    <li class="nav-item px-lg-4">
        {% url 'Proyecto_web_app:home' as home_url %}
        <a class="nav-link text-uppercase fw-bold text-expanded {% if request.path == home_url %}
        text-black{% endif %}" href="{{ home_url }}">Inicio</a>
    </li>

    <li class="nav-item px-lg-4">
        {% url 'Proyecto_web_app:servicios' as servicios_url %}
        <a class="nav-link text-uppercase fw-bold text-expanded {% if request.path == servicios_url %}
        text-black{% endif %}" href="{{ servicios_url }}">Servicios</a>
    </li>

    <li class="nav-item px-lg-4">
        {% url 'Proyecto_web_app:blog' as blog_url %}
        <a class="nav-link text-uppercase fw-bold text-expanded {% if request.path == blog_url %}
        text-black{% endif %}" href="{{ blog_url }}">Blog</a>
    </li>

    <li class="nav-item px-lg-4">
        {% url 'Proyecto_web_app:tienda' as tienda_url %}
        <a class="nav-link text-uppercase fw-bold text-expanded {% if request.path == tienda_url %}
        text-black{% endif %}" href="{{ tienda_url }}">Tienda</a>
    </li>

    <li class="nav-item px-lg-4">
        {% url 'Proyecto_web_app:contacto' as contacto_url %}
        <a class="nav-link text-uppercase fw-bold text-expanded {% if request.path == contacto_url %}
        text-black{% endif %}" href="{{ contacto_url }}">Contacto</a>
    </li>
</ul>

1. `<ul class="nav justify-content-center my-2">`: Esto define una lista no ordenada (ul) con algunas clases de Bootstrap, como "nav" y "justify-content-center", para crear una barra de navegación centrada en la página.

2. Cada elemento de la lista (li) representa un enlace en la barra de navegación.

3. `{% url 'Proyecto_web_app:nombre_de_la_vista' as nombre_de_url %}`: Estas líneas utilizan la plantilla de Django para generar la URL de una vista de Django y asignarla a una variable llamada "nombre_de_url." Esto permite que el código sea más limpio y evita repetir la generación de URLs en cada enlace.

4. `<a>`: Cada enlace (`<a>`) contiene clases de Bootstrap y utiliza la variable previamente definida, como `{{ home_url }}`, para definir el atributo "href" con la URL correspondiente a la vista.

5. `{% if request.path == nombre_de_url %}text-black{% endif %}`: Esta es una condición que verifica si la URL actual (`request.path`) coincide con la URL generada para el enlace (la variable "nombre_de_url"). Si es cierto, se agrega la clase "text-black" al enlace, lo que generalmente se utiliza para resaltar el enlace activo en la barra de navegación.


Si por ejemplo pinchamos en la página 'blog' la página web quedaría de la siguiente manera:

resaltando el enlace



martes, 28 de febrero de 2023

12.- Desarrollo Práctico de una aplicación. Creación Proyecto, Aplicación, Primeras vistas y Plantillas.

Vamos a crear una aplicación completa de Django y a la vez profundizando más en varios conceptos. La aplicación que crearemos tendrá cinco vistas:

  • Inicio
  • Servicios
  • Tienda 
  • Blog
  • Contacto
Todo lo que sigue ya lo hemos visto en capitulos previos, asi que no me detendré a comentarlo. Comenzaremos creando el proyecto con el nombre que queramos, en mi caso usaré PracticaDjango:


$ django-admin startproject PracticaDjango

Esto nos creará un directorio con la siguiente estructura:

PracticaDjango       > Directorio Padre 
    manage.py        > Archivo de gestión de Django.
    PracticaDjango --> Es un directorio

Es siguiente caso es crear la aplicación que gestionará la aplicación web. Así que entramos en el directorio padre y creamos la aplicación:

PracticaDjango$ python manage.py startapp Proyecto_web_app

Salida:

PracticaDjango       > Directorio Padre 
    manage.py        > Archivo de gestión de Django.
    PracticaDjango --> Es un directorio
    Proyecto_web_app > El directorio de la app recien creada.

Recuerda que una cosa es el proyecto y otra la aplicación. En Django dentro de un proyecto puedes tener muchas aplicaciones.

Ahora es un buen momento para verificar que nuestro proyecto funciona, así que vamos a la consola y ejecutamos el servidor:

PracticaDjango$ python manage.py runserver

Abrimos el navegador y entramos en localhost:8000 y si todo ha ido bien verás la siguiente pantalla:

pantalla inicio de Django

Seguidamente creamos de forma sencilla las vistas de cada una de las cinco url de la aplicación. Para ello vamos al archivo views.py y para hacer algunas pruebas importamos la clase HttpResponse.

PracticaDjango/Proyecto_web_app/views.py

from django.shortcuts import render, HttpResponse

# Create your views here.

def home(request):
    return HttpResponse('Home')

def servicios(request):
    return HttpResponse('Servicios')

def tienda(request):
    return HttpResponse('Tienda')

def blog(request):
    return HttpResponse('Blog')

def contacto(request):
    return HttpResponse('Contacto')

Ahora nos vamos a registrar las URLS. Importamos las vistas y luego registramos las direcciones.

PracticaDjango/PracticaDjango/urls.py

from django.contrib import admin
from django.urls import path
from Proyecto_web_app import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),
    path('servicios/', views.servicios, name='servicios'),
    path('tienda/', views.tienda, name='tienda'),
    path('blog/', views.blog, name='blog'),
    path('contacto/', views.contacto, name='contacto'),
]

Volvemos a ejecutar el servidor, si lo hemos cerrado y probamos cada una de las url, para ver que todas las vistas funcionan.

Ej para la vista blog.

vista blog funcionando correctamente


Reorganización de las url para un mejor funcionamiento.


Hay que tener en cuenta que nosotros creamos un proyecto en Django y este puede tener varias aplicaciones. De igual forma es muy frecuente que una aplicación Django la puedas reaprovechar en diferentes proyectos. Hasta ahora registramos nuestras Urls dentro del archivo urls.py que estaba en PracticaDjango. Pero imaginaros si tuviéramos que registrar en el las urls, no de una única aplicación, sino de tres, cuatro o quince aplicaciones. En este caso este archivo tendría muchísimo código y nos seria complicado saber que urls son de una aplicación y cual de otra. Así que lo bueno es que las urls de cada aplicación estén dentro de su directorio. 

Para ello seguimos las instrucciones que ya nos indica Django en el archivo urls.py del Proyecto.

1.- Creamos un archivo llamado urls.py pero dentro de la aplicación.

2.- Luego dentro de ese archivo importamos el path

PracticaDjango/Proyecto_web_app/urls.py

from django.urls import path

3.- A continuación importamos las vistas de la aplicación.

PracticaDjango/Proyecto_web_app/urls.py

from django.urls import path
from . import views

4.- Tenemos que trabajar con la lista urlpatterns y para no tener que escribir tanto, vamos al url del proyecto, copiamos el urlpatterns y lo pegamos aquí. Lógicamente quitamos la vista admin que no corresponde a la aplicación, sino al proyecto.

PracticaDjango/Proyecto_web_app/urls.py

from django.urls import path
from Proyecto_web_app import views

app_name = "Proyecto_web_app"

urlpatterns = [
    path('', views.home, name='home'),
    path('servicios/', views.servicios, name='servicios'),
    path('tienda/', views.tienda, name='tienda'),
    path('blog/', views.blog, name='blog'),
    path('contacto/', views.contacto, name='contacto'),
]


'app_name' en Django se utiliza para definir un nombre único para la aplicación, evitando conflictos de nombres con otras aplicaciones que pudiéramos crear luego.

'name' se asigna a cada URL dentro de la aplicación y proporciona una etiqueta única para esta URL en nuestro proyecto.

Estos atributos nos ayudarán a a organizar y referenciar fácilmente las URLs en nuestro código y en las plantillas de Django.  Por ejemplo cuando más adelante tengamos que construir una URL en una plantilla en vez de usar una referencia absoluta para referirnos a la vista 'home' haremos referencia a ella como 'Proyecto_web_app:home'.

Y como hemos movido todas las vistas de la aplicación, en el archivo urls.py del PROYECTO quitamos todo lo que hemos movido y dejamos solo el admin. Quitamos también la importación de las vistas. En definitiva lo dejamos como estaba al principio. Solo nos falta enlazar el url del proyecto con el url de la aplicación, lo haremos a través del path como nos pone Django en la documentación de este mismo archivo, usando el comando 'include' que previamente habremos de importar.

PracticaDjango/PracticaDjango/ulrs.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls), 
    path('proyecto_web_app/', include('Proyecto_web_app.urls')), 
]

Llegado este punto, tenemos que comprobar que funcionan bien las urls teniendo en cuenta el path que acabamos de escribir. Para acceder a las vistas tendríamos que hacer lo siguiente. Vamos a ver como acceder a una con un ejemplo.

aceso a url tienda
Pero al hacer esto, hemos complicado un poco la url porque tenemos que agregar 'proyecto_web_app', que es el nombre de nuestro proyecto. Si queremos teclear las urls como antes, que sin poner nada nos llevaba a las vistas, tenemos que ir al urls del proyecto y en el path tenemos que dejar vacía la raíz para que no haya que poner nada y así, funcionaría como al principio.

PracticaDjango/PracticaDjango/urls.py 

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('Proyecto_web_app.urls')),  
]

url acortada de la aplicación

Plantillas de las vistas.


Nos vamos a la carpeta de la aplicación y creamos una carpeta llamada 'templates' y dentro de esta carpeta crearemos otra con el nombre también de la aplicación que será donde depositaremos las plantillas html. Tenemos que crear una plantilla para cada una de las vistas. Vamos a hacer una como ejemplo para la vista inicio pero tenemos que crear otras iguales para el resto.

PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/home.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home</title>
</head>
<body>
    Home
</body>
</html>
Ahora debemos modificar el archivos de las vistas de la aplicación para que renderice las plantillas que habremos creado. Pero para que esto funcione es IMPORTANTE registrar nuestra aplicación. Tenemos que ir al archivo settings.py del proyecto y el la lista de INSTALLED_APPS tenemos que registrar la aplicación.

PracticaDjango/PracticaDjango/settings.py

...
# Application definition

INSTALLED_APPS = [
    # Nuestras aplicaciones.
    'Proyecto_web_app.apps.ProyectoWebAppConfig',
    # Aplicaciones por defecto.
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
...

Podemos aprovechar este mismo archivo para modificar nuestra hora local (TIME_ZONE) y el idioma (LANGUAGE_CODE) como ya vimos en el capitulo 1.

Después de lo anterior, ya solo nos queda modificar el archivo de las vistas de la aplicación para que se rendericen cada una de las vistas cuando se llame a la url correspondiente.

PracticaDjango/Proyecto_web_app/views.py

from django.shortcuts import render, HttpResponse

# Create your views here.

def home(request):
    return render(request, 'Proyecto_web_app/home.html')

def servicios(request):
    return render(request, 'Proyecto_web_app/servicios.html')

def tienda(request):
    return render(request, 'Proyecto_web_app/tienda.html')

def blog(request):
    return render(request, 'Proyecto_web_app/blog.html')

def contacto(request):
    return render(request, 'Proyecto_web_app/contacto.html')

domingo, 26 de febrero de 2023

11.- Django. API Forms

Django tiene principalmente dos clases para construir formularios: Form y ModelForm.

La primera es la que veremos en este capitulo. La segunda ModelForm se utiliza para crear formularios de forma dinámica a partir de los campos contenidos en modelos. La veremos en más detalle más adelante pero si quieres echarle un vistazo puedes consultar la documentación en https://docs.djangoproject.com/en/4.1/topics/forms/modelforms/.

Vamos con el uso de la clase Form.

 Documentación API Form.

Nos va a permitir crear y validar formularios de forma sencilla.

Para utilizarla lo primero que tenemos que hacer es crear en cualquier lugar de nuestro proyecto (aunque por convención se suele realizar en el mismo directorio que tengamos el views.py) un archivo llamado forms.py y dentro crearemos una clase para cada formulario que queramos crear.

gestionPedidos/forms.py

Una vez creado el archivo lo primero que tenemos que hacer es importar esta clase forms, y una vez importada crearemos una clase cuya instancia será el formulario de contacto que queremos crear. A la clase la podemos llamar como queramos y dentro de los argumentos hay poner forms.Form. A continuación especificaremos los campos que tendrá el formulario que construya esta clase.

gestionPedidos/forms.py

from django import forms

class FormularioContacto(forms.Form):
    #Especificamos los campos del formulario
    asunto = forms.CharField()
    email = forms.EmailField()
    # etiqueta textarea en django
    mensaje = forms.CharField(widget=forms.Textarea(attrs={'cols': 45, 'rows': 15}))

La clase Form es el corazón del sistema de manejo de formularios de Django. Especifica los campos en el formulario, su diseño, widgets de visualización, etiquetas, valores iniciales, valores válidos y (una vez validados) los mensajes de error asociados con campos no válidos.

Los campos que podemos utilizar en un formulario son los siguientes:

BooleanFieldCharFieldChoiceFieldTypedChoiceFieldDateField
DateTimeFieldDecimalFieldDurationFieldEmailFieldFileField
FilePathFieldFloatFieldImageFieldIntegerFieldGenericIPAddressField,
MultipleChoiceFieldTypedMultipleChoiceFieldNullBooleanFieldRegexFieldSlugField
TimeFieldURLFieldUUIDFieldComboFieldMultiValueField
SplitDateTimeFieldModelMultipleChoiceFieldModelChoiceField.

Los argumentos que son comunes a la mayoría de los campos anteriores son:

  • required: Si es True el campo no se puede dejar en blanco o dar un valor None. Los Campos son obligatorios por defecto, también puedes establecer required=False para permitir valores en blanco en el formulario.
  • label: label es usado cuando renderizamos el campo en HTML. Si label no es especificado entonces Django crearía uno a partir del nombre del campo al poner en mayúscula la primera letra y reemplazar los guiones bajos por espacios (por ejemplo. Renewal date).
  • label_suffix: Por defecto, se muestran dos puntos después de la etiqueta. Este argumento le permite especificar como sufijo diferente que contiene otros caracteres.
  • initial: El valor inicial para el campo cuando es mostrado en el formulario.
  • widget: El widget de visualización para usar.
  • help_text texto adicional que se puede mostrar en formularios para explicar cómo usar el campo.
  • error_messages: Una lista de mensajes de error para el campo. Puede reemplazarlos con sus propios mensajes si es necesario.
  • validators: Una lista de funciones que se invocarán en el campo cuando se valide.
  • localize: Permite la localización de la entrada de datos del formulario (consulte el enlace para obtener más información).
  • disabled: El campo se muestra pero su valor no se puede editar si esto es True. Por defecto es False.
Para ver como ha quedado y como crea Django el formulario vamos a abrir el shell de django para ver el resultado.

$ python manage.py shell
>>> from gestionPedidos.forms import FormularioContacto
>>> prueba = FormularioContacto()
>>> print(prueba)
La salida es el código html que se genera al instanciar la clase.

Salida:

<tr>
    <th><label for="id_asunto">Asunto</label></th>
    <td>      
      <input type="text" name="asunto" required id="id_asunto">     
    </td>
</tr>

<tr>
    <th><label for="id_email">Email:</label></th>
    <td>      
      <input type="email" name="email" required id="id_email">
     </td>
</tr>

<tr>
    <th><label for="id_mensaje">Mensaje:</label></th>
    <td>      
      <textarea name="mensaje" cols="45" rows="15" required id="id_mensaje">
      </textarea>           
    </td>
</tr>

Como se ve se lo esta formateando automáticamente con forma de tabla. Por defecto todos los campos son requeridos y también se les añade los label automáticamente.

Pero también podemos darle formato de párrafo (con etiquetas <p>).

$ python manage.py shell
>>> from gestionPedidos.forms import FormularioContacto
>>> prueba = FormularioContacto()
>>> print(prueba.as_p())
SALIDA


<p>
    <label for="id_asunto">Asunto</label>
    <input type="text" name="asunto" required id="id_asunto">
</p>

<p>
    <label for="id_email">Email:</label>
    <input type="email" name="email" required id="id_email">
</p>

<p>
    <label for="id_mensaje">Mensaje:</label>
    <textarea name="mensaje" cols="45" rows="15" required id="id_mensaje">
    </textarea>
</p>

Pero también se puede crear como una lista desordenada.

$ python manage.py shell
>>> from gestionPedidos.forms import FormularioContacto
>>> prueba = FormularioContacto()
>>> print(prueba.as_ul())
SALIDA

<li>
    
    <label for="id_asunto">Asunto</label>
    <input type="text" name="asunto" required id="id_asunto">
</li>

<li>
    <label for="id_email">Email:</label>
    <input type="email" name="email" required id="id_email">
</li>

<li>
    <label for="id_mensaje">Mensaje:</label>
    <textarea name="mensaje" cols="45" rows="15" required id="id_mensaje">
    </textarea>
</li>

Como se ve, django no incluye las etiquetas <table> ni tampoco <form>. Lo que si hace Django es validar los datos introducidos en los campos. Lo primero es que hemos visto es que los campos son requeridos, pero también le da otros tipos de validaciones. Por ejemplo en el campo email si se introduce un email no válido no lo permitiría. Esto implica que a la hora de enviar un formulario este puede ser válido o no (por ejemplo un email incorrecto o dejar en blanco un campo requerido)

Para saber si un formulario es válido tenemos el método dentro de la api forms que es is_valid(). Este método nos dice si ha pasado la validación, devolviendo True si es correcta o Falsa si no lo es. Si este método devuelve True podremos usar una propiedad que es cleaned_data. Esta propiedad devuelve los campos enviados pero ya validados. 

Vamos a verlo con un ejemplo. Creemos un formulario pasándole los campos como argumentos a través de un diccionario.

>>> mi_formulario=FormularioContacto({'asunto':'prueba','email':'usuario@correo.es','mensaje':'texto de prueba',})
>>> mi_formulario.is_valid()
True
>>> mi_formulario.cleaned_data
{'asunto': 'prueba', 'email': 'usuario@correo.es', 'mensaje': 'texto de prueba'}
>>> 


Vamos a aplicar todo esto a nuestro proyecto. Vamos al archivo de vista de contacto, donde ya teníamos un código que funcionaba correctamente, lo comentamos y vamos a crear uno nuevo con esto que hemos visto. Lo primero es importar la clase que hemos creado para poder crear el formulario.


gestionPedidos/views.py

# Para poder enviar emails a través del formulario de contacto.
from django.core.mail import send_mail
from django.conf import settings
# api de formulario
from gestionPedidos.forms import FormularioContacto

...
def contacto(request):
    # Costruyendo el mismo formulario mediante API forms
    if request.method=="POST":
        mi_formulario=FormularioContacto(request.POST)
        if mi_formulario.is_valid():
            informacion_formulario = mi_formulario.cleaned_data
            send_mail(
                informacion_formulario['asunto'],
                informacion_formulario['mensaje']+' '+ informacion_formulario['email'],
                settings.EMAIL_HOST_USER,
                [settings.EMAIL_DESTINO],
            )
            return render(request, 'gracias.html')
    else: # metodo GET
        mi_formulario=FormularioContacto()
    
    return render(request, 'formulario_contacto_api.html', {'form':mi_formulario})


La primera vez que entramos en la vista se renderiza un formulario en blanco ya que el método utilizado no es el método POST. La plantilla 'formulario_contacto_api.html' aun no esta creada pero lo haremos luego. A esa plantilla le pasamos el código html con la creación del formulario. Una vez que se rellena el formulario comprobamos que este sea válido y si lo es, pasamos la información ya filtrada a la variable informacion_formulario con la propiedad cleaned_data.  Luego accedemos a los valores de los campos a través del diccionario. 

Ya solo queda crear el archivo 'formulario_contacto_api.html'

gestionPedidos/templates/formulario_contacto_api.html

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Contacto</title>
</head>
<body>
    <h1>Contacta con nosotros.</h1>
    {% if form.errors %}
    <p style="color: red;">Por favor,revisa este campo.</p>
    {% endif %}

    <form action="" method="post">
        {% csrf_token %} 
        <table>
            {{ form.as_table}}
        </table>
        <input type="submit" value="Enviar">
    
    </form>
</body>
</html>

La API forms crea una validación por defecto, pero aun así que lo primero que haremos es prever un posible error con {% if forms.errors %} con su código para validarlo. Luego ponemos las etiquetas <form>, el token, el <table> y dentro la API form ya crea el formulario por nosotros.

El resultado es el siguiente. Si intentas por ejemplo introducir un correo electrónico no válido o dejar un campo requerido en blanco el propio formulario te avisará de ello.

Formulario Generado con el APi form

En el ejemplo anterior hemos instanciado el formulario como una tabla {{ form.as_table }}. Le hemos dicho a Django que renderice los campos del formulario usando elementos HTML para diseñar una tabla. También podríamos haberlos renderizado como un párrafo con as_p o como una lista desordenada usando as_ul. Otra opción sería renderizar cada campo iterando a través de cada uno de los campos del formulario, como en el siguiente ejemplo:


{% for field in form %}

<div>

{{ field.errors }}
{{ field.label_tag }} {{ field }}

</div>
{% endfor %}
  

Como te habrás dado cuenta también hemos añadido el {{ csrf_token }}. Esta etiqueta añade al formulario un campo oculto con un token autogenerado para evitar el ataque maliciosos cross-site request forgery (CSRF). Puedes encontrar más información sobre este ataque en https://owasp.org/www-community/attacks/csrf.

La plantilla de esta etiqueta genera un campo oculto que se renderiza de esta forma:

<input type='hidden' name='csrfmiddlewaretoken'
value='26JjKo2lcEtYkGoV9z4XmJIEHLXN5LDR' />

Por defecto Django siempre comprueba si esta esta etiqueta en todos las solicitudes ('request') que se hacen a través del método POST, por tanto recuerda incluirla en todos los formularios que se envíen a través del método POST.