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.