viernes, 24 de noviembre de 2023

17.3 Base de datos PostgreSQL. Busquedas, stemming y ranking de resultados.

En las aplicaciones web es una práctica común la realización de búsquedas en la base de datos a partir de entradas proporcionadas por el usuario. A continuación vamos a ver como introducir estas capacidades de búsqueda en la aplicación Blog.

El ORM de Django nos permite realizar búsquedas sencillas en la base de datos usando el filtro "contains" que no distingue por ejemplo entre mayúsculas o minúsculas. Por ejemplo podemos utilizarlo para ver los post que contienen la palabra "funciona" dentro del cuerpo del post.

from blog.models import Post
Post.objects.filter(contenido__contains='funciona')
Sin embargo, si queremos realizar búsquedas más complejas necesitamos un motor de búsqueda más complejo. Django proporciona una poderosa funcionalidad de búsqueda pero basada en la base de datos PostgreSQL. El módulo django.contrib.postgres proporciona funcionalidades ofrecidas por PostgreSQL que no son compartidas por las otras bases de datos que admite Django. Puedes obtener información sobre la búsqueda de texto completo de PostgreSQL en https://www.postgresql.org/docs/14/textsearch.html.


Instalando PostgreSQL.


Hasta el momento en nuestro proyecto estamos usando la base de datos SQLlite. Sin embargo, PostgreSQL es mucho mejor a la hora de realizar búsquedas de texto complejas en la base de datos. Vamos a migrar nuestra base de datos de SQlite a PostgreSQL para beneficiarnos de sus mejores características.

SQlite está muy bien para entornos de desarrollo, sin embargo para un entorno de producción, necesitarás una base de datos más poderosa como PostgreSQL, MariaDB, MySQL etc.

Ya habíamos comentado anteriormente como instalar PostgreSQL, puedes encontrar en enlace aquí.
Vamos a resumirlo. Como yo trabajo con una distribución Debian, los comandos de instalación son los siguiente:
 
$ sudo apt-get install postgresql postgresql-contrib

# Entramos en la consola de postgresql
$ sudo su - postgres
postgres@lenovo:~$ psql
psql (14.5 (Ubuntu 14.5-0ubuntu0.22.04.1))
Type "help" for help.

# Creamos un usuario y su contraseña. 
# En el ejemplo usuario = "tu_usuario" y contraseña="xxxxxxxx"
postgres=# CREATE USER tu_usuario WITH PASSWORD 'xxxxxxxx';
Salida: CREATE ROLE # Creamos una base de datos de ejemplo. Se pude poner el nombre # que se quiera. # En el ejemplo aplicacion_db postgres=# CREATE DATABASE aplicacion_db OWNER tu_usuario ENCODING 'UTF8';
Salida: CREATE DATABASE # Para salir del terminal de postgresql y volver al terminal. postgres-# \q postgres@lenovo:~$ exit Salida: logout

También necesitamos instalar el adaptador psycop2 PostgreSQL para Python. Ejecuta el siguiente comando en el shell para instalarlo:

pip install psycopg2-binary


Realizando una Copia de los datos existentes.


Antes de cambiar de base de datos en nuestra aplicación, necesitamos realizar una copia de los datos existentes en la base de datos SQlite. Exportaremos esos datos, cambiaremos la base de datos a PostgreSQL e importaremos los datos otra vez a la nueva base.

Django viene con una forma muy sencilla de cargar y grabar datos de una base a un archivo. Se puede hacer en formado JSON, XML o YAML. Vamos a guardar todos los registros de nuestra base de datos en un archivo. Ejecuta el siguiente comando desde el shell:

python manage.py dumpdata --indent=2 --output=mysite_data.json

La salida del comando será parecido a esto:

[................................................................................................................]

Todos los datos existentes han sido exportados en formato JSON a un nuevo archivo llamado "mysite_data.json". Si tienes un error mientras ejecutas el comando, usa el siguiente comando para activar el modo UTF-8 de Python:

python -Xutf8 manage.py dumpdata --indent=2 --output=mysite_data.json

Ahora cambiaremos la base de datos en el proyecto de Django y volveremos a importar los datos a la nueva base.


