sábado, 25 de febrero de 2023

Web Scraping

 ¿Que es el Web Scraping?

Es usar, en este caso, Python para extraer contenido  y datos de un sitio web. Para poder usarlo es necesario tener unos conocimientos mínimos de como se estructura el lenguaje HTML. Este se basa en etiquetas. Y precisamente esas etiquetas son las que vamos a utilizar para extraer esos datos. 

Vamos a verlo con un ejemplo.  Supongamos que nos interesa extraer el valor liquidativo de un fondo de inversión. Ese valor liquidativo se publica por ejemplo en la siguiente página web.


https://www.quefondos.com/es/fondos/ficha/index.html?isin=ES0164853014


pagina web donde se recoge el valor del fondo

Para poder extraer el valor liquidativo tenemos que instalar las librerías - BeautifulSoup - y - Request -. 

La librería BeautifulSoup nos sirve para extraer información de contenido en formato HTML o XML.

La librería Requests se utiliza en Python para hacer peticiones HTTP.

Usaremos también la librería webbrowser que ya está instalada por defecto en la biblioteca estándar de Python y que usaremos al final del programa para abrir el navegador y ver la página web de donde hemos extraído los datos. Si no tienes instaladas las librerías lo puedes hacer con:

$ pip install beautifulsoup4
$ pip install requests

 El programa empezará importando las librerías y estableciendo en una variable la página web desde donde vamos a descargar los datos.

main.py

#!/usr/bin/env python3

# Si no los tenemos, instalamos BeautifulSoup y requests
# pip install beautifulsoup4
# pip install requests

from bs4 import BeautifulSoup
import requests
import webbrowser # esta instalado por defecto, nos lleva a una pagina web desde python.

URL = "https://www.quefondos.com/es/fondos/ficha/index.html?isin=ES0164853014"

Vamos a utilizar requests para obtener un objeto donde se encuentre la página que queremos con todos sus elementos.

...
URL = "https://www.quefondos.com/es/fondos/ficha/index.html?isin=ES0164853014"
page = requests.get(URL)

Si ejecutáramos este programa y el servidor nos proporcionase la página de forma correcta, obtendríamos la siguiente respuesta.

<Response [200]>

Sin embargo lo que a nosotros nos interesa es el contenido de esa página, lo que conseguiremos usando page.content, y luego extraeremos el código HTML para poder buscar el dato que queremos con el argumento "html.parser".

...
URL = "https://www.quefondos.com/es/fondos/ficha/index.html?isin=ES0164853014"
page = requests.get(URL)
# Lo que nos interesa de la pagina es el contenido por lo que usamos page.content
# y luego que lo pase en formato html
soup = BeautifulSoup(page.content, 'html.parser')

Si imprimimos esta variable "soup" obtendremos el código HTML de la página.

Código HTML de la página. "soup" 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<html lang="es-ES">
<head>
<title>CAIXABANK SELECCION TENDENCIAS, FI PLUS (ES0164853014) · CAIXABANK ASSET MGMT</title>
<meta content="Fondo de inversión, Ficha, Informe, CAIXABANK SELECCION TENDENCIAS, FI PLUS, ISIN, ES0164853014, Gestora, CAIXABANK ASSET MGMT, Política de inversión, Valor liquidativo, Rentabilidad, Ranking, Riesgo, Rating VDOS, Comisiones, Gráfico, Evolución, Histórico" name="keywords"/>
<meta content="Consulta el informe de CAIXABANK SELECCION TENDENCIAS, FI PLUS (ISIN:ES0164853014). Gestionado por
CAIXABANK ASSET MGMT. Categoría VDOS: MIXTO FLEXIBLE" name="description"/>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<meta content="no-cache" http-equiv="cache-control">
<meta content="no-cache" http-equiv="pragma">
<meta content="index, follow" name="robots">
...
<div class="w100">
<h4 id="bodytitle_h3_3">Última valoración</h4>
<p>
<span class="floatleft">Valor liquidativo: </span><span class="floatright">14,677700 EUR</span>
</p>
<p>
<span class="floatleft">Patrimonio (miles de euros):</span><span class="floatright">1.338.861,29</span>
</p>
<p>
<span class="floatleft">Fecha: </span><span class="floatright">16/02/2023</span>
</p>
</div>
...


Ahora viene la parte manual del tema y lo más importante. Lo que necesitamos es encontrar dentro de que etiquetas está el valor que queremos localizar. Dentro de todo el código HTML de la página el valor liquidativo está dentro de la etiqueta <div class="w100">. Sin embargo al buscar en la página por esta etiqueta van a aparecer muchos elementos, más de los que nos gustaría, pero por lo menos ya hemos hecho una primera acotación.

Aquí os muestro el código que queremos buscar dentro de la página. Para analizar una página web una buena herramienta que se puede utilizar, es dentro del propio navegador buscar en herramientas, "herramientas para desarrolladores". Eso nos facilitará ver el código de la parte que nos interesa capturar. En nuestro caso es el siguiente:

<div class="w100">
<h4 id="bodytitle_h3_3">Última valoración</h4>
<p>
<span class="floatleft">Valor liquidativo: </span><span class="floatright">14,574500 EUR</span>
</p>
<p>
<span class="floatleft">Patrimonio (miles de euros):</span><span class="floatright">1.328.576,22</span>
</p>
<p>
<span class="floatleft">Fecha: </span><span class="floatright">19/02/2023</span>
</p>
<p>
<span class="floatleft">1 día: </span><span class="floatright"><span class="igual">0,00%</span></span>
</p>
</div>

 Para ello usaremos:

