Saltar a contenido

Estáticos

Django básico

Los ficheros estáticos («assets») son ficheros que no necesitan un preprocesamiento y que se utilizan «tal cual son». Nos estamos refiriendo a:

  • Imágenes.
  • Vídeos.
  • Fuentes tipográficas.
  • Hojas de estilo CSS.
  • Código JavaScript.

Estáticos en desarrollo

Siempre y cuando tengamos activada la aplicación 'django.contrib.staticfiles' (que ya viene por defecto) Django se encargará de servir los ficheros estáticos cuando así sea necesario.

Producción

Esto sólo es válido para un entorno de desarrollo, cuando pasamos a producción habrá que configurar el servidor web correspondiente para gestionar los ficheros estáticos.

Ubicación

Los ficheros estáticos «deberían» estar ubicados en la carpeta static de cada aplicación del proyecto. Cuando hacemos referencia a un estático usamos una ruta. Funciona de manera análoga a la ubicación de las plantillas.

Una aproximación inicial sería definir una hoja de estilos CSS y una carpeta de imágenes para todo el proyecto. Lo podríamos ubicar por ejemplo en la aplicación shared para que los recursos sean compartidos por todo el proyecto:

shared
└── static
    ├── css
    │   └── base.css
    └── images
        ├── logo.svg
        └── background.png
  • La ruta para acceder a base.css sería css/custom.css
  • La ruta para acceder a logo.svg sería images/logo.svg

Django se encargará de rastrear las carpetas static de las aplicaciones para encontrar los estáticos indicados. Por lo tanto los espacios de nombres también son importantes a la hora de organizar los estáticos de nuestro proyecto Django.

Una buena práctica (en el caso de estáticos específicos de una aplicación concreta) es crear una subcarpeta con el nombre de la aplicación. Supongamos por ejemplo que queremos guardar ciertas imágenes específicas de un «post»:

posts
└── static
    └── posts
        └── images
            ├── fav.png
            └── like.png
  • La ruta para acceder a fav.png sería posts/images/fav.png
  • La ruta para acceder a like.png sería posts/images/like.svg

Acceso a estáticos

En este apartado veremos cómo acceder a ficheros estáticos tanto desde una plantilla como desde una vista.

Hay dos variables importantes en settings.py que definen el comportamiento de los estáticos en un proyecto Django:

  • STATIC_URL: Define la URL que se utilizará cuando hagamos referencia a un fichero estático. Su valor por defecto es 'static/'.
  • STATICFILES_DIRS: Define directorios adicionales donde Django irá a buscar ficheros estáticos. Su valor por defecto es [].

Estáticos en plantillas

Para acceder a ficheros estáticos desde una plantilla Django debemos utilizar la etiqueta {% static %}.

Supongamos que tratamos de acceder a los ficheros estáticos definidos en el ejemplo anterior del «blog»:

shared/templates/base.html
{% load static %}<!--(1)!-->

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Blog</title>
    <link rel="stylesheet" href="{% static 'css/base.css' %}"><!--(2)!-->
  </head>

  <body>
    <header>
        <img src="{% static 'images/logo.svg' %}"/><!--(3)!-->
    </header>

    <p>
        I like this post
        <img src="{% static 'posts/images/like.png' %}"/><!--(4)!-->
    </p>
  </body>
</html>

    • Es necesario cargar la etiqueta static.
    • Debería escribirse al principio de la plantilla, eso sí, siempre después de {% extends %} que debe ser la primera..
  1. URL generada: /static/css/base.css
  2. URL generada: /static/images/logo.svg
  3. URL generada: /static/posts/images/like.png

Caché

Los navegadores web tratan de cachear1 todo el contenido que pueden para así acelerar la carga de las páginas. Es por ello que, a veces, no verás reflejados los cambios en ciertos ficheros estáticos.

Si es tu caso, puedes probar a recargar el navegador sin caché:

Ctrl + F5

Ctrl + F5

Cmd + Shift + R

Estáticos en vistas

Lo más habitual es utilizar ficheros estáticos directamente en plantillas, pero puede darse el caso donde necesitemos acceso a los estáticos en las vistas. Se diferencian dos aproximaciones:

1⃣ Acceso a la ruta en la URL.
2⃣ Acceso a la ruta en el sistema de ficheros.

Para obtener la ruta en la URL de un determinado estático podemos utilizar la misma etiqueta static pero desde django.templatetags.static.

Veamos un ejemplo:

posts/views.py
from django.templatetags.static import static as static_url#(1)!


def my_view(request):
    # ...
    like_url_path = static_url('posts/images/like.png')#(2)!

  1. Es necesario importar la función static().
  2. En este caso se devolverá la URL /static/posts/images/like.png

Para obtener la ruta en el sistema de ficheros de un determinado estático podemos utilizar la función path desde django.contrib.staticfiles.

Veamos un ejemplo:

posts/views.py
from django.contrib.staticfiles.storage import path as static_path #(1)!


def my_view(request):
    # ...
    like_file_path = static_path('posts/images/like.png')#(2)!

  1. Es necesario importar la función path().
  2. En este caso se devolverá la ruta /home/guido/dev/blog/posts/static/posts/images/like.png

Bootstrap

Django avanzado

Bootstrap ofrece un conjunto de herramientas que facilitan el desarrollo de interfaces «frontend» para aplicaciones web.

En el momento de la escritura de este documento, Bootstrap figura entre los 30 proyectos con más ⭐ de GitHub2.

Instalación