Reemplazando la base de datos en el proyecto.


Edita el archivo settings.py del proyecto y modifica el apartado DATABASES de la siguiente forma:

PracticaDjango/PracticaDjango/settings.py

  
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'aplicacion_db', # nombre de la base de datos.
        'USER': 'tu_usuario', # utiliza el usuario que hayas escogido.
        'PASSWORD': 'xxxxxxxx', # Cambia las xxxxxxxx por la clave que hayas puesto.
        'HOST': '127.0.0.1',
        'DATABASE_PORT': '5432',
    }
}


Reemplaza las 'xxxxxxxx' con la contraseña que usaste cuando creaste el usuario de la base de datos PostgreSQL. La nueva base de datos está vacia.

Ejecuta el siguiente comando para realizar las migraciones de la nueva base de datos PostgreSQL.

python manage.py migrate

En terminal verás un montón de salidas sobre las migraciones que se están aplicando.


Cargando los datos en la nueva base de datos.


Ejecuta el siguiente comando para cargar de nuevo los datos en la base de datos.

python manage.py loaddata mysite_data.json

Deberías ver la siguiente salida en el terminal:

Installed 104 object(s) from 1 fixture(s)

El número de objetos puede diferir en tu proyecto dependiendo del modelo.

SIN EMBARGO PUEDES ENCONTRARTE CON ESTE PROBLEMA (si no te ocurre sigue adelante, no hace falta que leas esto)

django.db.utils.IntegrityError: Problem installing fixture '/home/chema/Projects/PYTHON/DJANGO/PracticaDjango/mysite_data.json': Could not load contenttypes.ContentType(pk=1): llave duplicada viola restricción de unicidad «django_content_type_app_label_model_76bd3d3b_uniq»
DETAIL:  Ya existe la llave (app_label, model)=(admin, logentry).

ContentType es una especie de registro de los modelos de una aplicación, pensado como una interfaz para poder acceder a información sobre un modelo sin tener que saber mucho sobre los detalles del mIismo.
El marco de autenticación de Django utiliza ContentType para asignar permisos a modelos. La aplicación de administración de Django rastrea los cambios en sus objetos a través del modelo LogEntry que a su vez usa ContentTypes (ese es el modelo que está causando el error).

Si se consulta ContentType para un modelo que aún no conoces, se crea un nuevo registro con ese modelo: eso significa que la composición de la tabla ContentType puede diferir de un entorno a otro; depende del orden en el que se solicitan los modelos. 

Viendo el volcado del error para ContentType, y suponiendo que para cualquier modelo que use ContentType, el volcado también contiene las referencias a esa nueva tabla ContentType, deberíamos poder eliminar todas las entradas que se encuentran actualmente en la tabla ContentType local:

Para solucionarlo entraremos en el shell de Django:

python manage.py shell

y tecleamos: 

from django.contrib.contenttypes.models import ContentType
ContentType.objects.all().delete()

Con esto deberíamos poder cargar sin problemas los datos de nuevo en la base nueva. Ejecuta de nuevo:

python manage.py loaddata mysite_data.json

Puede realizar, si quieres una copia de seguridad del contenido de tu ContentType, si quieres estar seguro de no perder nada usando:

python manage.py dumpdata contenttypes.ContentType

Una vez cambiado la base de datos y cargado los datos de la base antigua en la nueva vamos a ver que todo funcione. Ejecutamos el servidor de desarrollo y comprobamos que todo, sobre todo los post, este ahí y todo funcione correctamente.


Realizando búsquedas sencillas.


Edita el archivo settings.py del proyecto y añade django.contrib.postgres al grupo de aplicaciones instaladas:

PracticaDjango/PracticaDjango/settings.py

