はじめに
このドキュメントは,もともとはラボの新メンバー向けの入門テキストとして作成したもので,はじめてDjangoにふれる人にざっとその全体像をつかんでもらうことを狙っています.Djangoの日本語での参考資料も充実してきたので今さらという気がしないでもないですが,見直しを機にQiitaに移すことにしました.万が一でもどなたかの参考になれば幸いです.
説明のための具体例として,Djangoのオフィシャルチュートリアルにある投票アプリをとりあげています(が,説明上の都合で少しコードを追加,変更しているところもあります).素人の独学がベースで,特に前半は公式チュートリアルをやってみた感想のような記事です(今回は全7回中の最終回).
全体のコードはまとめてGitHubに置きました.
変更履歴
- [2021/04/26] Djangoのバーションを3.2に更新し,それ基づいて内容を微修正しました.
画像の表示とアップロード
今回は,画像ファイルをデータベースで管理し,ページに表示する方法や,ユーザに画像ファイルをアップロードしてもらう方法をみていきます.
なお,Djangoで画像ファイルを扱うためにはPillowという画像処理ライブラリが必要になるので,まだインストールしていない場合は,下記のコマンドであらかじめインストールしておきましょう.
$ pip install pillow # pipenvを利用している場合は pipenv install pillow
最初に,mysiteのプロジェクト内に,pollsとは別に,albumというアプリケーションを作成します.
$ python manage.py startapp album
外側のmysiteのディレクトリで上記のコマンドを打ち込むと,その中にalbumというディレクトリが新たに作成されます(ここで忘れずに,settings.pyのINSTALLED_APPS
のリストにこのアプリケーションを追加しておきましょう).
続いて,画像を扱う簡単なモデルを作成します.具体的には,albumディレクトリ内のmodels.py(album/models.py)の中に下記の内容を書き足します.
from django.db import models
class Image(models.Model):
picture = models.ImageField(upload_to='images/')
title = models.CharField(max_length=200)
def __str__(self):
return self.title
これは,画像ファイルを表すpicture
とその表題を表すtitle
という2つのfieldをもった,Imageクラスの定義になっています(なお,このmodels.ImageField()
を使うためにPillowが必要になるわけです).
画像ファイルの実体はデータベース内に格納されるわけではなく,あるディレクトリにアップロードされることになります.models.ImageField()
の引数に指定されているupload_to
でこのディレクトリを指定しています.
上の例では,upload_to
にimages/というディレクトリが指定されていますが,これはMEDIA_ROOT
を起点とした相対パス指定になります.また,MEDIA_ROOT
内の画像ファイルをurlで参照する際に起点となるアドレスをMEDIA_URL
として指定しておく必要があります.
今回は,外側のmysiteディレクトリ内にmediaというディレクトリを作成し,それをMEDIA_ROOT
に指定しましょう.また,MEDIA_URL
もわかりやすくmediaという名称にしておきます.具体的には,settings.pyの中に下記のように指定しておくことにします.
BASE_DIR = Path(__file__).resolve().parent.parent
MEDIA_ROOT = BASE_DIR.joinpath('media')
MEDIA_URL = '/media/'
BASE_DIR
はデフォルトのままですが,具体的には外側のmysiteディレクトリを指すことになります.MEDIA_ROOT
はその中のmediaディレクトリという意味にります.また,MEDIA_URL
をこのように設定すると,開発用サーバを用いている場合,http://localhost:8000/media/ が画像ファイルのurlの起点となります.
次に,このモデルをadminサイトに登録して,adminサイトでImageクラスの画像ファイルを操作できるようにしましょう.まず,album/admin.pyに次の内容を書き込みます.
from django.contrib import admin
from .models import Image
admin.site.register(Image)
makemigrationsとmigrateでデータベースの構成を更新してから,開発用サーバを立ち上げ,adminサイトにアクセスしてみましょう.追加したImageクラスが現れているはずです.
これでadminサイトを通じて,画像ファイルをデータベースにアップロードしたり,データベースから削除したりできるようになりました.実際に画像ファイルをいくつかアップロードしてみましょう.
続いて,画像ファイルを表示するページを作成します.このためにまず,album/views.pyの中に次のような簡単なview関数を用意しましょう.
from django.shortcuts import render
from .models import Image
def showall(request):
images = Image.objects.all()
context = {'images':images}
return render(request, 'album/showall.html', context)
データベース内にあるすべてのImageクラスのオブジェクトを取得して,それをimages
という名称でtemplateに渡していることがわかります.
次に,templateを用意します.具体的には,album/templates/album/showall.htmlというファイルを作成し,下記の内容を書き込みます.
<h1>Your Album</h1>
{% for image in images %}
<img src='{{ MEDIA_URL }}{{image.picture}}' width=200>
<h2>{{ image.title }}</h2>
{% endfor %}
albumアプリケーションに関するurlルーティングは,album/urls.pyというファイルを作成し,その中に指定するようにします.ここでは,ひとまず次のように指定しておけばよいでしょう.
from django.urls import path
from . import views
app_name = 'album'
urlpatterns = [
path('showall/', views.showall, name='showall'),
]
また,あわせてmysite/urls.pyの方を次のように更新しておきます.
from django.contrib import admin
from django.urls import include, path
from django.views.generic import base
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('polls/', include('polls.urls')),
path('album/', include('album.urls')),
path('admin/', admin.site.urls),
path('accounts/login/', base.RedirectView.as_view(pattern_name="polls:login")),
path('accounts/profile/', base.RedirectView.as_view(pattern_name="polls:index")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns
の2行目の項目で,album/以降のルーティングを,上で作成したalbum/urls.pyに引き継ぐように指定していることがわかります.
なお,最後に追加されているstatic()
は,開発用サーバが,MEDIA_ROOT
以下のディレクトリに格納されているファイルをMEDIA_URL
を起点としたurlで供給できるようにするための設定です(詳しくはここを参照).
これでOKといいたいところですが,もう1つだけ準備が必要です.settings.pyのTEMPLATES
の中のcontext_processors
に次の項目を追加しておきます(そうでないとtemplate内でMEDIA_URL
の値が参照できないそうです).
TEMPLATES = [
{
...
'OPTIONS': {
'context_processors': [
...
'django.template.context_processors.media', # これを追加する
],
},
},
]
以上で準備は整ったはずです.http://localhost:8000/album/showall/ にアクセスして画像が表示されることを確認してみましょう.
最後にユーザが画像ファイルをアップロードするためのページを作成しておきましょう.まず,Imageクラスのデータ(画像ファイルとそのタイトル)を取得するためのフォームを作成します.album/forms.pyに次の内容を書き込みます.
from django import forms
from .models import Image
class ImageForm(forms.ModelForm):
class Meta:
model = Image
fields = ['picture', 'title']
今回はModelForm
という仕組みを利用しました.これは,あるモデルに対応した形式でデータを取得したい場合に,モデルの定義を利用してフォームを簡単に定義する仕組みです(詳細はここを参照).class Meta
の中をみると,これはImageクラスに対応しており,pictureとtitleのデータを取得するためのフォームであることがわかります.
続いて,album/views.pyの中にアップロード用のview関数upload()
を用意します.
from django.shortcuts import render, redirect
from .models import Image
from .forms import ImageForm
...
def upload(request):
if request.method == "POST":
form = ImageForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect('album:showall')
else:
form = ImageForm()
context = {'form':form}
return render(request, 'album/upload.html', context)
これは,フォームを利用してユーザからデータを取得する場合の典型的なパターンになっていますが,2点だけ補足しておきます.
まず,POSTのリクエストを受けた際にImageForm()
にrequest.POST
だけではなく,request.FILES
も渡している点です.これはPOSTで画像(に限らず他の種類であっても)ファイルを取得する際に必要になるので頭の片隅においておきましょう.
2点目は,form.save()
です.上のImageFormクラスでは特にsave()
メソッドは定義していませんが,form.ModelForm
を継承しているので,デフォルトのsave()
メソッドが機能します.
具体的には,モデルのインスタンスを渡した場合はそれが取得データで上書きされ,今回のように,インスタンスを渡さなかった場合は新しいインスタンスが生成,保存されることになります(詳しくはここを参照).
簡単なtemplateも用意しておきましょう.album/templates/album/upload.htmlのファイルを作成し,次の内容を書き込みます.
<h1>Please Upload</h1>
<form enctype="multipart/form-data" action="{% url 'album:upload' %}" method="post">
{% csrf_token %}
{{form.as_p}}
<input type="submit" value="Upload">
</form>
ここでのポイントは,<form>
タグの属性としてenctype="multipart/form-data"
を指定しておくことです.この指定がないとファイルをDjangoに渡せません.
最後にurlルーティングの情報をalbum/urls.pyに追加します.具体的には,urlpatterns
の中に下の記述を追加します.
path('upload/', views.upload, name='upload'),
ついでに,showallのページからuploadのページにリンクを張っておきましょう.album/templates/album/showall.htmlの末尾に次の1行を追加します.
<h2><a href="{% url 'album:upload' %}">Upload more</a></h2>
以上で準備が整ったので,開発サーバを立ち上げて,http://localhost:8000/album/upload/ にアクセスすると画像のアップロードができるはずです.
おわりに
以上で最終回まで終了です. m(__)m