Skip to content
Aprende Angular de forma rápida y efectiva  Ver curso

Upload de imágenes con Django

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!

Published inDjango

21 Comments

  1. Te agradezco me re sirvio, me volvi loco para configurarlo.

  2. Hola tocayo.. No me quedo claro a que nivel estan las carpetas

    /static
    /static
    /static_only
    /media
    /images
    /templates

    podrias tabular un poco para que queden mas claro?

    Gracias

    • Enrique Oriol Enrique Oriol

      ¡Hola!

      Gracias por el comentario, he indentado esa sección para que se vea más clara la estructura de archivos.

      ¡Saludos!

  3. Rosario Rosario

    Hola, tengo una duda.
    Yo hice algo similar pero para cargar archivos; la cuestión es que los archivos se suben a una carpeta que se crea dentro del proyecto y no se visualizan en el panel de admin de django. Cómo puedo solucionarlo?

    • Rosario Rosario

      Ya encontré mi problema, mil gracias por tu blog 🙂

      • Enrique Oriol Enrique Oriol

        ¡¡¡me alegro!!!

        un saludo

      • Rosario Rosario

        Hola, me surgió otra duda 😀

        Hay manera de decirle en qué carpeta específicamente quiero que me guarde el archivo cargado?

  4. Damian Lluch Damian Lluch

    hola!!, muchas gracias loco!, me re sirvio.

  5. Edu Clerici Edu Clerici

    Hola muy buen tutorial!, Queria reazliar una consulta.

    En un supuesto template, que valor debe tener mi atributo «src» de una etiqueta «img», para hacer referencia a una imagen cargada de la manera que acabas de mostrar?

    • Enrique Oriol Enrique Oriol

      Pues una referencia RELATIVA a la carpeta de imágenes, como hago con la URL de la background image.

      Ejemplo:

      Saludos,

  6. muy buen aporte, te agradesco en gran manera, me fue de mucha utilidad

    • Enrique Oriol Enrique Oriol

      ¡Gracias!

  7. tengo un problema me da el siguiente error: raise TypeError(‘view must be a callable or a list/tuple in the case of include().’)
    TypeError: view must be a callable or a list/tuple in the case of include().

    este error me da cuando añado la siguiente linea en myproyecto/urls.py:
    url(r’^static/(?P.*)$’, ‘django.views.static.serve’, {‘document_root’: settings.STATIC_ROOT}),

    que puede ser?

    • Marc Sola Vazquez Marc Sola Vazquez

      A mi tambien me da ese error. Lo has soucionado?

      • Hola, soy nuevo en django pero yo solucione ese error de esta manera espero les sirva!

        1.importando esto: from django.conf.urls.static import static
        2. Eliminando las lineas que contienen (
        url(r’^static/(?P.*)$’, ‘django.views.static.serve’,
        {
        ‘document_root’: settings.STATIC_ROOT
        }),
        url(r’^media/(?P.*)$’, ‘django.views.static.serve’,
        {
        ‘document_root’: settings.MEDIA_ROOT
        }),

        3. Al final del UrlPatterns…

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

        aqui la documentacion completa!

        https://docs.djangoproject.com/es/1.11/howto/static-files/

  8. Javier Javier

    y si llega un usuario registrado con antecedentes de conducta antisocial y empieza a subir ficheros de gigabytes …como no hemos hecho validación..hacemos su día. Nunca hay que confiar en nadie para nada. Está bien hacer un blog sobre file upload, pero dejarlo a medias es un riesgo de seguridad, porque qué significa registrado, que ha confirmado su correo o que ha pagado mediante tarjeta y confirmado con su móvil ?

    Haz uno sencillito de cómo validar tipo y volumen del fichero permitidos.

    gracias

  9. Javier Javier

    Buenas, una pregunta al cargar una imagen se me guarda en una carpeta media que se creo al hacer collecstatic , pero al cargar la pagina donde esta el url hace referencia a una carpeta media que esta dentro de la app principal. Que debo configurar para que me agarren las imagenes de una carpeta definida?

  10. Saludos, muy completa tu explicación. Tengo poco tiempo con Python.Estoy montando unas imagenes pero necesito pasarle de parametro el id del album y no seleccionarlo. Como podria realizarlo?

    • Estoy haciendolo fuera del administrador

  11. Esteban Pizarro Esteban Pizarro

    podrias hacer el mismo tutorial pero con las versiones actuales de python y django?

Deja un comentario