result = soup.find_all('div', class_="w100")

En la variable result se almacenará en forma de lista cada una de las etiquetas <div> que a la vez contengan la clase con el nombre "w100". Una de ellas contendrá el valor liquidativo que estamos buscando. Pero como en el código hay varias de ellas tenemos que buscar algo que diferencie la que nosotros buscamos del resto. En este caso, dentro de la etiqueta <div class="w100"> la que contiene el valor liquidativo es la que dentro tiene esta etiqueta <h4 id="body_title_h3_3>. Lo que haremos será iterar sobre la lista result buscando con i.find() aquella que contenga esta ultima etiqueta. Cuando la encontremos, usaremos el mismo método para localizar la etiqueta que contiene el valor liquidativo. El código completo de la aplicación sería el siguiente:

main.py

#!/usr/bin/env python3

# Si no los tenemos, instalamos BeautifulSoup y requests
# pip install beautifulsoup4
# pip install requests

from bs4 import BeautifulSoup
import requests
import sys
import webbrowser  # esta instalado por defecto, nos lleva a una pagina web desde python.

URL = "https://www.quefondos.com/es/fondos/ficha/index.html?isin=ES0164853014"
page = requests.get(URL)
# Lo que nos interesa de la pagina es el contenido por lo que usamos page.content
# y luego que lo pase en formato html
soup = BeautifulSoup(page.content, 'html.parser')
# Para la busqueda se pasa la etiqueta html en la que esta contenido el dato
# result = soup.find_all('span', class_="floatright")
result = soup.find_all('div', class_="w100")

valores = {}
for i in result:
    # getText() - elimina todas las etiquetas html y nos deja solo el contenido de texto.
    # find() - busca la primera conformidad con el parámetro buscado.
    # print(i.getText())
    key = i.find('h4', id="bodytitle_h3_3")
    if key is not None:
        titulo = i.find('h4', id="bodytitle_h3_3")
        valor = i.find('span', class_='floatright')
        print(titulo)
        print(valor)
        print(f"{titulo.getText()}: {valor.getText()}")
    print("------------------------------------------------------")

# Nos muestra la página web que le pasemos la url.
webbrowser.open(URL)
Finalmente, la salida que buscamos sería la siguiente:

------------------------------------------------------
<h4 id="bodytitle_h3_3">Última valoración</h4>
<span class="floatright">14,574500 EUR</span>
Última valoración: 14,574500 EUR
------------------------------------------------------
Puedes encontrar el código completo del programa en el siguiente enlace.


domingo, 12 de febrero de 2023

10.- Django. Formulario de contacto y envío de email con datos. Variables de Entorno.

En este capitulo crearemos un ejemplo de formulario de contacto, veremos el método POST y aprovecharemos para mostrar como enviar un email que nos informe que hay un nuevo usuario y nos envíe la información introducida.

Empezamos.

Para crear el formulario de contacto lo primero que debemos hacer es irnos a las vistas, al archivo views.py y al final crear una nueva vista. De momento lo único que va a hacer es devolvernos un renderizado de un archivo, que aun no hemos creado, pero que lo haremos luego (contacto.html)

gestionPedidos/views.py

...
def contacto(request):
    '''Vista para definir un formulario de contacto.'''
    return render(request, "contacto.html")

El siguiente paso es irnos a las urls para registrar la vista.

tiendaVirtual/urls.py

...
from django.contrib import admin
from django.urls import path
# Siempre hay que importar las vistas de la aplicación
from gestionPedidos import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('busqueda_juegos/', views.busqueda_juegos),
    path('buscar/', views.buscar),
    path('contacto/', views.contacto),
]
y lo que nos falta es crear en la carpeta "templates", el archivo html del formulario que será el siguiente:

gestionPedidosl/templates/contacto.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>Contáctanos</title>
</head>
<body>
    <h1>Contacta con nosotros.</h1>
    <form action="/contacto/" method="POST">
        {% csrf_token %}
        <!-- Cuadro de texto de entrada -->
        <p>Asunto <input type="text" name="asunto"> </p>
        <p>Email <input type="text" name="email"> </p>
        <p>Mensaje<p>
        <p></p><textarea name="mensaje" rows="15" cols="45"></textarea></p>
        <input type="submit" value="Enviar">
    </form>
</body>
</html>
Es importante dentro de la etiqueta del formulario añadir {% csrf_token %} para evitar un ataque malicioso llamado "Cross Site Request Forgery".  Permite validar que las peticiones son realizadas desde un sitio web autorizado y no desde otras fuentes. Si te interesa una mayor explicación puedes encontrar la información aquí.

Otro matiz importante es que cuando pulsemos el botón enviar usaremos como forma de envío el método "POST". La diferencia con el método "GET", que usamos en el capitulo anterior, es que mientras que este envía los datos usando la URL y por tanto los podemos ver su contenido en la barra de navegación, el método POST los envía de forma que no podamos verlos (en segundo plano y ocultos para el usuario). 

Luego definimos los elementos del formulario que tendrá la siguiente forma:


formulario de contacto


De momento el formulario no hace nada, pero podemos probarlo para ver que funciona correctamente.

