Sobre este documento

Este guión de prácticas ha sido elaborado siguiendo la filosofía de los tutoriales de página de Django. Este guión ha sido adaptado para las clases de SAT y SARO de los Grados que se imparten en la Escuela de Ingeniería de Telecomunicación de la Universidad Rey Juan Carlos.

Para este guión, partimos del estado del guión #3, que se puede encontrar en el siguiente repositorio: https://gitlab.etsit.urjc.es/cursosweb/x-serv-15.6-cms_put-resolved del GitLab de la ETSIT. Nota: si partes del repositorio, nota que el superusuario será "grex" y la contraseña "pakito"; esto es así, porque los usuarios se guardan en la base de datos (que viene incluida en el repo).

Gestión de Usuarios y Permisos

Hasta ahora, nuestra aplicación no tenía en cuenta quién hacía qué, cualquiera podía subir contenidos y comentarios, así como cambiarlos. Vamos a ver cómo introducir autenticación y autorización en nuestra aplicación de Django. Para ello, vamos a añadir una primera vista a nuestra aplicación, tal que así:

cms/views.py
from django.http import HttpResponse
[...]

def loggedIn(request):
    if request.user.is_authenticated:
        logged = "Logged in as " + request.user.username
    else:
        logged = "Not logged in. <a href='/admin/'>Login via admin</a>"
    return HttpResponse(logged)
   

En esta vista, podemos ver que existe un objeto request.user que tiene una propiedad is_authenticated. request.user viene determinado a partir de la sesión; si hay una sesión abierta, habrá un usuario (que estará autenticado). Si no hay sesión, entonces no habrá un usuario autenticado.

Para que la vista funcione, hemos de añadir a cms/urls.py la siguiente regla:

cms/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('', views.index),
    path('<str:llave>', views.get_content),
    path('loggedIn', views.loggedIn),
]

Si ejecutamos el servidor y pedimos loggedIn nos dará un error 404. Esto es porque las URLs se comprueban siguiendo el orden de definición de urls.py. Con la configuración actual, si pedimos loggedIn se cumple al regla '<str:llave>' al ser loggedIn una cadena de caracteres y se ejecutará su vista. Para que todo funcione bien, cms/urls.py debería ser así:

cms/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('', views.index),
    path('loggedIn', views.loggedIn),
    path('<str:llave>', views.get_content),
]

Nota que en caso de no estar autenticado, la página de respuesta contiene un enlace. Si pinchas sobre ese enlace, te llevará a la página de inicio del portal de gestión de tu proyecto Django, lo que generalmente llamamos admin (o la interfaz de administración). Si recuerdas bien, creamos hace un par de prácticas un superusuario. Introduce sus credenciales para entrar en la interfaz de administración. Y a continuación, vuelve a visitar la página loggedIn. Podrás comprobar que estás autenticado y que te saluda con request.user, que es tu nombre de usuario.

Ya que hemos visto como hacer login, véamos como salirnos de nuestra sesión (logout). Para ello, añadamos la siguiente vista:

cms/views.py
from django.contrib.auth import logout
from django.shortcuts import redirect
[...]

def logout_view(request):
    logout(request)
    return redirect("/cms/")   

Como se puede observar, la vista es muy sencilla. Básicamente hace uso del método logout que se importa de django.contrib.auth. django.contrib.auth es una de las aplicaciones que vienen por defecto en Django.

Además, hacemos uso de un shortcut, que es redirect. redirect nos permite hacer una redirect como respuesta HTTP.

Debemos modificar cms/urls.py para que quede así:

cms/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('', views.index),
    path('loggedIn', views.loggedIn),
    path('logout', views.logout_view),
    path('<str:llave>', views.get_content),
]

Prueba a visitar la página de logout y posteriormente la de loggedIn. Comprobarás que ciertamente has salido de la sesión.

.

Hecho el logout, veamos cómo hacer login. Hacer login no es tan sencillo como logout, porque hace falta tener un formulario de autenticación (como el que hemos usado, prestado, de admin). Django ofrece funcionalidad para realizar el login, pero necesita que nosotros específiquemos cómo va a ser ese formulario de autenticación. Así que vamos a implementar la funcionalidad de login y lo haremos para el proyecto entero (i.e., para todas las apps). Para eso, primero debemos modificar el urls.py del proyecto:

proyecto/urls.py
from django.contrib import admin
from django.urls import include, path
from django.contrib.auth.views import LoginView as login