INSTALLED_APPS = [
    # Nuestras aplicaciones
    'Proyecto_web_app.apps.ProyectoWebAppConfig',
    'Servicios.apps.ServiciosConfig',
    'Blog.apps.BlogConfig',
    'Contacto.apps.ContactoConfig',
    # Aplicaciones de terceros
    'django_bootstrap5',
    # Mapa del Sitio
    'django.contrib.sites', # add sites to installed_apps
    'django.contrib.sitemaps',  # add Django sitemaps to installed app
    # PostgreSQL
    'django.contrib.postgres',
    # Aplicaciones por defecto
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
Abre el shell de Django ejecutando el siguiente comando:

python manage.py shell

Ejecuta los siguientes comandos en el shell:

>>> from Blog.models import Post
>>> Post.objects.filter(titulo__search='ver')
<QuerySet [<Post: quinto post para ver la paginacion>, <Post: Cuarto post para ver como funciona el slug>, <Post: Un tercer post para ver como sigue>]>

Esta búsqueda usa PostgreSQL para crear un vector de búsqueda para el campo título, usando el termino "ver". Lo que se obtiene son todos los post que contienen la palabra "ver" dentro del título del post.

Buscando a través de varios campos.

Puede que quieras realizar una búsqueda a través de varios campos del modelo. En ese caso necesitaras crear un objeto SearchVector. Vamos a crear un vector para buscar en los campos 'titulo' y 'contenido' del modelo Post.

Ejecuta el siguiente código en el shell de Python:

>>> from django.contrib.postgres.search import SearchVector

>>> from Blog.models import Post

>>> Post.objects.annotate(search=SearchVector('titulo','contenido'),).filter(search='sitio')

<QuerySet [<Post: Este es el primer post de prueba>]>

>>> 

Usando 'annotate' y definiendo un SearchVector con ambos campos, puedes realizar una búsqueda contra el campo 'titulo' y 'contenido'.


Construyendo una vista de busqueda.

Vamos a construir una vista personalizada que permita a los usuario realizar búsquedas en los post que estén publicados. Lo primero que necesitaremos es crear un formulario de búsqueda. Edita el archivo forms.py de la aplicación Blog y añade el siguiente formulario:

PracticaDjango/Blog/forms.py

from django import forms
from .models import Comentario

class ComentarioForm(forms.ModelForm):
    class Meta:
        model = Comentario
        
class BusquedaForm(forms.Form):
    consulta = forms.CharField()

Usaremos el campo "consulta" para permitir a los usuarios introducir términos de búsqueda. Edita el archivo views.py de la aplicación Blog y añade el siguiente código:

/PracticaDjango/Blog/views.py

#...
# Importamos el formulario para los comentarios de cada post.
from .forms import ComentarioForm, BusquedaForm
from django.views.decorators.http import require_POST

# Para realizar busquedas en los campos de los post.
from django.contrib.postgres.search import SearchVector

#...
def busqueda_post(request):
    formulario = BusquedaForm()
    consulta = None
    resultados = []

    if "consulta" in request.GET:
        formulario = BusquedaForm(request.GET)
        if formulario.is_valid():
            consulta = formulario.cleaned_data["consulta"]
            resultados = Post.objects.annotate(
                search=SearchVector("titulo", "contenido"),
            ).filter(search=consulta)

    return render(
        request,
        "Blog/busqueda.html",
        {"formulario": formulario, "consulta": consulta, "resultados": resultados},
    )
Lo que hemos hecho es, primero, crear una instancia del formulario BusquedaForm. Para comprobar si el formulario ha sido enviado, buscamos el parámetro "consulta" en el diccionario request.GET. Enviamos el formulario usando el método GET en vez del método POST, asi que la URL resultante incluye le parámetro "consulta".

Por ejemplo http://127.0.0.1:8000/blog/busqueda/?consulta=primero

Cuando se envía el formulario, lo instanciamos con los datos suministrados por le método GET, y verificamos que los datos contenidos son válidos. Si los datos pasan la validación, buscamos en los post la entrada introducida a través de los campos titulo y contenido utilizando un vector SearchVector.

Con esto la vista para la búsqueda está lista. Ahora tenemos que crear una plantilla para mostrar el formulario y los resultados de la búsqueda cuando los usuarios realicen una consulta.

Crea un nuevo archivo dentro del directorio templates/Blog/ de la aplicación Blog y llámalo busqueda.html. Luego añade el siguiente código:

PracticaDjango/Blog/templates/Blog/busqueda.html

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

<!-- Cargamos la etiqueta personalizada -->
{% load blog_tags %}

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

{% block content %}

<div class="container-fluid bg-white" style="margin-bottom: 50px;">

{% if consulta %}
    <h1>Post que contienen la consulta "{{ consulta|truncatewords:15 }}"</h1>
    <h3>
    {% with resultados.count as total_results %}
        Encontrados {{ total_results }} resultado{{total_results|pluralize}}
    {% endwith %}
    </h3>
    {% for post in resultados %}
    <h4>
        <a href="{{ post.get_absolute_url }}">
            {{ post.titulo }}
        </a>
    </h4>
    {{ post.contenido|markdown|truncatewords_html:12 }}
    {% empty %}
    <p>No hay resultados para la consulta.</p>
    {% endfor %}

    <p><a href="{% url 'Blog:busqueda_post' %}">Realizar nueva búsqueda</a></p>

{% else %}
    <h1>Busqueda en los Posts</h1>
    <form method="get">
        {{ formulario.as_p }}
        <input type="submit" value="Busqueda">
    </form>
{% endif %}

</div>

{% endblock %}
 Al igual que en la vista, aquí distinguimos también si el formulario ha sido enviado comprobando la presencia del parámetro "consulta". Antes de que la consulta sea enviada, mostramos el formulario y el botón para enviarlo. Cuando el formulario se envía, mostramos los resultados de la consulta, el número total de resultados y la lista de post que coinciden con el resultado de la búsqueda.

Finalmente, edita el archivo urls.py de la aplicación blog y añade el siguiente parámetro de URL:

PracticaDjango/Blog/urls.py

from django.urls import path
from . import views

app_name = 'Blog'

urlpatterns = [
# post views
# path('', views.lista_post, name='lista_post'),
path('', views.lista_post.as_view(), name='lista_post'),
path('<int:year>/<int:month>/<int:day>/<slug:post>/', views.detalle_post, name='detalle_post'),
path('categoria/<int:categoria_id>/', views.categoria, name='categoria'),
path('<int:post_id>/comentario/', views.post_comentario, name='post_comentario'),
path('busqueda/', views.busqueda_post, name='busqueda_post'),
]

Ahora abre la dirección http://127.0.0.1:8000/blog/busqueda/ en tu navegador. Deberías ver el siguiente formulario:

formulario de busqueda


Introduce una consulta y pulsa el botón de búsqueda. Verás el resultado de la consulta de esta forma:

resultados de la busqueda


Con estos pasos que hemos hecho, hemos creado un motor de búsqueda básica para los posts.


Stemming y ordenación de los resultados por su relevancia.

Stemming, en el ámbito de la búsqueda de datos y procesamiento de texto en informática, se refiere a un proceso utilizado en el procesamiento del lenguaje natural (NLP) que busca reducir las palabras a su forma base o raíz, conocida como el "stem" en inglés. El objetivo principal es simplificar las palabras para que diferentes formas de la misma palabra se consideren como iguales, lo que facilita la búsqueda y el análisis de texto.

Por ejemplo, palabras como "correr", "corriendo" y "corrió" se reducirían al mismo stem, que en este caso sería "corr". Al hacer esto, se puede agrupar el contenido que contiene estas palabras en sus formas derivadas, facilitando el análisis y la recuperación de información.

El stemming no siempre produce palabras válidas o legibles, ya que se centra en la reducción de las palabras a su forma base sin considerar su significado contextual. Es una técnica útil en la recuperación de información y el procesamiento de texto, pero puede presentar limitaciones en ciertos contextos lingüísticos debido a las variaciones y excepciones en la estructura de las palabras en diferentes idiomas.

Django proporciona una clase SearchQuery para traducir términos en un objeto de consulta de búsquedas. Por defecto, los términos se pasan a través de algoritmos de stemming, lo que ayuda a obtener mejores coincidencias.

El motor de búsqueda de PostgreSQL también elimina palabras vacías para realizar búsquedas, como "a", "the", "on"  y "of" en inglés. Las "stop words" son un conjunto de palabras de uso común en un idioma. Se eliminarán al crear una consulta de búsqueda porque aparecen con demasiada frecuencia como para ser relevantes en las búsquedas. Puedes encontrar la lista de "stop words" para el idioma inglés en esta dirección https://github.com/postgres/postgres/blob/master/src/backend/snowball/stopwords/english.stop. Sin embargo lo anterior se puede realizar en cualquier lenguaje. Por ejemplo también podemos encontrar una lista de "stop words" en español en https://github.com/postgres/
postgres/blob/master/src/backend/snowball/stopwords/spanish.stop.

También queremos ordenar esos resultados por su relevancia. PostgreSQL proporciona una función de clasificación que ordena los resultados en función de la frecuencia con la que aparecen los términos de consulta y de lo cerca que están entre si.

Edita el archivo views.py de la aplicación Blog y añade el siguiente código:

PracticaDjango/Blog/views.py

#...
# Para realizar busquedas en los campos de los post.
from django.contrib.postgres.search import SearchVector, SearchQuery, SearchRank

#...
def busqueda_post(request):
    formulario = BusquedaForm()
    consulta = None
    resultados = []

    if "consulta" in request.GET:
        formulario = BusquedaForm(request.GET)
        if formulario.is_valid():
            consulta = formulario.cleaned_data["consulta"]
            vector_busqueda = SearchVector("titulo","contenido", config='spanish')
            consulta_busqueda = SearchQuery(consulta, config='spanish')
            resultados = Post.objects.annotate(
                search = vector_busqueda,
                rank = SearchRank(vector_busqueda, consulta_busqueda)
            ).filter(search=consulta_busqueda).order_by('-rank')

    return render(
        request,
        "Blog/busqueda.html",
        {"formulario": formulario, "consulta": consulta, "resultados": resultados},
    )

En el código superior, hemos creado un objeto SearchQuery, filtrando los resultados en base a él, y usando SearchRank para ordenar los resultados por su relevancia. Como verás hemos usado también el vector_busqueda y consulta_busqueda con el atributo config, lo que nos permite establecer diferentes configuraciones. Esto nos va a permitir trabajar con diferentes idiomas. Por ejemplo, en el código usamos el stemming y removemos las "stop words" usando el idioma Español. 


Uso de ponderaciones en las consultas.

Podemos potenciar vectores específicos para que se les atribuya más peso cuando se ordenen los resultados por su relevancia. Por ejemplo, podemos utilizar esto para dar más relevancia a las publicaciones que coinciden por título en lugar de por contenido.

Edita el archivo views.py de la aplicación del blog y modifica la vista busqueda_post de la siguiente manera. El nuevo código está resaltado en negrita:

PracticaDjango/Blog/views.py

#...
def busqueda_post(request):
    formulario = BusquedaForm()
    consulta = None
    resultados = []

    if "consulta" in request.GET:
        formulario = BusquedaForm(request.GET)
        if formulario.is_valid():
            consulta = formulario.cleaned_data["consulta"]
            vector_busqueda = SearchVector(
                "titulo", weight="A", config="spanish"
            ) + SearchVector("contenido", weight="B", config="spanish")
            consulta_busqueda = SearchQuery(consulta, config="spanish")
            resultados = (
                Post.objects.annotate(
                    search=vector_busqueda,
                    rank=SearchRank(vector_busqueda, consulta_busqueda),
                )
                .filter(rank__gte=0.3)
                .order_by("-rank")
            )

    return render(
        request,
        "Blog/busqueda.html",
        {"formulario": formulario, "consulta": consulta, "resultados": resultados},
    )


En el código anterior, aplicamos diferentes pesos a los vectores de búsqueda construidos utilizando los campos de título y cuerpo. Los pesos predeterminados son D, C, B y A, y se refieren a los números 0.1, 0.2, 0.4 y 1.0, respectivamente. Aplicamos un peso de 1.0 al vector de búsqueda del título (A) y un peso de 0.4 al vector del contenido (B). Las coincidencias en el título prevalecerán sobre las coincidencias en el contenido del cuerpo. Filtramos los resultados para mostrar solo aquellos con una clasificación superior a 0.3.

Puedes encontrar el contenido de este capitulo en este enlace de Github.


jueves, 16 de noviembre de 2023

17.2- Añadir un mapa del sitio para nuestro proyecto.

Un mapa del sitio es un documento XML que contiene todas las URL importantes de nuestro sitio web. Los buscadores lo utilizan como herramientas de navegación para indexar sus sitios web, rastrean e indexan de manera mas rápida y eficiente usando este sistema. 

Es por esto por lo que es conveniente tener un "sitemap" para nuestro sitio, ya que así se automatiza el envío de nuestras páginas web a los servidores de los buscadores. Esto es una de las técnicas que se utilizan para mejorar nuestro posicionamiento en los motores de búsqueda.

Por suerte, Django viene con un framework para crear "sitemap". Para utilizarlo tenemos que registrar dentro de las aplicaciones instaladas, tanto "sites" como "sitemap" de la siguiente forma. Edita el archivo "settings.py" de la aplicación PracticaDjango y añade el código sombreado:


PracticaDjango/PracticaDjango/settings.py

    # Aplicaciones de terceros
    'django_bootstrap5',
    # Mapa del Sitio
    'django.contrib.sites', # add sites to installed_apps
    'django.contrib.sitemaps',  # add Django sitemaps to installed app
    # Aplicaciones por defecto
    'django.contrib.admin',
    # ...

Ejecuta las migraciones para añadir el "sitemaps" a la base de datos:

python manage.py makemigrations

python manage.py migrate

Lo siguiente es añadir el nombre de nuestro dominio a los Sitios.

  • En el terminal ejecutamos: python manage.py runserver
  • Ve al administrador de Django y entra usando tus credenciales.
  • Ve a la sección Sitios y haz clic en añadir sitio.
Sección Sitios en el administrador de Django
Como nombre de dominio que aparece por defecto está example.com. Pincha en él, para cambiarlo. Aquí puedes poner el dominio o host que será usado por el framework sites y sus aplicaciones. En un entorno de producción deberás usar el nombre de dominio de tu página web, pero mientras estemos desarrollando la aplicación cámbialo a 127.0.0.1:8000 que es nuestro local host.

Cambiar el sitio en el panel de admon de Django


Puedes encontrar más información sobre el framework sites en https://docs.djangoproject.com/en/4.2/ref/contrib/sites/.

El siguiente paso es crear un nuevo archivo llamado sitemaps.py. Lo crearemos dentro del directorio de la aplicación práctica Django. En el, crearemos un sitemap dinámico para la aplicación Blog y un sitemap estático para el resto de las vistas de nuestra página web. (exceptuando la vista para la tienda que aún no la tenemos creada)

PracticaDjango/PracticaDjango/sitemaps.py

from django.contrib.sitemaps import Sitemap
from django.urls import reverse
from Blog.models import Post

class BlogSitemap(Sitemap):
    changefreq = "monthly"
    priority = 0.8
    protocol = 'http'

    def items(self):
        return Post.objects.all()
    
    def lastmod(self, obj):
        return obj.updated

    # def location(self, obj):
    #     return '/blog/%s' % (obj.slug)
    
class StaticSitemap(Sitemap):
    changefreq = "yearly"
    priority = 0.8
    protocol = 'http'
 
    def items(self):
        return ["Proyecto_web_app:home",
                "Servicios:servicios",
                "Contacto:contacto",
                ] #devuelve las páginas estáticas; 
 
    def location(self, item):
        return reverse(item) #devuelve las URL de las páginas estáticas; 

Hemos creado un sitio personalizado que hereda de la clase Sitemap del modulo sitemaps. El atributo "changefreq"  indica la frecuencia con la que se cambian las páginas. El atributo "priority" indica la relevancia dentro de la página web (el valor máximo es 1). El protocólo es "http" o "https", utiliza "https" en entornos de producción.

El método item() devuelve todos los objetos del modelo que queramos que se listen en el mapa del sitio. Por ejemplo la clase BlogSiteMap devuelve todos los post del blog. Por defecto Django llama al método get_absolute_url() que recuerda ya habíamos definido en el modelo de Post. Si quisiéramos especificar una URL para cada objeto tendríamos que hacer uso del método "locate". En este caso no porque tenemos definido previamente un get_absolute_url().

También hemos creado una clase para las páginas estáticas de nuestro proyecto, los nombres de las URL las hemos obtenido utilizando el método reverse para obtener la URL absoluta de estas páginas.

Hemos añadido las páginas estáticas: 'home', 'servicios' y 'contacto' porque son las que tenemos ya creadas. (la página para la tienda la trataremos en post posteriores). Para ello usamos los nombres a las que están conectados. Por ejemplo la vista para servicios en su archivo urls.py esta conectada al nombre "Servicios:servicios"

Tu decides que páginas estáticas quieres que aparezcan en tu "directorio", encuentra y selecciona el nombre de la URL de su aplicación, después inclúyela en el método item() dentro de la clase StaticSiteMap.

De donde sale Servicios:servicios para incluirlo en el item() de las páginas estáticas.

from django.urls import path

from . import views

app_name = 'Servicios'

urlpatterns = [
    path('', views.servicios, name='servicios'),
]

Después de que creamos el sitemap, tenemos que crear una URL para él. Edita el archivo urls.py de la aplicación PracticaDjango y añade el sitemap de la siguiente forma. Las nuevas líneas están resaltadas.



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

from django.conf import settings
from django.conf.urls.static import static

from django.contrib.sitemaps.views import sitemap
from .sitemaps import BlogSitemap, StaticSitemap

sitemaps = {
    'static':StaticSitemap, #add StaticSitemap to the dictionary
    'blog':BlogSitemap #add DynamicSitemap to the dictionary
}

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('Proyecto_web_app.urls')),
    path('servicios/', include('Servicios.urls')),
    path('blog/', include('Blog.urls')),
    path('contacto/', include('Contacto.urls')),
    path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
]
urlpatterns+=static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)