Ahora, para comprobar que este formulario (que está utilizando el método "POST") funciona, vamos a hacer lo siguiente. Si al dar al botón enviar todo va bien, nos devolverá un renderizado indicándonos que la información se ha enviado correctamente. Volvemos al archivo de vistas views.py:

gestionPedidos/views.py

...
def contacto(request):
    '''Vista para definir un formulario de contacto.'''
    if request.method=="POST":
        return render(request, "gracias.html")
    else:
        return render(request, "contacto.html")

La explicación es la siguiente.

La primera vez que entramos en la página del formulario, no estamos utilizando el método "POST" sino el "GET" con lo que se renderizará el formulario de contacto. Ahora bien, cuando le damos al botón enviar, entonces la información se envía de nuevo a esta vista /contacto/ usando, ahora si, el método "POST", con lo que se renderizará la página html "gracias.html", que crearemos ahora y que nos servirá para confirmar el envío y que todo funciona correctamente.

gestionPedidos/templates/gracias.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>Enviado</title>
</head>
<body>
    <h3>Gracias por enviar la información.</h3>
</body>
</html>

Si entramos en la url /contacto/ y enviamos el formulario nos debería salir el mensaje de Enviado. (siempre que tengamos el servidor conectado, claro)


archivo gracias.html


Envío de Emails en Django.


Enviar correos con Django es muy sencillo. Para enviar correos con Django es necesario tener un servidor local de protocolo simple de transferencia de correo (SMTP), o poder acceder a un servidor SMTP externo, como tu proveedor de servicios de correo electrónico (Gmail, Yahoo, Outlook etc)

Para ello, vamos a utilizar la librería core.mail. Para poder enviar mails lo primero que tenemos es ir al archivo settings.py y configurar una serie de parámetros. En este archivo, al final del todo pondremos las siguientes instrucciones.

tiendaVirtual/settings.py

...
# Configuración de servidor de correo de django
EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = 'smtp.outlook.com' o 'smtp.gmail.com' etc
EMAIL_PORT = 587
EMAIL_HOST_USER = 'usuario@outlook.com' o 'usuario@gmail.com'
EMAIL_HOST_PASSWORD = 'la contraseña del correo'
EMAIL_USE_TLS = True
El EMAIL_HOST es el servidor de correo que vas a usar para enviar los correos. Como gmail siempre me ha dado problemas, yo personalmente utilizo outlook. Utilizaremos el método smtp para enviar los correos. El resto es buscar la configuración asociada al correo que utilices y que puedes buscar en Google. El valor por defecto sino se especifica nada el "localhost"

EMAIL_PORT es el puerto por el que se comunica el servicio SMTP, que es el protocolo que se utiliza para enviar el correo. Por defecto el puerto es el 25.
 
EMAIL_HOST_USER es tu usuario o cuenta de correo.

EMAIL_HOST_PASSWORD es la contraseña de tu correo electrónico. Si estas usando Gmail como servidor SMTP desde que implemento la verificación en dos pasos y otras medidas de seguridad, no puedes usar tu contraseña del correo directamente. En vez de ello, Google te permite crear una contraseña especifica para la aplicación desde tu cuenta.  Para ello ve al navegador y abre la siguiente dirección, https://myaccount.google.com/. En el menú de la izquierda haz click en Seguridad, verás una pantalla como esta:

The Signing in to Google page for Google accounts

En "como inicias sesión en Google", selecciona "Verificación en dos pasos". En la parte inferior de la página selecciona "Contraseñas de aplicación". Ahí introduce un nombre que te ayude a recordar dónde vas a utilizar la contraseña de la aplicación. Después selecciona "Generar". Para introducir la contraseña de aplicación, sigue las instrucciones que aparecen en pantalla. La contraseña de aplicación es el código de 16 caracteres que se genera en tu dispositivo. 

Si no puedes usar un servidor SMTP (Gmail, Outlook, yahoo o cualquier otro servicio de correo), puedes decirle a Django que envíe estos emails a la consola del sistema remplazando la entrada EMAIL_BACKEND de archivo settings.py por esta otra:

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Al usar esta configuración, Django enviará los emails a la consola del sistema en lugar de enviarlos fuera de tu equipo. Este resulta bastante útil para probar la aplicación.

Para probar si la configuración funciona vamos a hacer una prueba desde consola. Nos vamos a la misma e introducimos el comando.

$ python manage.py shell

Lo primero es importar la librería core.mail y dentro de este la función sendmail(). Luego le pasamos los argumentos que nos pide. Puedes encontrar la documentación Django sobre como enviar email aqui. Quedaría algo como esto:

(miEntorno) chema@lenovo:~/Cursos/DJANGO/tiendaVirtual$ python manage.py shell
Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.core.mail import send_mail
>>> send_mail(
... 'Aqui el asunto del correo',
... 'Aqui el mensaje',
... 'tu_usuario@outlook.com',
... ['correo_destino@gmail.com'],
... fail_silently=False,
... )
1
Entramos en la consola interactiva de Django e importamos la librería que comentamos previamente.
Luego rellenamos los campo de asunto, mensaje etc. 'desde_correo@outlook.com' es el correo que registramos previamente en el archivo settings.py, es decir el correo que utilizamos para enviar los mensajes. 

La opción fail_silently=False final sirve para que si algo falla nos muestre las trazas del error y poder tener una idea de lo que ocurre. 
El 1 final es que el correo se envió correctamente.

