Upload plików w Django

Upload plików w Django

W Django nie zapomniano o kwestii uploadu plików. Deweloperzy tego frameworka starali się uczynić tę czynność jak najprostszą mimo to prawidłowa realizacja tego zadania wymaga odrobiny wysiłku. Na początek przytoczę znaleziony na stackoverflow.com minimalny przykład prezentujący krok po kroku jak w Django pobrać od użytkownika plik i zapisać go na serwerze. Będzie on stanowił doskonały punkt wyjścia dla bardziej zaawansowanych tematów jak dbanie o równomierny rozkład plików w katalogach czy przycinanie obrazków w panelu administracyjnym z użyciem jQuery i jCrop, którymi to tematami zajmę się w kolejnych wpisach.

Poniższy opis oparty jest na przykładzie, którego źródło można ściągnąć z github-a

Przykład bazuje na Django 1.3 w którym główny plik settings.py i urls.py trzymane są w katalogu głównym projektu.

minimal-django-file-upload-example/
    src/
        myproject/
            database/
                sqlite.db
            media/
            myapp/
                templates/
                    myapp/
                        list.html
                forms.py
                models.py
                urls.py
                views.py
            __init__.py
            manage.py
            settings.py
            urls.py

W Django 1.4 nastąpiła zmiana w domyślnej strukturze projektu i wyżej wspomniane pliki powinny trafić do podkatalogu o nazwie odpowiadającej nazwie projektu.

1. Konfiguracja: /myproject/settings.py

Przede wszystkim należy zacząć od prawidłowej konfiguracji bazy danych ponieważ szczegółowa ścieżka do pliku zapisywana będzie w bazie danych, mediów – czyli wskazania katalogu w projekcie, do którego będą trafiały wszystkie wgrywane przez użytkowników pliki oraz URL-a zarezerwowanego na potrzeby serwowania plików statycznych. Trzeba też pamiętać aby aplikacja zawierająca model ze zdefiniowanymi polami do uploadu plików była dodana do INSTALLED_APPS.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': '/path/to/myproject/database/sqlite.db'),
        'USER': '',
        'PASSWORD': '',
        'HOST': '',
        'PORT': '',
    }
}

MEDIA_ROOT = '/path/to/myproject/media/'
MEDIA_URL = '/media/'

INSTALLED_APPS = (
 …
 'myapp',
)

Więcej na temat konfiguracji mediów i plików statycznych w Django.

2. Model: myproject/myapp/models.py

Do uploadu plików w modelu Django służy pole FileField lub też jego specjalna wersja dla obrazków ImageField. Oba z wymienionych pól mają atrybut „upload_to” umożliwiający wskazanie szczegółowej lokalizacji składowania pliku.

# -*- coding: utf-8 -*-
from django.db import models
 
class Document(models.Model):
    docfile = models.FileField(upload_to='documents/%Y/%m/%d')

Jeśli ustawimy upload_to=’documents/%Y/%m/%d’ to w katalogu określonym w zmiennej MEDIA_ROOT zostanie utworzona ścieżka oparta na bieżącej dacie np. „documents/2013/03/09”.

Atrybutowi „upload_to” możemy przypisać zarówno tekst, jak i też metodę czy funkcję zwracającą ścieżkę do katalogu, co daje nam większe możliwości, ale jest to kwestia bardziej złożona i omówię ją przy innej okazji.

3. Formularz: myproject/myapp/forms.py

Wykorzystanie obiektów formularzy w Django bardzo ułatwia pracę z formularzami o czym przekonałem się wielokrotnie. Także w tym przypadku najprościej jest użyć ModelForm – czyli formularza opartego na modelu, w którym nie trzeba definiować pól formularza tylko wskazać na jakim modelu dany formularz ma się opierać i które pola mają podlegać edycji. Przytaczany tu przykład jednak pokazuje jak stworzyć zupełnie niestandardowy formularz z wykorzystaniem kontrolki pola do uploadu pliku FileField. UWAGA! – nie należy mylić pól modelu z polami formularza, które mimo podobieństwa nazwy są czymś innym.

# -*- coding: utf-8 -*-
from django import forms
 
class DocumentForm(forms.Form):
    docfile = forms.FileField(
        label='Select a file',
        help_text='max. 42 megabytes'
    )