En el código superior hemos añadido las importaciones necesarias, y definido un diccionario llamado sitemaps. Se pueden usar varios directorios para el sitio. Hemos definido un patrón URL que coincide con el patrón sitemap.xls y utiliza la vista del mapa del sitio proporcionada por Django. El diccionario sitemaps se pasa a la vista del mapa del sitio. 

Por fin el mapa del sitio proporcionado por Django lo podemos encontrar en http://127.0.0.1/sitemap.xls. Ejecuta el servidor de desarrollo con "python manage.py runserver" y ve a esa dirección. Deberías ver todos los post del blog y las páginas estáticas que hayas añadido.


http://127.0.0.1/sitemap.xls

<urlset>
<url>
<loc>http://127.0.0.1:8000/</loc>
<changefreq>yearly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>http://127.0.0.1:8000/servicios/</loc>
<changefreq>yearly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>http://127.0.0.1:8000/contacto/</loc>
<changefreq>yearly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>
http://127.0.0.1:8000/blog/2023/11/13/post-usando-markdown/
</loc>
<lastmod>2023-11-13</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>
http://127.0.0.1:8000/blog/2023/10/27/post-septimo/
</loc>
<lastmod>2023-10-27</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>http://127.0.0.1:8000/blog/2023/10/27/post-sexto/</loc>
<lastmod>2023-10-27</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>
http://127.0.0.1:8000/blog/2023/10/27/quinto-post-para-ver-la-paginacion/
</loc>
<lastmod>2023-10-27</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>
http://127.0.0.1:8000/blog/2023/10/15/cuarto-post-para-ver-como-funciona-el-slug/
</loc>
<lastmod>2023-10-24</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>
http://127.0.0.1:8000/blog/2023/10/15/este-primer-post-prueba/
</loc>
<lastmod>2023-10-15</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>
http://127.0.0.1:8000/blog/2023/10/15/tercer-post-para-ver-como-sigue/
</loc>
<lastmod>2023-10-15</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>
http://127.0.0.1:8000/blog/2023/10/15/segundo-post-para-probar-mas/
</loc>
<lastmod>2023-10-15</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
</urlset>

Código de este capitulo en GitHub.