Una vez que vemos que los parámetros son correctos y que nos llegan los correos electrónicos ¿Cómo hacemos para que desde nuestro formulario de contacto, lo que el usuario haya tecleado cuando le de al botón enviar nos llegue a nuestro correo electrónico?

Pues tenemos que ir al archivo de vistas y concretamente a la vista contacto. Allí tenemos que adaptar lo que hemos visto para enviar un email con los contenidos de los campos del formulario.

Lo primero empezaremos importando del módulo django.core.mail el método send_mail y también settings para poder usar las propiedades que definimos antes. Luego es hacer lo mismo que realizamos por consola. El archivo views.py quedaría tal que así.

 gestionPedidos/views.py
from django.shortcuts import render
from django.http import HttpResponse
# Para poder usar el modelo Articulos de la base de datos
from gestionPedidos.models import Articulos
# Para poder enviar emails del formulario de contacto.
from django.core.mail import send_mail
from django.conf import settings

...
def contacto(request):
    '''Vista para definir un formulario de contacto.'''
    if request.method=="POST":

        asunto = request.POST['asunto']
        mensaje = request.POST['mensaje'] + " " + request.POST['email']
        email_from = settings.EMAIL_HOST_USER

        send_mail(
            asunto,
            mensaje,
            email_from,
            ['correo_destino@correo.com'],
            )

        return render(request, "gracias.html")
    else:
        return render(request, "contacto.html")
Las variable asunto coge su valor del formulario, a través del método POST. En el formulario también llamamos asunto a la casilla de texto que recogía la información. Lo mismo ocurre con mensaje que coge su valor del mensaje que el usuario tecleo en el formulario y después de un espacio en blanco le añado el email para que cuando lo recibamos podamos contestarle si queremos.

email_from coge su valor del archivo de configuración y recordad que este correo, es la cuenta de correo que hemos configurado para enviar los archivos. Luego en una lista pondremos el correo o correo de destino a donde queremos que llegue la información de ese formulario.

Por ultimo si se ha enviado correctamente se renderizará el archivo "gracias.html".

Usar Variables de entorno para preservar datos privados.


Para preservar información importante y confidencial en nuestros proyectos de Django es útil usar las variables de entorno. De forma esquemática el proceso es el siguiente:

1.- Instalamos el siguiente paquete.

pip install django-environ

2.- Cuando este instalado, creamos un archivo llamado .env en el mismo directorio en donde este el archivo settings.py. Su contenido esta formado por parejas de clave-valor y es muy importante que no haya espacios ni antes ni después del igual ya que sino no funcionará. Por ejemplo, para usar como variables de entorno el EMAIL_HOST_USER y EMAIL_HOST_PASSWORD pondríamos lo siguiente:

tiendaVirtual/.env

EMAIL_HOST_USER=usuario@outlook.com
EMAIL_HOST_PASSWORD=#ladificilcontraseña
En el directorio base o raíz, añadimos al archivo .gitignore (o lo creamos si no lo está) lo siguiente:

*.pyc
__pycache__
db.sqlite3
/env
*.env
.vscode
Esto es para que GIT el controlador de versiones que normalmente se usa en los proyectos no haga un seguimiento de estos archivos, ni de sus valores.

Por último en el archivo settings.py en la línea de importación añadimos

tiendaVirtual/settings.py

import environ
env = environ.Env()
environ.Env.read_env()
y ya podemos usar los valores de entorno en el archivo de configuración de la siguiente forma:

tiendaVirtual/settings.py

...

# Configuración de servidor de correo de django
EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = 'smtp.outlook.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = env('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')
EMAIL_USE_TLS = True
Ahora bien, si en el archivo de vistas de la aplicación queremos usar para algo los valores de estas variables tendremos que importarlos desde el archivo settings.py, ya que se encuentran ahí. Lo podemos hacer usando:

from django.conf import settings
Por eso en el apartado anterior al definir las vistas usamos "email_from = settings.EMAIL_HOST_USER"





miércoles, 1 de febrero de 2023

9.- Django - Formularios.

Vamos a ver como crear un formulario, de momento de forma manual y luego como enviar la información al servidor.

- Request object Django.

Empezamos creando la carpeta que contendrá las plantillas de formularios, como ya dijimos en capítulos anteriores se puede usar cualquier nombre pero por convención se utiliza "templates". (al crearla con este nombre dentro del proyecto no hace falta registrarla.)



Ahora dentro de esta carpeta creamos el archivo html que contendrá el formulario: busqueda_juegos.html.

Creamos una estructura típica de html, para diseñar el formulario. Es muy sencilla, solamente un cuadro donde meter texto y un botón para enviar el formulario.

gestionPedidos/templates/busqueda_juegos.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>Busqueda de Productos</title>
</head>
<body>
    <form action="/buscar/" method="get">
        <!-- Cuadro de texto de entrada -->
        <input type="text" name="prd">
        <input type="submit" value="Buscar">
    </form>
</body>
</html>

Cuando pulsemos el botón enviar de este formulario nos debe llevar a una dirección llamada /buscar/ que crearemos luego. Como medio de trasporte usaremos el método GET para enviar la información, aunque luego veremos como usar el método POST y sus diferencias.

Identificamos la información que enviaremos en el cuadro de texto con el nombre de "prd" ya que tendremos que rescatarla luego.