Hay varias maneras de instalar Bootstrap y de integrarlo en un proyecto Django. En esta sección veremos cómo implantarlo usando npm y acceso a ficheros estáticos.

Lo primero será instalar los paquetes JavaScript correspondientes:

$ npm install bootstrap bootstrap-icons #(1)!

  1. Desde el raíz de nuestro proyecto Django.

El comando anterior creará una carpeta node_modules con multitud de ficheros y subcarpetas, correspondientes a los paquetes Node instalados y a todas sus dependencias.

Control de versiones

Recuerda excluir la carpeta node_modules del control de versiones añadiéndola al fichero .gitignore de tu proyecto.

También crearán dos ficheros de seguimiento de paquetes (1):

  • package.json que almacena las versiones (mínimas) de los paquetes instalados.
  • package-lock.json que almacena las dependencias de los paquetes instalados.
  1. En el caso de utilizar uv (gestión de paquetería Python), podríamos decir que:

    • package.json \(\approx\) pyproject.toml
    • package-lock.json \(\approx\) uv.lock

Configuración

Para poder acceder a los archivos creados en node_modules desde las plantillas Django, necesitamos especificar en la configuración del proyecto que dicha carpeta contiene estáticos.

Para ello añadimos la siguiente línea al fichero settings.py:

main/settings.py
STATICFILES_DIRS = [BASE_DIR / 'node_modules']#(1)!

    • Esta variable permite añadir rutas extras donde Django irá a buscar ficheros estáticos.
    • Puedes añadirla donde quieras, pero un buen lugar podría ser junto a la variable STATIC_URL.

Plantillas

Dado que Bootstrap utiliza ficheros .css y .js necesitamos cargarlos correctamente desde nuestras plantillas.

Suponiendo que disponemos de una plantilla base tendríamos que añadir lo siguiente:

shared/templates/base.html
{% load static %}<!--(1)!-->

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title></title>
    <link rel="stylesheet" href="{% static 'bootstrap/dist/css/bootstrap.min.css' %}"><!--(2)!-->
    <link rel="stylesheet" href="{% static 'bootstrap-icons/font/bootstrap-icons.min.css' %}"><!--(3)!-->
    <link rel="stylesheet" href="{% static 'css/custom.css' %}"><!--(4)!-->
  </head>

  <body>
    <div class="container"><!--(5)!-->
      {% block content %}
      {% endblock %}
    </div>
  </body>

  <script type="text/javascript" src="{% static 'bootstrap/dist/js/bootstrap.bundle.min.js' %}"></script><!--(6)!-->
</html>

  1. Necesitamos cargar las utilidades para estáticos.
  2. Cargamos los estilos de Bootstrap.
  3. Cargamos los iconos de Bootstrap.
  4. Cargamos estilos propios (opcional).
  5. La clase container es el bloque fundamental de Bootstrap.
  6. Cargamos los scripts de Bootstrap.

A partir de aquí ya podremos usar todos los recursos que nos proporciona Bootstrap para diseñar una interfaz de usuario moderna, responsiva y funcional.

Modales

La implementación de ventanas modales suele ser una estrategia interesante para confirmar acciones y mostrar mensajes informativos.

A continuación se presenta una propuesta de modal con Bootstrap mediante una etiqueta personalizada poniendo como ejemplo de uso su aplicación al borrar un «post» del «blog»:

shared/templates/shared_extras.py
import uuid

from django import template
from django.urls import reverse

register = template.Library()


@register.inclusion_tag('includes/modal.html')
def modal(
    btn_text,#(1)!
    url,#(2)!
    *url_args,#(3)!
    title='Attention',#(4)!
    body='Are you sure to continue?',#(5)!
    action='Continue',#(6)!
    btn_classes='btn btn-primary',#(7)!
    btn_icon='',#(8)!
):
    url = reverse(url, args=url_args)
    modal_id = uuid.uuid4()
    return dict(
        modal_id=modal_id,
        title=title,
        body=body,
        action=action,
        url=url,
        btn_text=btn_text,
        btn_classes=btn_classes,
        btn_icon=btn_icon,
    )

  1. Texto del botón que lanza el modal.
  2. Nombre de URL a ejecutar si se «confirma» el modal.
  3. Argumentos para conformar la URL.
  4. Título de la ventana modal.
  5. Texto para el cuerpo de la ventana modal.
  6. Texto del botón de confirmación en la ventana modal.
  7. Clases CSS a aplicar sobre el botón que lanza el modal.
  8. Icono a mostrar en el botón que lanza el modal.
shared/templates/includes/modal.html
<!-- ↓ Button to launch modal -->
<button type="button" class="{{ btn_classes }}" data-bs-toggle="modal" data-bs-target="#{{ modal_id }}">
  <i class="{{ btn_icon }}"></i>
  {{ btn_text }}
</button>

<!-- ↓ Modal window -->
<div class="modal fade" id="{{ modal_id }}" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h1 class="modal-title fs-5" id="exampleModalLabel">{{ title }}</h1>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        {{ body }}
      </div>
      <div class="modal-footer">
        <a href="{{ url }}" class="btn btn-primary">{{ action }}</a>
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>
posts/templates/posts/post/delete.html
{% modal
    "Delete post"
    "posts:delete-post" post.slug
    title="Confirm"
    body="Do you want to delete this post?"
    btn_classes="btn btn-danger btn-sm"
    btn_icon="bi bi-journal-x"
%}

  1. Guardar copias de datos de forma temporal en una ubicación de almacenamiento más rápida. 

  2. Información via git-stars.org