Python Django入門 初めの1歩からWEBアプリ作成までの流れ その7 画像のアップロードと表示

Pillowのインストール

後程解説するModelの箇所で紹介しますが、ImageFieldを利用するためには、Pillowのインストールが必須の様子です。

pip install Pillow

settings.pyにMEDIA_ROOTの設定

アップロードされる画像の保存先をMEDIA_ROOTとしてsettings.pyに定義します。

通常は、プロジェクトフォルダ直下に”media”という名称で作成します。

# pj/settings.py
import os
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

BASE_DIRはデフォルトの場合、プロジェクトフォルダになりますので、プロジェクトフォルダ直下に’media’というフォルダが作成され、ここにアップロードされた画像が保存されるようになります。

尚、このmediaフォルダは手動で作成する必要はなく、上記の設定をしていれば初回画像アップロード時に自動的にmediaフォルダが作成されます。

画像アップロード用のModel作成

ImageFieldを持ったクラスをModelに定義すると、そのクラスから生成されたインスタンスは①データベースへ保存される②MEDIA_ROOTに画像が保存される というように2か所にデータが保存されることになります。通常のModelで定義されるFieldであれば①のデータベースへの保存のみですが、ImageFieldは②の役割も担ってくれるという面で特殊なフィールドです。

models.py
# pj/app/models.py
from django.db import models

class UploadImage(models.Model):
    image = models.ImageField(upload_to = 'images/')

また、上記のようにImageFieldのキーワード引数”upload_to”にフォルダ名を指定することで、MEDIA_ROOT以下の保存先を指定することが出来ます。

上記の例では、”pj/media/MEDIA_ROOTimages/upload_to” が保存先に指定されていることになります。

画像アップロード用のFormの作成

以前の解説記事で、モデルを継承してModelFormというフォームモデルを作成すれば、フォームから入力したデータがデータベースに保存されることを解説しました。

この時にフォームモデルに、ImageFieldを持たせれば、画像アップロード用のフォームが作成されます。

forms.py
from django import forms
from .models import *

class UploadForm(forms.ModelForm):
    class Meta:
        model = UploadImage
        fields = ['image']
    
    def __init__(self, *args, **kwargs):
        for field in self.base_fields.values():
            field.widget.attrs["class"]="form-control"
        super().__init__(*args, **kwargs)    

init関数の部分は、Bootstrapの「form-control」クラスを各フィールドに適用するために実装しています。必須ではありませんので、必要に応じて実装を検討下さい。

上記をビュークラスからテンプレートに渡して、ブラウザ上に表示させると下記のような画面が表示されます。

そのため、次の作業は、このテンプレートの作成とforms.pyをテンプレートに引き渡す仕組みをビュー関数に作成することになります。

views.py

views.pyには2つの役割が実装されています。

  1. GETリクエストが来た際に、フォームをテンプレートに渡す。
    (params={}とreturn render()の部分がこの処理に相当します。)
  2. POSTリクエストが来た際に、下記の処理を実行する。
    フォームに、RequestPOST情報FILES情報を引数に渡してフォームインスタンスを作成
    ①で作成したフォームインスタンスに対して”is_valid()”メソッドを使ってバリデーションを行います。
    ②の結果、正常なデータが取得されている場合、インスタンスの親クラス(ModelForm)のsave()メソッドを使ってフォームインスタンスをデータベースに保存します。
    テンプレートに引き渡すパラメータ(param)にidを追加し、③でデータベースに保存したフォームインスタンスのidを格納します。
views.py
from django.shortcuts import render
from .forms import UploadForm
from .models import UploadImage

def index(request):
    params = {
        'title':'画像のアップロード',
        'upload_form':UploadForm(),
        'id':None,
    }
    if (request.method == 'POST'):
        form = UploadForm(request.POST, request.FILES) # ①
        if form.is_valid(): # ②
            upload_image = form.save() # ③
            params['id'] = upload_image.id # ④
        
    return render(request, 'app/index.html', params)

テンプレート作成

テンプレート作成時の注意点は下記の2点です。

  1. views.pyから引き渡されるforms.pyで定義したフォームクラスをテンプレートに{{ フォームクラス名.as_p }}のようにして引き渡す。
  2. formタグの属性に「 method=”post” enctype=”multipart/form-data” 」を設定する。
  3. formタグのaction属性の属性値は、URLテンプレートタグを使って、app/urls.pyに設定されているname=”テンプレート名”を設定する。*

*URLテンプレートタグは、urls.pyでnameを付けられているpath関数に紐づけることが出来ます。