Ahora que tenemos el formulario creado, tenemos que crear una vista que nos lleve al mismo. Así que nos vamos al archivo views.py y allí definimos la vista que nos va a devolver el renderizado del formulario.

from django.shortcuts import render

# Create your views here.

def busqueda_juegos(request):
    return render(request, "busqueda_juegos.html")
Tenemos que registrar la URL que nos lleve a este formulario en el archivo tiendaVirtual/urls.py

from django.contrib import admin
from django.urls import path
# Siempre hay que importar las vistas de la aplicación
from gestionPedidos import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('busqueda_juegos/', views.busqueda_juegos),
]
Vamos a probarlo. Ejecutamos el servidor de Django:

$ python manage.py runserver

y accedemos a la dirección que hemos registrado para el formulario:

formulario de busqueda de juegos
Aunque aun no hace nada, porque no hemos construido la página o url a donde se enviará el formulario /buscar/

Vamos con ello. Creemos una vista y registrémosla luego en el PATH. Cuando esta vista reciba la información del formulario a través del método request.GET[] lo almacenará en la variable mensaje y lo mostrará en pantalla. 'prd' se corresponde a lo que estaba almacenado en el cuadro de texto.

gestionPedidos/views.py

from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.

def busqueda_juegos(request):
    return render(request, "busqueda_juegos.html")

def buscar(request):
    mensaje = f"Articulo Buscado: {request.GET['prd']}"
    # como request.GET es un diccionario se podría usar también
    # mensaje = f"Articulo Buscado: {request.GET.get('prd')}
    return HttpResponse(mensaje)
Nos falta registrar esta vista en el url.

tiendaVirtual/urls.py

from django.contrib import admin
from django.urls import path
# Siempre hay que importar las vistas de la aplicación
from gestionPedidos import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('busqueda_juegos/', views.busqueda_juegos),
    path('buscar/', views.buscar),
]

y ahora si vamos al navegador y entramos en la url /busqueda_juegos/ y tecleamos cualquier cosa y lo enviamos, nos mostrará eso mismo que hayamos puesto en la pantalla.

Lo que vamos a hacer ahora es que la información introducida en el formulario sirva para hacer búsquedas en la base de datos que teníamos creada. La idea es que vayamos al formulario e introduzcamos el nombre de un juego. 

El ordenador busque ese nombre en la base de datos de los artículos (gestionpedidos_Articulos) dentro del campo "nombre" y en el caso en el que este, nos devuelva la información de todos los campos de ese articulo (nombre, categoría y precio). Si no está nos dirá que el artículo no se ha encontrado.

Hasta ahora esto son los juegos que están en la base de datos.

juegos registrados en la base de datos

Antes de ponernos con ello tenemos que solucionar un pequeño problema. Este es, que si en el formulario no tecleas nada y pulsas el botón de enviar no te sale nada. Hasta ahora esto no era un problema pero si puede serlo si buscas "nada" en una base de datos.

Para ello nos vamos al archivo de vitas e introducimos un condicional para que si el formulario viene vacío, nos lo indique.

gestionPedidos/views.py

...
# Create your views here.

def busqueda_juegos(request):
    return render(request, "busqueda_juegos.html")

def buscar(request):
    # Condicional para verificar que el formulario no 
    # venga vacio.
    if request.GET['prd']:
        mensaje = f"Articulo Buscado: {request.GET['prd']}"
    else:
        mensaje="No se ha introducido información para buscar el producto."
    
    return HttpResponse(mensaje)

Una vez hecho lo anterior vamos a buscar un juego en la base de datos. 

Voy a escribir el código y luego lo comento. Empezamos yendo al archivo de vistas views.py


gestionPedidos/views.py

from django.shortcuts import render
from django.http import HttpResponse
# Para poder usar el modelo Articulos de la base de datos
from gestionPedidos.models import Articulos

# Create your views here.

def busqueda_juegos(request):
    return render(request, "busqueda_juegos.html")

def buscar(request):
    # Condicional para verificar que el formulario no 
    # venga vacio.
    if request.GET['prd']:
        # mensaje = f"Articulo Buscado: {request.GET['prd']}"
        juego=request.GET['prd']
        items = Articulos.objects.filter(nombre__icontains=juego)
        return render(request, "resultados_busqueda.html", {'items':items, 'query':juego})
    else:
        mensaje="No se ha introducido información para buscar el producto."
    
    return HttpResponse(mensaje)
Comenzamos importando el modelo Articulos para poder trabajar con el.

La instrucción del mensaje la voy a comentar por no borrarla. Luego creo la variable "juego" que almacenará lo que viene desde el formulario. Después creamos otra variable "items" que recogerá los juegos que existan en la base de datos que cumplan el criterio de búsqueda. Esto lo conseguimos usando el método filter junto el nombre del campo seguido con la propiedad con doble guion bajo __icontains.

Esto equivale en lenguaje SQL a:


* item es lo que viaje dentro de la variable juego.

En el campo "nombre" de la base de datos busca cualquier cosa que contenga la palabra que vaya en item. Es como si tuviese caracteres comodín por delante y por detrás de la palabra que vaya en item. 

Ejemplo.  Si en el formulario tecleo la palabra "of" buscará en la base de datos, por el campo nombre todos los registros que tengan delante, en medio, contengan o tengan detrás la palabra of. No distingue en la búsqueda entre mayúscula o minúsculas, da igual.

__icontains