urlpatterns = [
    path('cms/', include('cms.urls')),
    path('calc/', include('calc.urls')),
    path('admin/', admin.site.urls),
    path('login', login.as_view()),
]

En este código, la parte relevante viene dada por la línea donde importamos la vista login de django.contrib.auth.views. Lo que estamos haciendo, por tanto, es utilizar una vista de una aplicación Django (llamada auth) y que viene preisntalada en Django. Esta vista realiza todas las acciones necesarias para gestionar el login, pero hace uso de una plantilla.

Para generar la plantilla que nos permita tener el formulario de login, primero debemos indicar en settings.py el diretorio donde estará nuestra plantilla. Recordarás que algo similar hicimos para las plantillas de nuestra aplicación, aunque entonces no necesitamos indicar nada en settings.py ya que las plantillas para las aplicaciones siempre se encuentran en el subdirectorio templates de la aplicación. Para plantillas de proyecto, necesitamos sin embargo indicar dónde van a estar alojadas:

proyecto/settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['templates', ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Una vez modificado el fichero settings.py, tendremos que crear el subdirectorio templates. Este subdirectorio lo crearemos en el raíz de Django (donde está manage.py). Dentro del subdirectorio templates, a su vez crearemos otro subdirectorio llamado registration y ahí crearemos un fichero llamado login.html. La razoń de todo esto es que la vista login buscará la plantilla en registration/login.html dentro del directorio donde se guarden todas las plantillas (en nuestro caso, templates). El contenido de la plantilla es el que se muestra a continuación:

templates/registration/login.html
<html>
  <body>
    <form method="POST" action="/login">
    {% csrf_token %}
      <table>
        <tr>
          <td>Username</td>
          <td>{{ form.username }}</td>
        </tr>
        <tr>
          <td>Password</td>
          <td>{{ form.password }}</td>
        </tr>
      </table>
    <input type="submit" value="login" />
    </form>
  </body>
</html>

Como puedes observar, se trata de un formulario HTML, que cuando se pulsa el botón de 'login', envía un POST a login. El formulario tiene dos campos, form.username y form.password, que es donde se han de introducir las credenciales. Una cuestión interesante es la línea justo después de la etiqueta form. Esta línea incluye el csrf_token, que es un código que se despliega como un campo del formulario oculto. De esta manera, Django puede controlar que todo POST que le llegue haya sido mediante el envío de un formulario propio; en caso de que reciba un POST sin el CSRF correcto, entenderá que puede ser debido a un ataque y lo desechará.

Prueba la página web de login y el formulario. Verás que una vez logueado con éxito, te redirigirá a una página que dará un 404. Esta redirección se puede cambiar. Busca en la documentación de Django en la web, cómo hacerlo.

Si has llegado hasta aquí, pon en el chat de Teams el siguiente mensaje: "Acabo de terminar la parte de login".

Gestionando ficheros estáticos

Los sitios web generalmente necesitan servir ficheros adicionales, como imágenes, código JavaScript u hojas de estilo CSS. En Django, al ser ficheros que no se modifican, los llamaremos «static files» y en vez de servirse como una vista (que es para generar paǵinas o servicios dinaḿicos), lo haremos de otra manera. Para eso, Django ofrece django.contrib.staticfiles para gestionarlos. A continuación veremos cómo hacerlo.

Configurando static files

  1. Asegúrate que django.contrib.staticfiles está incluido en INSTALLED_APPS de settings.py.

  2. En tu archivo de settings, define STATIC_URL, por ejemplo:

    proyecto/settings.py
    STATIC_URL = '/static/'
    
  3. En tus plantillas, puedes usar la etiqueta static para construir la URL de tus elementos estáticos. Así, sólo has de indicar el camino relativo a donde se pida el archivo. Por ejemplo, en la plantilla a continuación se indica que la imagen example.jpg se encuentra dentro del subdirectorio de ficheros estáticos de tu aplicación, cms.

    cms/templates/cms/plantilla.html
    {% load static %}
    <img src="{% static "cms/example.jpg" %}" alt="My image">
    
  4. Almacena, por tanto, tus ficheros estáticos en una carpeta llamada static en tu aplicación. Por ejemplo cms/static/cms/example.jpg.

Probemos a añadir una imagen. Para ello, vamos a crear una vista que utilice la plantilla que hay en cms/templates/cms/plantilla.html:

cms/views.py
from django.http import HttpResponse
[...]
from django.template import loader

[...]

def imagen(request):
    template = loader.get_template('cms/plantilla.html')
    context = {}
    return HttpResponse(template.render(context, request))

Nota que no pasamos ningún valor como contexto a la plantilla (la única variable es static que viene de settings.py), por lo que el contexto es un diccionario vacío.

Ahora, busca una imagen en formato JPEG y guárdala como cms/static/cms/example.jpg. Para que se vea la imagen, vamos a tener que modificar cms/urls.py para que quede así:

cms/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('', views.index),
    path('imagen', views.imagen),
    path('loggedIn', views.loggedIn),
    path('logout', views.logout_view),
    path('<str:llave>', views.get_content),
]

