Frameworks

Tutorial Django: Formularios y Templates para guardar y mostrar fotos

En este nuevo capítulo vamos trabajar con las plantillas que utilizamos en el post anterior para mostrar la información de forma más visual. Continuaremos viendo las vistas genéricas para crear formularios de forma que podamos modificar o añadir registros a nuestra base de datos.

Publicado el 11 de Diciembre de 2014
Compartir

En este nuevo capítulo vamos trabajar con las plantillas que utilizamos en el post anterior para mostrar la información de forma más visual. Continuaremos viendo las vistas genéricas para crear formularios de forma que podamos modificar o añadir registros a nuestra base de datos.

Como punto de partida vamos a modificar la plantilla category_list.html que teníamos en el capítulo anterior para dejarla así:

<h1>ALBUM</h1>

<h2>Category</h2>

<div>
    {% for c in object_list %}
        <a href="{% url 'category-detail' c.pk %}">
            {{ c.name }}
        </a>
    {% endfor %}
</div>

De nuestra nueva plantilla podemos destacar dos elementos, las variables que están identificadas por {{ }} y ya usamos en el capítulo anterior y los tags que están representados por {% %}.

Las variables son evaluadas y sustituidas por su valor en la representación de la plantilla. La forma en que se muestran las variables puede ser modificada mediante el uso de filtros que veremos más adelante.

Los tags nos ofrecen la posibilidad de incluir lógica o control de flujo dentro de las plantillas entre otras cosas.

En nuestra plantilla estamos usando {% for %} para recorrer las distintas categorías que tenemos y poder mostrar su nombre con la variable {{ c.name }} , el valor name del objeto c extraído de la lista object_list.

{% url %} es otro tag que usaremos muy a menudo , en este caso le pasamos el nombre de la url a la que queremos apuntar y el parámetro que necesita.

Si vemos el resultado en el navegador http://127.0.0.1:8000/álbum/category/, tenemos lo siguiente:

Imagen 0 en Tutorial de Django: Formularios y Templates: Guardar y enseñar nuestras fotos.

Si lo pensamos bien, vemos que el título álbum y la cabecera que indica si estamos en la sección Category o Photo se van a repetir para el resto de plantillas. Para evitar tener que duplicar código y seguir el principio DRY de Django, tenemos a nuestra disposición dos tags que nos van a permitir usar una única plantilla como esqueleto de nuestra aplicación e ir insertando en esta las partes propias del resto de plantillas, esto se conoce como Template inheritance . Vamos a usarlo.

Lo primero que haremos es modificar la plantilla category_list.html para dejarla así:

{% extends 'base.html' %}

{% block section %}
    <h2>Category</h2>
    <hr>
{% endblock section %}

{% block maincontent %}
    <div>
        {% for c in object_list %}
            <a href="{% url 'category-detail' c.pk %}">
                {{ c.name }}
            </a>
        {% endfor %}
    </div>
{% endblock maincontent %}

Aquí tenemos los dos nuevos tags, {% extends %} y {% block %} . El primero va a cargar nuestra plantilla base.html, que será la plantilla esqueleto e incluirá las secciones contenidas en los tags block donde se lo indiquemos.

Ahora vamos a crear un nuevo directorio ./templates en el que incluiremos la plantilla base.html

(tutorial)openwebinars@~/aplicaciones/myapps: tree templates
templates
└── base.html

0 directories, 1 file
(tutorial)openwebinars@~/aplicaciones/myapps: 

base.html quedará de la siguiente forma:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Album</title>
</head>
<body>
    <h1>Album</h1>
    <ul>
        <li>
            <a href="{% url 'category-list' %}">Category</a>
        </li>
        <li>
            <a href="{% url 'photo-list' %}">Photo</a>
        </li>
    </ul>
    <hr>
    <div>
        {% block section %}
        {% endblock section %}
    </div>
    <div>
        {% block maincontent %}
        {% endblock maincontent %}
    </div>
</body>
</html>

Hemos incluido dos nuevos links para poder navegar entre categorías y fotos.

Vamos a modificar la que era nuestra url principal, para que deje de mostrar la primera vista que hicimos y la usaremos como página de inicio. Editamos ./album/views.py y ./album/urls.py

En la ./album/views.py añadimos:

from django.shortcuts import render

def base(request):
    return render(request, 'base.html')

y en ./álbum/urls.py:

from django.conf.urls import patterns, url
from album import views