Ahora vamos a construir el html que muestra la información, que es el que se ve arriba. Terminamos primero con el archivo de vistas y le decimos al archivo de vistas que nos renderice el request llamandodo al archivo "resultados_busqueda.html" (que aun no tenemos creado) y que nos renderice los resultados de la búsqueda.

 return render(request, "resultados_busqueda.html", {'items':items, 'query':juego})

El archivo "resultados_busqueda.html" es el siguiente:


gestionPedidos/templates/resultados_busqueda.html.py

<!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>Resultados Busqueda</title>
    <style>
        td {
          text-align: center;
        }
    </style>
</head>
<body>
    <p>Estas buscando: <strong>{{query}}</strong></p>
    <table border="1">
    {% if items %}
        <h4>Artículos encontrados: {{items|length}} juegos</h4>
        <!-- Recorremos la lista -->
        <tr>
            <th>Nombre</th>
            <th>Categoria</th>
            <th>Precio</th>
        </tr>
        {% for item in items%}
            <tr>
                <td>
                    {{item.nombre}} 
                </td>
                <td>
                    {{item.categoria}}
                </td>
                <td>
                    {{item.precio}}
                </td>
            </tr>    
        {% endfor %}
        
    {% else %}
        <p>La información solicitada no existe.</p>
    {% endif %}
    </table>
</body>
</html>

    

Recorremos los datos encontrados a través de un bucle y mostrando los resultados en una tabla de html.

¿Cómo limitar el número de caracteres a buscar en la base de datos?


Si dejamos que el usuario introduzca un número ilimitado de caracteres en el campo de búsqueda esto podría ralentizar las búsquedas y el servidor, causando problemas. Esto es muy sencillo y lo realizaremos con un condicional de Python en el archivo de las vistas.

gestionPedidos/views.py

....
def buscar(request):
    # Condicional para verificar que el formulario no 
    # venga vacio.
    if request.GET['prd']:
        # mensaje = f"Articulo Buscado: {request.GET['prd']}"
        juego=request.GET['prd']
        if len(juego)>20:
            mensaje = "El texto de busqueda es demasiado largo (max 20 caracteres)"
        else:
            items = Articulos.objects.filter(nombre__icontains=juego)
            return render(request, "resultados_busqueda.html", {'items':items, 'query':juego})
    else:
        mensaje="No se ha introducido información para buscar el producto."
    
    return HttpResponse(mensaje)
Otra forma es definirlo en dentro de la plantilla "busqueda_juegos.html" dentro del formulario en la etiqueta <input> con el atributo maxlength, de la siguiente forma:

gestionPedidos/templates/busqueda_juegos.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>Busqueda de Productos</title>
</head>
<body>
    <form action="/buscar/" method="get">
        <!-- Cuadro de texto de entrada -->
        <input type="text" name="prd" maxlenght=20>
        <input type="submit" value="Buscar">
    </form>
</body>
</html>



lunes, 23 de enero de 2023

8.- Django - Panel de Administración.

  ¿Te pierdes entre tantos temas? 📚✨ Descubre todo lo que ofrece este blog en un solo vistazo [👉 Ver índice completo]

Normalmente la gestión de una página web se hace desde un panel de control. Se puede añadir nuevos usuarios, añadir registros a las tablas que hemos creado etc.  Al crear el proyecto con el comando "startproject" por defecto el panel de administración ya aparece habilitado. Fijaros que en el proyecto ya hay un archivo llamado "admin.py" que sirve para esto y también que en settings.py por defecto donde tenemos INSTALLED_APPS ya aparece la librería django.contrib.admin y otras para manejar esto.

Si ejecutamos el servidor con:

$ python manage.py runserver

y vamos a la dirección:

/localhost:8000/admin ya vemos como nos aparece el panel de administración:


Panel de Administración de Django



Para acceder necesitas un usuario y una contraseña. Esto se consigue creando un SUPERUSUARIO que maneje esto:


$ python manage.py createsuperuser


Nos preguntará por un nombre de usuario, correo electrónico y contraseña. Una vez creado ya nos podremos loguear y veremos la siguiente pantalla.


pantalla inicio administrador de Django

De momento está muy vacío pero ya nos permite crear grupos y usuarios para la administración.

Vamos a ver como incluir las tablas de la base de datos en el panel de administración. De esta forma podremos crear registros sin tener que abrir el shell de python. Lo haremos directamente de forma gráfica.

Edita el archivo admin.py de la aplicación gestionPedidos. Si lo abrimos veremos lo siguiente:

from django.contrib import admin

# Register your models here.

A modo de comentario nos dice que registremos aquí nuestros modelos. En el ejemplo Clientes, Artículos y Pedidos. Así que vamos a importarlos y luego añadir el código para que se muestren en el panel de administración:

from django.contrib import admin

# Register your models here.

# Importamos los modelos
from .models import Clientes, Articulos, Pedidos

# Los registramos en el pandel de administración.
admin.site.register(Clientes)
admin.site.register(Articulos)
admin.site.register(Pedidos)

Para ver si hemos tenido éxito ejecutamos el servidor y vamos a la url del panel de administración y ya nos aparecen las tablas. Como curiosidad DJANGO añade una "s" al final de todas las tablas con lo que si el nombre de la tabla ya terminaba en "s" esta se repetirá.

Panel de administración con las tablas