Si todo ha ido bien, deberías ver la imagen en el navegador.

Aviso: Sirviendo ficheros estáticos

Una vez configurado, podrás servir los ficheros estáticos. Ten en cuenta, sin embargo, que durante el desarrollo, si utilizas django.contrib.staticfiles, los ficheros estáticos serán servidos de manera automática por runserver cuando DEBUG esté puesto a True (mira django.contrib.staticfiles.views.serve()). Este método es ineficiente y probablemente inseguro, así que no debería utilizarse en entornos en producción.

Más adelante, en esta misma práctica, en Desplegando ficheros estáticos, podrás encontrar estrategias válidas para servir ficheros estáticos en producción.

Tu proyecto probablemente tenga también elementos que no estén ligados a una aplicación específica. Además de usar un directorio static/ dentro de tus apps, puedes definir una lista de directorios (STATICFILES_DIRS) en tu fichero settings donde Django buscará ficheros estáticos. Nota que STATICFILES_DIRS es una variable que no existe por defecto en settings.py, por lo que tendrás que crearla. Por ejemplo:

proyecto/settings.py
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static"),
    '/var/www/static/',
]

De esta manera, Django tendrá ficheros estáticos en dos localizaciones: en el subdirectorio static del proyecto y en '/var/www/static/' (tienen que ser directorios donde tengas permiso de lectura). Crea el subdirectorio static e incluye en el mismo una imagen.

Sirviendo ficheros estáticos durante el desarrollo

Si utilizas django.contrib.staticfiles tal y como se explica más arriba, runserver lo hará de manera automática cuando DEBUG sea to True. Si no tienes django.contrib.staticfiles en INSTALLED_APPS, puedes servir ficheros estáticos de todas maneras utilizando la vista django.views.static.serve().

Por ejemplo, si tu STATIC_URL se ha definido como /static/, puedes hacerlo añadiendo el siguiente código a tu urls.py:

proyecto/urls.py
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    # ... aquí vienen el resto del URLConfs ...
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

Para acceder vía tu navegador, tendrás que apuntar a http://localhost:8000/static/imagen.jpg (siendo imagen.jpg el nombre de la imagen que has almacenado en el subdirectorio /static/).

Nota

Esta función auxiliar fucionará en modo debug sólo si el prefijo es local (esto es, /static/) y no una URL (por ejemplo, http://static.example.com/).

Además, esta función auxiliar sólo sirve del directorio STATIC_ROOT; no hace descubrimiento de ficheros estáticos como sí hace django.contrib.staticfiles.

Si has llegado hasta aquí, pon en el chat de Teams el siguiente mensaje: "Acabo de terminar la práctica de hoy".

A continuación, podrás ver cuestiones a tener en cuenta: Primero, dónde poner archivos que suban los usuarios, y el segundo, cómo se hace cuando se despliega en producción.

Sirviendo ficheros estáticos de un usuario durante el desarrollo

Durante el desarrollo, puedes servir ficheros estáticos subidos por usuarios a MEDIA_ROOT usando la vista django.views.static.serve().

Por ejemplo, si tu MEDIA_URL está definida como /media/ en settings.py, puedes servir estos ficheros añadiendo el siguiente código a tu urls.py:

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

urlpatterns = [
    # ... the rest of your URLconf goes here ...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Despliegue

django.contrib.staticfiles ofrece una manera de agrupar todos los ficheros estáticos en un único directorio y servirlos de manera sencilla.

  1. Indica como STATIC_ROOT en settings.py el directorio desde el cual te gustaría servir todos los ficheros estáticos, por ejemplo:

    proyecto/settings.py
    STATIC_ROOT = "/var/www/example.com/static/"
    
  2. Ejecuta la instrucción collectstatic de Django:

    $ python manage.py collectstatic
    

    Esto copiará todos tus ficheros estáticos en el directorio STATIC_ROOT.

  3. Usa un servidor web de tu elección para servir los ficheros estáticos. El documento Deploying static files ofrece algunas estrategias comunes para desplegar ficheros estáticos.