urlpatterns = patterns('',
                       url(r'^$', views.base, name='base'),
                       url(r'^category/$', views.CategoryListView.as_view(), name='category-list'),
                       url(r'^category/(?P<pk>\d+)/detail/$', views.CategoryDetailView.as_view(),
                           name='category-detail'),
                       url(r'^photo/$', views.PhotoListView.as_view(), name='photo-list'),
                       url(r'^photo/(?P<pk>\d+)/detail/$', views.PhotoDetailView.as_view(),
                           name='photo-detail'),
                       )

Además, vamos a indicar la nueva ruta donde estará la plantilla base.html añadiendo en ./myapps/settings.py

TEMPLATE_DIRS = (
    os.path.join(BASE_DIR, 'templates/'),
)

Comprobamos el resultado en el navegador, http://127.0.0.1:8000/album/.

Imagen 1 en Tutorial de Django: Formularios y Templates: Guardar y enseñar nuestras fotos.

y http://127.0.0.1:8000/album/category/

Imagen 2 en Tutorial de Django: Formularios y Templates: Guardar y enseñar nuestras fotos.

Ahora ya podemos practicar con category_detail.html para que herede de base.html y experimentar con las distintas etiquetas html. Nosotros lo hemos dejado así, http://127.0.0.1:8000/album/category/1/detail/

Imagen 3 en Tutorial de Django: Formularios y Templates: Guardar y enseñar nuestras fotos.
{% extends 'base.html' %}
{% load static %}

{% block section %}
    <h2>{{ object.name }}</h2>
    <hr>
{% endblock section %}

{% block maincontent %}
<div>
    <table border="1px">
        <tr>
            {% for p in object.photo_set.all %}
                <td>
                    <div>{{ p.title }}</div>
                    <div>
                        <a href="{% url "photo-detail" p.id %}">
                            <img src="{% static p.photo.url %}" style="width:200px;height:200px"/>
                        </a>
                    </div>
                </td>
            {% endfor %}
        </tr>
    </table>
</div>
{% endblock maincontent %}

Vemos que en cada categoría se muestra una miniatura con los enlaces a las distintas fotos que pertenecen, para mostrar las imágenes necesitamos incluir STATICFILES_DIRS en ./album/settings.py

STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'media'),
)

Ahora que tenemos los enlaces al detalle de las fotos, vamos a completar la navegación modificando photo_detail.html para que nos muestre la foto y los detalles.

Editamos photo_detail.html.

{% extends 'base.html' %}
{% load static %}


{% block section %}
    <h2>
        {{ object.title }}
        {% if object.favorite %}
            &#9733;
        {% else %}
            &#9734;
        {% endif %}
        {% if object.category_id %}
            ( <a href="{% url "category-detail" object.category_id %}">{{ object.category }}</a> )
        {% endif %}
    </h2>
    <p><strong>comments: </strong>{{ object.comment|default:"no comments" }}</p>
    <a href="{% url "photo-update" object.id %}">edit</a>
    <a href="{% url "photo-delete" object.id %}">delete</a>
    <hr>
{% endblock section %}

{% block maincontent %}
<div>
    <div>
        <img src="{% static object.photo.url %}" style="width:550px;height:420px"/>
    </div>
</div>
{% endblock maincontent %}

En esta nueva plantilla hemos incluido varias cosas interesantes. Con la etiqueta load estamos cargando el modulo static.py que necesitamos para mostrar las imágenes. Para mostrar los comentarios usamos la variable {{ object.comment }} con un filtro default para incluir un comentario por defecto cuando el valor es una cadena vacía “”, además tenemos dos nuevos enlaces para editar y borrar.

Como ya hemos visto, estos enlaces apuntan a una vista a través de ./album/urls.py. Con el primero modificaremos las fotos mientras que con el segundo vamos a poder borrar un registro de nuestra base de datos.

Vamos a crear estas nuevas funcionalidades y aprovechamos para recordar como se trabajaba con las vistas genéricas.

Editamos ./album/views.py para añadir lo siguiente.

from django.core.urlresolvers import reverse_lazy
from django.views.generic.edit import UpdateView, CreateView, DeleteView

class PhotoUpdate(UpdateView):
    model = Photo
    
    
class PhotoCreate(CreateView):
    model = Photo


class PhotoDelete(DeleteView):
    model = Photo
    success_url = reverse_lazy('photo-list')

También hemos incluido la vista PhotoCreate que usaremos enseguida.

Enlazamos las vistas con urls editando ./album/urls.py y añadiendo lo siguiente dentro de patterns.

# Update
url(r'^photo/(?P<pk>\d+)/update/$', views.PhotoUpdate.as_view(), name='photo-update'),
#Create
url(r'^photo/create/$', views.PhotoCreate.as_view(), name='photo-create'),
#Delete
url(r'^photo/(?P<pk>\d+)/delete/$', views.PhotoDelete.as_view(), name='photo-delete'),