4. Widok: myproject/myapp/views.py

W widoku realizowany jest cały proces obsługi formularza. Po przeprowadzeniu walidacji możemy śmiało pobrać plik z request.FILES i przypisać go wprost do pola modelu. Jeszcze łatwiej byłoby w przypadku formularza opartego o model, ale tak za to mamy większą elastyczność gdyż przed zapisaniem pliku w bazie danych moglibyśmy jeszcze coś z nim zrobić (np. zmienić nazwę).

# -*- coding: utf-8 -*-
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
 
from myproject.myapp.models import Document
from myproject.myapp.forms import DocumentForm
 
def list(request):
    # Handle file upload
    if request.method == 'POST':
        form = DocumentForm(request.POST, request.FILES)
        if form.is_valid():
            newdoc = Document(docfile = request.FILES['docfile'])
            newdoc.save()
 
            # Redirect to the document list after POST
            return HttpResponseRedirect(reverse('myapp.views.list'))
    else:
        form = DocumentForm() # A empty, unbound form
 
    # Load documents for the list page
    documents = Document.objects.all()
 
    # Render list page with the documents and the form
    return render_to_response(
        'myapp/list.html',
        {'documents': documents, 'form': form},
        context_instance=RequestContext(request)
    )

5. Konfiguracja URL-i: myproject/urls.py

Django nie udostępnia mediów z MEDIA_ROOT automatycznie gdyż byłoby to niebezpieczne w środowisku produkcyjnym. Na serwerze developerskim to Apache, Nginx czy Lighttpd zajmują się serwowaniem plików statycznych natomiast w środowisku developerskim trzeba sobie dodać regułkę do urls-ów.

# -*- coding: utf-8 -*-
from django.conf.urls.defaults import patterns, include, url
from django.conf import settings
from django.conf.urls.static import static
 
urlpatterns = patterns('',
    (r'^', include('myapp.urls')),
) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

,

6. URL-e aplikacji: myproject/myapp/urls.py

Aby widok z danej aplikacji był dostępny także dla niego trzeba dodać regułkę do urls.py

# -*- coding: utf-8 -*-
from django.conf.urls.defaults import patterns, url
 
urlpatterns = patterns('myapp.views',
    url(r'^list/$', 'list', name='list'),
)
<pre>
 
<h2>7. Szablon: myproject/myapp/templates/myapp/list.html</h2>
 
<p>Aby można było wgrać plik na serwer wymagane jest aby tag formularza zawierał atrybut enctype="multipart/form-data" oraz żeby formularz był wysyłany metodą POST</p>
 
<pre lang="html4strict">
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Minimal Django File Upload Example</title>
    </head>
    <body>
    <!-- List of uploaded documents -->
    {% if documents %}
        <ul>
        {% for document in documents %}
            <li><a href="{{ document.docfile.url }}">{{ document.docfile.name }}</a></li>
        {% endfor %}
        </ul>
    {% else %}
        <p>No documents.</p>
    {% endif %}
 
        <!-- Upload form. Note enctype attribute! -->
        <form action="{% url list %}" method="post" enctype="multipart/form-data">
            {% csrf_token %}
            <p>{{ form.non_field_errors }}</p>
            <p>{{ form.docfile.label_tag }} {{ form.docfile.help_text }}</p>
            <p>
                {{ form.docfile.errors }}
                {{ form.docfile }}
            </p>
            <p><input type="submit" value="Upload" /></p>
        </form>
    </body>
</html>

8. Uruchomienie przykładu

Tak dla pełnego obrazu – aby uruchomić omawiany tu minimalny, przykład uploadu plików w django należy ściągnąć jego źródła lub przekopiować z wyżej zamieszczonych listingów a następnie uruchomić w konsoli odpowiednie komendy:

> cd myproject
> python manage.py syncdb
> python manage.py runserver

Po uruchomieniu serwera pod adresem localhost:8000/list/ zobaczymy – na początek pustą – listę zuploadowanych plików oraz formularz. Pliki powinny się zapisywać w katalogu analogicznym do „/path/to/myproject/media/documents/2013/03/09/”

Autorem powyższego wyczerpującego przykładu jest Akseli Palén. Omówienie jest wzbogacone kilkoma moimi komentarzami.