Si pinchamos en cualquiera de las tablas ya podemos hacer cualquier cosa con los registros (crear, actualizar, borrar etc). Por ahora todos los campos de la tabla aparecen en negrita lo que quiere decir que al añadir uno nuevo es obligatorio rellenarlos. Esto más tarde veremos como modificarlo para poder añadir campos opcionales.

Por defecto cada registro de la base de datos aparece como un objeto. No lo hace así la tabla Artículos que ya teníamos creada, porque habíamos definido en models.py el método __str__(). Así que si queremos ver la información de esta manera hay que añadir este método como vimos en el capitulo anterior.

Para poder establecer un campo como opcional hay que modificar el modelo (models.py) añadiendo esto al campo que queramos que sea opcional (blank=True, null=True). Por ejemplo para poner como opcional el campo correo electrónico de la tabla clientes, haríamos lo siguiente:

gestionPedidos/models.py

...
# Las tablas se crean mediante clases.

class Clientes(models.Model):
    # Dentro de la clase se crean los diferentes campos.
    nombre = models.CharField(max_length=30)
    direccion = models.CharField(max_length=50)
    # Email ya valido ya esta creado
    email = models.EmailField(blank=True, null=True)
    telefono = models.CharField(max_length=9)

class Articulos(models.Model):
...

Como siempre que se hacen modificaciones al modelo hay que realizar las correspondientes migraciones.

(miEntorno) chema@lenovo:~/Cursos/DJANGO/tiendaVirtual$ python manage.py makemigrations
Migrations for 'gestionPedidos':
  gestionPedidos/migrations/0002_alter_clientes_email.py
    - Alter field email on clientes
(miEntorno) chema@lenovo:~/Cursos/DJANGO/tiendaVirtual$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, gestionPedidos, sessions
Running migrations:
  Applying gestionPedidos.0002_alter_clientes_email... OK
y ya el campo no aparece en negrita, así que no es un campo obligatorio:

Campo opcional en una tabla



Como cambiar como aparecen o se reflejan los nombres de los campos en las tablas en el panel de administración.

Por defecto Django traslada tal cual los nombres de los campos de las tablas. Con una particularidad que los pone la primera letra del nombre del campo en Mayúsculas. También si el nombre del campo fuera compuesto (Ej nombre_cliente) quitaría el guión bajo y pondría el nombre de la primera palabra en mayúsculas.

¿Pero que ocurre si queremos ver a través del panel de administración un nombre diferente? Imaginemos que en vez de que en la tabla de clientes el campo "dirección" queremos que aparezca como "el domicilio".

Para que esto ocurra hay que ir al modelo, localizar donde se encuentra dicho campo y en el método .CharField agregar un segundo argumento, verbosename con el nombre que queremos que aparezca en el panel de administración. Para ver los cambios no es necesario reiniciar el servidor.

gestionPedidos/models.py

...
# Las tablas se crean mediante clases.

class Clientes(models.Model):
    # Dentro de la clase se crean los diferentes campos.
    nombre = models.CharField(max_length=30)
    direccion = models.CharField(max_length=50, verbosename="el domicilio")
    # Email ya valido ya esta creado
    email = models.EmailField(blank=True, null=True)
    telefono = models.CharField(max_length=9)

class Articulos(models.Model):
...
cambio nombre de campo en panel de administración.


Como ver los nombre de los campos, en vez de un objeto en el panel de administración.

Por defecto cuando entramos en una tabla el panel de administración nos muestra cada registro como un objeto. Anteriormente mejoramos esto para la tabla artículos definiendo un método __str__(self) dentro de la clase que define esta tabla. ¿Pero como hacer para que en vez de un objeto el panel de administración nos muestre los campos que queramos nosotros? Por ejemplo que en la tabla clientes se muestre el nombre, dirección y teléfono.

Para ello debemos ir al archivo admin.py y crear una clase que herede de admin.ModelAdmin lo que nos va a permitir hacer modificaciones en los modelos. Antes de registrar las tablas creamos una clase de la siguiente forma y luego la registramos junto a la tabla.

gestionPedidos/admin.py

from django.contrib import admin

# Register your models here.

# Importamos los modelos
from gestionPedidos.models import Clientes
from gestionPedidos.models import Articulos
from gestionPedidos.models import Pedidos

# Para personalizar que campos se ven en las tablas en el panel de administración
# ya que si no cada registro se ve como un objeto.
class PanelClientes(admin.ModelAdmin):
    list_display("nombre","direccion","telefono")

# Los registramos en el pandel de administración.
admin.site.register(Clientes, PanelCliente)
admin.site.register(Articulos)
admin.site.register(Pedidos)

Ya podemos reiniciar el servidor y ver si ha funcionado.

mostrar nombres de campos en vez del objeto


Añadir una casilla de búsqueda de registros.

Cuando tengamos muchos registros nos puede venir bien tener una casilla que nos permita buscar registros basándonos en algún o algunos campos.

Para ello volvemos al archivo admin.py y debajo de la clase que creamos antes "PanelClientes" y debajo de list_display añadimos la propiedad search_fields con los campos por los que se pueda realizar la búsqueda.

gestionPedidos/admin.py

...

# Para personalizar que campos se ven en las tablas en el panel de administración
# ya que si no cada registro se ve como un objeto.
class PanelClientes(admin.ModelAdmin):
    list_display("nombre","direccion","telefono")
    # para que aparezca un campo de búsqueda y permita buscar por los campos
    # nombre y teléfono
    search_fields=("nombre", "telefono")