Ahora bien, para las dos primeras vistas, update y create , necesitaremos un formulario desde el que podamos interactuar bien modificando los valores de nuestros registros o bien creando uno nuevo. Para crearla usaremos el nombre que buscará Django por defecto, photo_form.html , esta plantilla es común para las dos vistas PhotoUpdate y PhotoCreate ya que muestra todos los campos definidos en el modelo y dependiendo de la llamada que se haga, desde el modo Update o Create mostrará los campos con los valores o vacíos respectivamente.

Este es el aspecto que tendrá photo_form.html:

{% extends "base.html" %}

{% block maincontent %}
    <form enctype="multipart/form-data" method="post">{% csrf_token %}
        {{ form.as_p }}
        <button type="submit">Save</button>
        <a class='btn' href="{% url 'photo-list' %}">Cancel</a>
    </form>
{% endblock maincontent%}

Volvemos a usar como plantilla principal base.html. Dentro de la etiqueta form usamos el atributo method como POST en lugar de GET ya que en update y create estamos modificando la información contenida en la base de datos. También es importante destacar el uso del tag {% csrf_token %} ; usaremos este tag dentro de las etiquetas form con atributos POST para evitar ataques CSRF. Básicamente estos ataques usan los datos de conexión de un usuario conocido y de confianza para realizar acciones no autorizadas sobre el servidor.

La variable form contiene los campos de nuestro modelo y para representarlos podemos usar as_p para mostrarlos entre etiquetas <p> en html, as_table para usar etiquetas <tr> o as_ul para usar etiquetas <li>

Tenemos que tener en cuenta una cosa más, cuando queramos cancelar la edición de un registro estamos redirigiendo la navegación hacia la vista nombrada como photo-list, de igual modo tenemos que definir hacia dónde iremos cuando grabemos los datos usando el boton save, para ello editamos ./album/models.py y añadimos el siguiente método dentro de la clase Photo.

def get_absolute_url(self):
        return reverse('photo-list')

Como estamos usando reverse es necesario importarlo desde django.core.urlresolvers

Si abrimos el detalle de una foto y pulsamos sobre el enlace edit veremos esto:

Imagen 4 en Tutorial de Django: Formularios y Templates: Guardar y enseñar nuestras fotos.

donde podemos modificar los valores que teníamos.

Con estos pasos que acabamos de hacer tenemos lista también la opción de crear un nuevo registro para añadir fotos , solo tenemos que incluir el enlace en la plantilla que queramos. Nosotros lo hemos hecho en base.html como otro elemento más de la lista.

Imagen 5 en Tutorial de Django: Formularios y Templates: Guardar y enseñar nuestras fotos.

Vamos a ver como preparar la opción de borrado . Aunque ya tenemos enlazados las vistas con la url y aparentemente no es necesaria ninguna plantilla para borrar un registro de la base de datos, si intentamos borrar una foto veremos el error TemplateDoesNotExist , esto es así porque Django nos pide una plantilla para confirmar la acción , justo debajo del error podemos ver el nombre que espera encontrar Django.

Imagen 6 en Tutorial de Django: Formularios y Templates: Guardar y enseñar nuestras fotos.

Vamos a crearla, en nuestro directorio ./album/templates/album incluimos una nueva plantilla photo_confirm_delete.html

{% extends 'base.html' %}

{% block maincontent %}
    <form action="" method="post">{% csrf_token %}
        <p>Delete "{{ object.title }}"?</p>
        <input type="submit" value="Confirm" />
        <a class='btn' href="{% url 'photo-list' %}">Cancel</a>
    </form>
{% endblock maincontent %}

Igual que al crear o modificar un registro tenemos que decirle el enlace al que debe dirigirse una vez terminada la acción, al borrar también tenemos que hacerlo, ya lo incluimos en la propia clase con el atributo success_url .

Con esto tenemos las herramientas necesarias para cambiar la plantilla photo_list.html y hacer que tenga mejor aspecto, también podemos generar nuevos formularios para crear, editar o eliminar categorías así como jugar con todas las opciones que nos ofrece html para personalizar nuestra aplicacion.

En el siguiente capítulo veremos como usar repositorios y mejoraremos la apariencia de nuestro álbum con Bootstrap.


Compartir este post

También te puede interesar...

Tecnología

Tutorial de Django: Cómo iniciar una aplicación con Django

23 Octubre 2014 José Plana
Tecnología

Tutorial de Django: Vistas y Urls: Procesar los datos.

18 Noviembre 2014 José Plana
Artículos
Ver todos