En el post de hoy vamos a ver como subir imágenes al servidor a través de nuestra aplicación Django.

Como se comenta en la documentación de Django, aceptar la carga de archivos por parte de usuarios desconocidos podría conllevar riesgos de seguridad, por lo que solo permitiremos subir archivos a usuarios registrados.

Aplicación de album de fotos

Vamos a crear una aplicación que nos permita loguearnos como usuario, crear un album de fotos, y añadir fotos al album.

Entorno

Partiremos de un proyecto con django-registration instalado para gestionar el registro y login de usuarios (si no sabes como hacerlo, te recomiendo leer el tutorial sobre django-registration), y por supuesto, estaremos trabajando en un entorno virtual con virtualenvwrapper.

Primeros pasos

Lo primero que hacemos es movernos al directorio donde tenemos el proyecto y activar nuestro entorno virtual

$ workon miEntorno

y crear un nueva aplicación de Django, que llamaremos albums

(miEntorno)$ ./manage.py startapp albums

Como queremos usar imágenes, necesitaremos instalar la librería Pillow, que es necesaria para usar el modelo de datos de Django para imágenes (ImageField):

(miEntorno)$ pip install pillow

Debemos añadir la nueva aplicación creada al módulo settings del proyecto para que la reconozca, por lo que editamos el archivo mi_proyecto/mi_proyecto/settings.py:

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'albums',
    'registration',
)

Modelo de datos

Abrimos el archivo mi_proyecto/albums/models.py y creamos los modelos que vamos a usar:

from django.db import models


class Album(models.Model):
    owner = models.ForeignKey('auth.User')
    title = models.CharField(max_length=200)
    timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
    updated = models.DateTimeField(auto_now_add=False, auto_now=True)

    def __unicode__(self,):
        return self.title

class AlbumImage(models.Model):
    album = models.ForeignKey(Album, related_name='images')
    image = models.ImageField(upload_to='albums/images/')

    def __unicode__(self,):
        return str(self.image)

Directorio multimedia

Como podemos ver, hemos definido una ruta donde cargar las imágenes (albums/images), así que crearemos la estructura inicial para los archivos estáticos:

mi_proyecto
    /db.sqlite3
    /manage.py
    /albums
        /…
    /mi_proyecto
        /…
    /static
        /static
        /static_only
        /media
            /images
        /templates

Y le indicaremos a nuestro módulo de settings (mi_proyecto/mi_proyecto/settings.py) la ruta a estos directorios:

STATIC_URL = '/static/'

MEDIA_URL = '/media/'

MEDIA_ROOT = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'static', 'media')

STATIC_ROOT = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'static', 'static-only')

STATICFILES_DIRS = (
    os.path.join(os.path.dirname(os.path.dirname(__file__)), 'static', 'static'),
)

TEMPLATE_DIRS = (
    os.path.join(os.path.dirname(os.path.dirname(__file__)), 'static', 'templates'),
)

Formulario de subida

Creamos el archivo mi_proyecto/albums/forms.py, e incluimos el siguiente contenido:

from django import forms
from .models import AlbumImage


class UploadImageForm(forms.ModelForm):

    class Meta:
        model = AlbumImage
        fields = ['album', 'image']

Creamos la función de la vista en mi_proyecto/albums/views.py

from django.contrib.auth.decorators import login_required
from django.shortcuts import render_to_response, RequestContext

from .forms import UploadImageForm

@login_required
def upload_image_view(request):
    if request.method == 'POST':
        form = UploadImageForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            message = "Image uploaded succesfully!"
    else:
        form = UploadImageForm()

    return render_to_response('albums/upload.html', locals(), context_instance=RequestContext(request))


def home_view(request):
    return render_to_response('base.html')

Atención al detalle: Para la vista de carga de ímágenes, estamos usando el decorador @login_required, lo que significa que solo podremos acceder a esta vista si estamos logueados.

Tendremos que indicar en nuestras urls el acceso a esta vista, por lo que cogemos mi_proyecto/urls.py y lo dejamos así:

from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    # Examples:
    url(r'^accounts/', include('registration.backends.default.urls')),
    url(r'^albums/', include('albums.urls')),
    url(r'^admin/', include(admin.site.urls)),
)

Creamos el archivo albums/urls.py, y añadimos:

from django.conf.urls import patterns, include, url


urlpatterns = patterns('albums.views',
    # Examples:
    url(r'^$', 'home_view', name='home_view'),
    url(r'^upload$', 'upload_image_view', name='upload_image_view'),

)

Ahora debemos crear la vista upload.html

Template de carga de imágenes

Crearemos la vista de upload.html, y para hacerla un poco bonita (esto es opcional), vamos a usar bootstrap a nivel muy básico. Para ver como incorporar bootstrap, puedes mirar la sección Añadiendo templates y algo de estilo de el post sobre push notifications

En el directorio templates crearemos el archivo base.html, con el contenido:


<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content=""> <link rel="icon" href="../../favicon.ico"> <title>Demo project</title> <!-- Bootstrap core CSS --> <link href="/static/css/bootstrap.css" rel="stylesheet"> <!-- Custom styles for this template --> <link href="/static/css/jumbotron.css" rel="stylesheet"> <!-- Custom CSS --> <link href="/static/css/custom.css" rel="stylesheet"> </head> <body> {% include 'navbar.html'%} {% block jumbotron %} {% endblock %} <hr> <div class="container"> <div class="row-fluid"> {% block content %} {% endblock %} </div> <hr> <footer> <p>© -KaiK-reations 2014</p> </footer> </div> <!-- Bootstrap core JavaScript ================================================== --> <!-- Placed at the end of the document so the pages load faster --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <script src="/static/js/bootstrap.min.js"></script> </body> </html>

Creamos también el archivo templates/navbar.html, donde añadiremos enlaces a login y registro, así como la posibilidad de logout si el usuario ya está autentificado:

<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
    <div class="container">
      <div class="navbar-header">
        <a class="navbar-brand" href="#">Demo project</a>
      </div>

      <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav pull-right">
                {% if request.user.is_authenticated %}
                    <li class='dropdown'>
                        <a href='#' class='dropdown-toggle' data-toggle='dropdown'>Account
                            <span class="caret"></span>
                        </a>
                        <ul class='dropdown-menu' role='menu'>
                            <li><a href="#">Account Information</a></li>
                            <li><a href="/accounts/logout">Logout</a></li>
                        </ul>
                    </li>
                {% else %}
                    <li><a href="/accounts/register">Sign Up</a></li>
                    <li><a href="/accounts/login">Login</a></li>
                {% endif %}

            </ul>            

        </div><!--/.navbar-collapse -->

    </div>
</div>

Finalmente, creamos el archivo templates/albums/upload.html:

{% extends 'base.html' %}



{% block jumbotron %}
    <!-- Jumbotron -->
    <div class="jumbotron">
      <h1>Image uploader</h1>
      <p class="lead">This is just a simple view to upload images to an album.</p>

    </div>
{% endblock %}



{% block content %}

    {% if message %}
      <div class='alert alert-info'>
        {{ message }}
      </div>
    {% endif %}

    <h3>Upload a picture</h3> 
    <form  role="form"  enctype="multipart/form-data" action="" method="POST" id="upload-image-form"> 
        {% csrf_token %}
        {{ form.as_p }}
    </form>

{% endblock %}

¡Importante!:

  • enctype=”multipart/form-data : Debemos indicar en el formulario que vamos a subir un archivo de este modo, o de lo contrario recibiremos un error de validación diciéndonos que el campo está vacío.
  • {% csrf_token %}: Siempre debemos añadir el token de autentificación de formularios Django, o recibiremos un error de servidor.

Añadimos Album y AlbumImage al panel de administración

Editamos el archivo albums/admin.py para añadir:

from django.contrib import admin

from .models import Album, AlbumImage

class AlbumImageInline(admin.TabularInline):
    model = AlbumImage
    extra = 3

class AlbumAdmin(admin.ModelAdmin):
    inlines = [ AlbumImageInline, ]

Probando los resultados

Sincronizamos la base de datos

(miEntorno)$ ./manage.py syncdb

y ejecutamos el servidor de desarrollo

(miEntorno)$ ./manage.py runserver

De entrada podemos crear algún álbum mediante el panel de administración, ya que nuestro formulario nos requerirá asociar la imagen a un álbum.

Podemos comprobar que si intentamos ir directamente a /albums/upload, seremos redirigidos a la vista de login. Es lógico ya que no nos hemos logueado. Cabe destacar que se añade la ruta de la que venimos en la url, para ser redirigidos tras el login (accounts/login/?next=/albums/upload)

Por otro lado, si nos conectamos a /albums/, deberíamos ver nuestra barra de navegación, con los enlaces a login y signup.

bootstrap login

Seleccionamos el enlace de Login e iremos a la vista de login, donde podremos introducir nuestro usuario y contraseña.

Una vez logueados, ahora sí podremos acceder a /albums/upload, donde nos encontraremos con el formulario:

image uploader form

 

Tras seleccionar el álbum y añadir una imágen mediante el selector de archivos, seleccionamos subir, se procesa nuestra peticiçon y… voilà! Nos apacere un mensaje diciendo que todo ha ido bien.

django image upload success

Podemos conectarnos al panel de administración para ver si realmente se ha creado la imagen.

django administration panel

No sólo vemos que se han cargado nuestras imágenes, sino que además podemos acceder mediante un enlace.

¡Fantástico! Vamos a probarlo.

Django debug page not found

¿Os suena? Sí amigos, es el debug de Django diciéndonos que no encuentra la página solicitada. Es normal, hemos creado la estructura de ficheros estáticos, pero no le hemos dado acceso a las peticiones mediante el módulo url.py, así que abrimos nuestro mi_proyecto/urls.py y añadimos:

from django.conf import settings
#...otros imports...

urlpatterns = patterns('',
    url(r'^static/(?P<path>.*)$', 'django.views.static.serve', {
        'document_root': settings.STATIC_ROOT
    }),
    url(r'^media/(?P<path>.*)$', 'django.views.static.serve', {
        'document_root': settings.MEDIA_ROOT
    }), 
    #...el resto de urls que ya teníamos...
)

De este modo, ahora sí, podremos ver las imágenes cargadas accediendo a través de la url que nos proporciona el panel de administración.

¡Aquí la tenemos!

django media url

 

Eso ha sido todo por hoy, ¡espero que haya sido de ayuda!