# Los registramos en el panel de administración.
admin.site.register(Clientes, PanelCliente)
admin.site.register(Articulos)
admin.site.register(Pedidos)
...
casilla para buscar registros


¿Cómo filtrar los registros que tengamos en las tablas?

Si cogemos la tabla Articulos que tenía unos cuantos artículos (juegos) vamos a ver como podemos filtrar los registros en base por ejemplo al campo "categoría".

Para ello tenemos que ir al archivo "admin.py" de la aplicación gestionPedidos y crear una nueva clase al igual que en su día hicimos para crear una pestaña de buscador. Creamos una nueva clase que herede de admin.ModelAdmin y usamos la instrucción list_filter de la siguiente forma:


gestionPedidos/admin.py

...

# Para personalizar que campos se ven en las tablas en el panel de administración
# ya que si no cada registro se ve como un objeto.
class PanelClientes(admin.ModelAdmin):
    list_display("nombre","direccion","telefono")
    # para que aparezca un campo de búsqueda y permita buscar por los campos
    # nombre y teléfono
    search_fields=("nombre", "telefono")

# Para establecer filtros de una tabla en base a un criterio (campo)
class PanelArticulos(admin.ModelAdmin):
    list_filter=("categoria",)

# Los registramos en el panel de administración.
admin.site.register(Clientes, PanelCliente)
admin.site.register(Articulos, PanelArticulos)
admin.site.register(Pedidos)
...


panel de administración con filtros


Otra forma de registrar las tablas en en el panel de administración es usando decoradores. En el ejemplo anterior se haría de la siguiente forma:

gestionPedidos/admin.py

...

# Para personalizar que campos se ven en las tablas en el panel de administración
# ya que si no cada registro se ve como un objeto.
@admin.register(Clientes)
class PanelClientes(admin.ModelAdmin):
    list_display("nombre","direccion","telefono")
    # para que aparezca un campo de búsqueda y permita buscar por los campos
    # nombre y teléfono
    search_fields=("nombre", "telefono")

# Para establecer filtros de una tabla en base a un criterio (campo)
@admin.register(Articulos)
class PanelArticulos(admin.ModelAdmin):
    list_filter=("categoria",)

# Los registramos en el panel de administración.
admin.site.register(Pedidos)
...

¿Cómo ordenar los registros que tengamos en las tablas en base a unos determinados campos?

Para conseguir esto vamos a usar la propiedad ordering = []. 

En el contexto de Django y la configuración del administrador (admin.py), se utiliza
 para especificar el orden predeterminado en el que se mostrarán los objetos del modelo en la vista de lista del panel de administración. Por ejemplo, vamos a ver como podemos ordenar los clientes para que se muestren por orden alfabético y si existiesen clientes con el mismo nombre que se ordenen por su dirección. Es decir, en este caso, los objetos se ordenarán primero por el campo 'nombre' y luego por el campo 'direccion'.

gestionPedidos/admin.py

...

# Para personalizar que campos se ven en las tablas en el panel de administración
# ya que si no cada registro se ve como un objeto.
class PanelClientes(admin.ModelAdmin):
    list_display("nombre","direccion","telefono")
    # para que aparezca un campo de búsqueda y permita buscar por los campos
    # nombre y teléfono
    search_fields=("nombre", "telefono")
    ordering = ["nombre", "direccion"]

# Para establecer filtros de una tabla en base a un criterio (campo)
class PanelArticulos(admin.ModelAdmin):
    list_filter=("categoria",)

# Los registramos en el panel de administración.
admin.site.register(Clientes, PanelCliente)
admin.site.register(Articulos, PanelArticulos)
admin.site.register(Pedidos)
...

Puedes encontrar mas información sobre el Administrador de Django en el siguiente sitio web https://docs.djangoproject.com/en/4.2/ref/contrib/admin/

¿Cómo cambiar el idioma del panel de administración?


Tenemos que ir al archivo "settings.py" y buscar la clave "LANGUAGE_CODE" comentarla por si queremos volver a ponerla en ingles y poner el idioma que queramos. Yo la pondré en español. También cambiaré la zona horaria a la de España que en verano son dos horas menos.


tiendaVirtual/settings.py

...

# Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/

# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'es-eu'

TIME_ZONE = 'Europe/Madrid'

USE_I18N = True

USE_TZ = True
...

Agregar usuarios con diferentes perfiles al panel de administración.


A parte de nuestro usuario que tiene un perfil de  Superusuario, es decir, puede realizar cualquier tipo de acción, podemos crear nuevos usuarios que pueden tener los mismos permisos que nosotros o no. 

Si es parte del staff puede entrar en el panel de administración de Django. Pero esto no implica que pueda modificar todo, puede estar limitado a ciertas acciones.

Puede ser un usuario activo que son aquellos que pueden acceder a urls que necesitan autentificación dentro de nuestro sitio.

Para añadir un nuevo usuario entramos en "Autentificación y autorización" y "añadir usuario".

registrar un nuevo usuario


Aunque tengamos el usuario creado, necesitamos que sea staff si queremos que pueda acceder al panel de administración. Pero si entra sin que le concedamos permisos no vera nada, tendremos por tanto que definir con los permisos, que es lo que puede hacer.


Pero para no tener que ir asignando permisos de forma individual podemos crear un grupo ya con todos ellos y asignarlo a los usuarios que necesitemos, lo cual es más fácil.