pj/templates/app/index.html
<!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.0">
    <link rel="stylesheet" href="{% static 'style.css' %}">
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{title}}</h1>
    <div>
        {% if id is not None %}
        <h2>画像が登録されました</h2>
        <p>画像のIDは {{id}} です</p>
        {% endif %}
    </div>
    <div>
        <h1>{{ title }}</h1>
        <h2>画像を登録する。</h2>
        <form action="{% url 'app/index' %}" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <p>{{ upload_form.as_p }}</p>
        <p><input type="submit" value="アップロード"></p>
        </form>
    </div>
</body>
</html>

urls.py

トップページにアクセス(下記のpath()関数内のURL位置引数=’ ‘の箇所がトップページへのアクセス時の処理を示します。)があった際、appのurls.pyにリクエストを転送するように設定しています。

pj/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('', include('app.urls')),
    path('admin/', admin.site.urls),
]

トップページにアクセスがあった際、views.pyに設定されているindex関数テンプレート名=’app/index’紐づけるように設定しています。

app/urls.py
from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='app/index'),
    path('admin/', admin.site.urls),
]

以上で画像を保存することが出来ます。

画像のアップロード後に下記のように表示されていれば成功です。

アップロードファイルを表示する方法

アップロードされた画像を表示する方法は、基本的には以前解説した静的ファイルの扱い方の方法を踏襲すれば大丈夫です。下記の記事でも解説している通り、静的ファイルがブラウザに表示される手順は下記のとおりです。

①ブラウザがHTMLファイルを読み込む

②ブラウザがHTMLファイル内の<link>や<img>などの静的ファイルのタグを発見すると、そのhref属性値やsrc属性値に設定されているURLをウェブサーバーにリクエストします。

③URLがSTATIC_URLで始まっているファイルのリクエストの場合、ウェブサーバー(Django開発環境の場合、Django開発環境用ウェブサーバーソフト)が静的ファイルの要求だと認識して、STATIC_ROOT(静的ファイル保存場所)の中を検索し、ファイルを取得してブラウザに返す

アップロードされたファイルについても上記と同様に、開発用ウェブサーバーソフトが静的ファイルであることを認識してもらうように設定する必要があります。

静的ファイルの場合は、ファイルのURLがSTATIC_URLから始まる場合、静的ファイルであると認識されました。

アップロードファイルの場合は、ファイルのURLがMEDIA_URLから始まる場合に、アップロードファイルのリクエストであると判断されます。

MEDIA_URLは、settings.pyに手動で追加する必要があります。

settings.py

MEDIA_URLの定義

settings.py
MEDIA_URL = 'media/'

pj/urls.py

次に、MEDIA_URLMEDIA_ROOT紐づけを行う必要があります。
MEDIA_URLから始まるファイルの要求があった場合、開発用ウェブサーバーソフトがMEDIA_ROOTからファイルを検索してもらうための紐づけが必要です。

そのためには、プロジェクトのurls.pyに下記のコードを入力します。

pj/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('', include('app.urls')),
    path('admin/', admin.site.urls),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

テンプレート

テンプレートでは、シンプルにimgタグのsrc属性値にviews.pyからURLを受け取れるようにするだけです。

templates/display.html
<!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.0">
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ title }}</h1>
    <div>
        <h2>ID:{{ id }}の画像</h2>
        <img src="{{ url }}">
    </div>
</body>
</html>

views.py

get_object_or_404(モデルクラス, pk=id)を用いてモデルクラス内でidに合致するモデルがあるかないかを検索しています。尚、idには、後のアプリケーションのurls.pyに登場しますが、リクエストURLの末尾にidを追加することになります。このidを用いて該当するモデルを検索します。

views.py
from django.shortcuts import render, get_object_or_404
from django.views.generic import TemplateView
from .forms import UploadForm
from .models import UploadImage

# Create your views here.
class Index(TemplateView):
    template_name = 'app/index'

def index(request):
    params = {
        'title':'画像のアップロード',
        'upload_form':UploadForm(),
        'id':None,
    }
    if (request.method == 'POST'):
        form = UploadForm(request.POST, request.FILES)
        if form.is_valid():
            upload_image = form.save()
            params['id'] = upload_image.id
        
    return render(request, 'app/index.html', params)

def display(request, image_id=0):
    upload_image = get_object_or_404(UploadImage, id=image_id)

    params = {
        'title':'画像の表示',
        'id':upload_image.id,
        'url':upload_image.image.url,
    }

    return render(request, 'app/display.html', params)

app/urls.py

app/urls.py
from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='app/index'),
    path('display/<int:image_id>', views.display, name='display'),
]

上記の設定を行った後、通常通りpython manage.py runserverを行います。

今回は、ID=2として画像のアップロードを実行しました。

そして、ブラウザのURL入力欄に下記のように、テンプレート名/ID を入力してリクエストを送信すると下記のようにアップロードした画像が返されます。

コメント

タイトルとURLをコピーしました