PythonMania

普段はロボットとAIを組み合わせて色々作ってます。Python関係以外も色々投稿していくと思います。

【Python】Djangoで画像アップロードフォームを作成してみる

f:id:mizuhiki0111:20190430175940p:plain



Djangoで画像アップロードフォームを作成し、サーバー上に保存してみようと思います。



環境
Windows10 64bit
Anaconda3
Python 3.7.3
Django2.2




①事前準備


Anacondaで環境作成

conda create -n myapp
conda activate myapp


Djangoのインストール

conda install Django

画像処理ライブラリPillowのインストール

pip install pillow


プロジェクトの作成

django-admin startproject myproject
cd myproject


アプリの作成

python manage.py startapp myapp

日本語化

myproject/myproject/settings.pyを以下のように変更

LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

プロジェクトに「myapp」を認識させる

INSTALLED_APPS = [
    "myapp",
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

テンプレートフォルダの作成とパス指定

templates\myappを作成し、myproject/myproject/settings.pyにテンプレートパスを指定

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates'),],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

初回migrate

>python manage.py migrate

サーバーテスト起動

>python manage.py runserver



②MEDIA_URLの作成

画像がアップロードされた際のパスを指定しておきます。


myproject/myproject/settings.pyに以下を追記します。

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR,"media")


これにより、アップロードされた画像はmyproject/media/mediaに保存されるよう設定することができました。
※mediaディレクトリはアップロード処理時に自動で作成されます。





③モデルの定義

myproject/myapp/models.pyを編集していきます。


models.pyはDjangoの機能の中で、主にデータベースへのアクセスを請け負っている部分になります。


今回の場合は画像処理なので「ImageField」という関数を使用し、先ほど設定したmediaディレクトリに保存する機能を作成していきます。

ImageFieldを使用することで、特定のディレクトリ(今回はmedia)に画像ファイルを保存し、ファイルパスをDBに保存することができます。



Djangoの場合、models.pyで作成したclassがデータベースのテーブルに該当しており、今回は「Photo」というテーブルに「image」カラムを作成します。


またアップロードした画像へのパスを取得するreturn_photo関数も作成しておきます。


from django.db import models

class Photo(models.Model):

    image = models.ImageField(upload_to='media')

    def return_photo(self):
        return self.image.url


upload_to=はMEDIA_ROOT(今回はmyproject/media)を自動で読み込みます。


upload_to='media'とすることでmyproject/media/mediaへ保存されるよう設定ができました。


models.pyを編集したので、コマンドラインで以下を実行しておきます。

python manage.py migrate



④forms.pyの作成


myproject/,myapp/forms.pyを作成し,、画像アップロードフォームを実装していきます。



今回はPhotoFormというクラスを作成していきますが、Djangoのformsクラスを継承することで簡単に作成することができます。


from django import forms

class PhotoForm(forms.Form):
    image = forms.ImageField()


⑤テンプレートファイルの作成


今回はメインとなるindex.htmlとアップロード後に表示されるresult.htmlの2種類を作成します。



index.html

<table>
  <form action="{% url 'index' %}"  method="POST" enctype="multipart/form-data">
  {% csrf_token %}
  {{ form.as_p }}
    <input type="submit" value="submit" />
  </form>
</table>

DjangoではPythonで扱っていた変数をhtmlに流用して使うことが可能です。


重要なところを順番に説明していきます。



form action="{% url 'index' %}" method="POST" enctype="multipart/form-data"

ここではformでアクションが行われた場合の操作を記載しています。

action="{% url 'index' %}" でmyapp/urls.pyの name="index"のパターンを、

method="POST"でPOSTリクエストとして呼び出す、という記載をしています。


今回の場合には「送信」ボタンが押された際に上記のリクエストを送るような処理になります。



{{ form.as_p }}

forms.pyで作成した画像アップロードフォームをレンダリングします。

とはいっても{{ form.as_p }}と記載すればフォームが表示されるわけではなく、views.pyで「forrm」という変数にforms.pyで作成した PhotoForm()を格納する処理が必要になります。

views.pyで定義をした後に、formの変数を{{form}}とテンプレートファイルに記載することでフォームを実装することができます。


ちなみに.as_pは段落として表示する場合に記載するものでhtmlの

タグと同じです。





⑥urlの設定


まずはプロジェクトレベルのURLを設定します。


myproject/,myproject/urls.py

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^myapp', include('myapp.urls')),
    url(r'^admin/', admin.site.urls),
]

次にmyproject/myapp/urls.pyを作成し、以下を記載。

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^/', views.index, name='index'),
]


これでhttp://127.0.0.1:8000/myapp/へアクセス時にmyproject/,myapp/views.pyのindex関数が呼び出されるようになりました。



⑦vies.pyの設定


リクエストがあった場合の処理を記載していきますが、この部分がかなり重要になります。


from django.shortcuts import render, redirect
from .forms import PhotoForm
from .models import Photo
from django.http import HttpResponse
from .import views



def index(request):
    if request.method == 'GET':
        return render(request, 'myapp/index.html', {
            'form': PhotoForm(),
        })

    elif request.method == 'POST':
        form = PhotoForm(request.POST, request.FILES)
        if not form.is_valid():
            raise ValueError('invalid form')

        photo = Photo()
        photo.image = form.cleaned_data['image']
        photo.save()
        input_photo = photo.return_photo()
        return render(request,'myapp/result.html')


順番に解説していきます。



最初にindex関数を作成します。今回はフォーム機能の実装なので、if文でGET・POSTそれぞれのリクエストがあった場合の処理を記載していきます。

まずGETの場合ですがこの場合は普通にindex.htmlをレンダリングしていきます。

ですがindex.htmlの中にアップロードフォームを表示する必要があります。

なので

return render(request, 'myapp/index.html', {
            'form': PhotoForm(),
        })

の部分で、index.htmlの{{ form }}と記載のある部分にforms.pyで定義したPhotoForm(),を表示するようにします。


このようにDjangoではrender関数の引数として特定の変数を指定し、html上で{{ }}で囲って使用することでPython上の変数をhtmlで引き継ぐことができます。



次にPOST時の処理を記載しています。

form = PhotoForm(request.POST, request.FILES)
        if not form.is_valid():
            raise ValueError('invalid form')

の部分でフォームに投稿されたファイルの形式をチェックしています。

今回は画像アップロードフォームなので、ファイルが画像じゃない場合にエラーを吐き出すようにしています。


photo = Photo()
photo.image = form.cleaned_data['image']
photo.save()
input_photo = photo.return_photo()

次にphoto = Photo()でmodels.pyのPhotoクラスのインスタンスを作成し、Photoクラスのimage関数ででアップロードされた画像の情報を取得しています。

またphoto.save()で取得した画像ファイルパスをデータベースに追加しています。

(input_photo = photo.return_photo()の部分は取得した画像のパス情報を格納している部分です。画像処理につなげる場合なんかに使えます。)

このようにforms.pyとmodels.pyを読み込んで様々な処理を行うことができました。



最後に

return render(request,'myapp/result.html')

でresult.htmlをレンダリングして完成です。