Djangoで画像をアップロード、そして表示　躓いたポイントと手順を解説

# はじめに
Djangoで作ったWebアプリに画像をアップロードし、アップロードした画像を表示する機能を実装したい!と思い、いろんな記事を調べ実装したがアップロードした画像が表示されない...という問題に直面した。そのときの僕のミスを紹介した後、基礎的な手順を解説します。手順が分かっている人は冒頭のミス紹介を、これからアップロード・表示方法を学びたい人は下の手順からご覧ください。
<font color="red">※</font>コードの挙動も書いていくため長い記事になります。コードの部分だけで実装可能なので適宜読み飛ばしてください。

# 1.僕のミス
ここでは端的にアップロードした画像が表示されなかった理由を紹介
```python:urls.py
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
```
をアプリケーションルーティングに記述してしまっていた。Djangoのプロジェクトのルーティングは、project_name/urls.pyに定義されているurlpatternsを基点として動作します。もしアプリケーションのルーティングに記述してしまうと、DjangoはメディアファイルにアクセスするためのURLパターンを正しく解釈できず、ファイルが見つからない（404エラー）という状態になります。

![スクリーンショット (323).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3896939/fd19cca1-74db-a4f1-a65e-c4048681518a.png)
正常にルーティング設定ができていれば、media/test.pngとurlを指定すれば画像が表示される。エラー文中間の
1.admin/
2.app/
からアプリケーションルーティングではなく、プロジェクトのルーティングを参照していることがわかる。
# 2.画像アップロード・表示の手順
### ライブラリのインストール
プロジェクトやアプリケーションは作成してある前提で始めます。まずは、Djangoで画像ファイルを扱うためにはPillowという画像処理ライブラリが必要になるので，まだインストールしていない場合は，下記のコマンドでンストールしましょう。
``` 
$ pip install pillow
```
### アップロードした画像を保存するフォルダを作成
ルートディレクトリ(manage.pyやtemplatesが含まれるディレクトリ)に画像を保存するフォルダ(ここではmediaという名前にする)を作成する。
```
$ cd myproject
$ mkdir media
```
のようにコマンドを実行するか、マウス等を用いたGUI操作によって作成。
### settings.pyでの設定
settings.pyに下記を追加します。
```python:settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / "media"
```
"MEDIA_URL"はメディアファイルにアクセスするためのURLパスです。ユーザーがブラウザでアップロードされたメディアファイルにアクセスする際に使用されます。
"MEDIA_ROOT"はメディアファイルを保存するディレクトリのパスです。サーバー上でアップロードされたファイルが格納される場所を指定します。BASE_DIR / "media_local"はBASE_DIRのmedia_localという名前のフォルダを指します。通常、BASE_DIRはルートディレクトリと同じものを指し、manage.pyやtemplatesが含まれるディレクトリを指します。
#### ◆注意点
* MEDIA_ROOT = BASE_DIR / "media"のmediaは先ほどルートディレクトリに作成したフォルダ名と同じにする
* MEDIA_URLのmediaとMEDIA_ROOTのmediaは同じ名前だが直接的には関係ない(詳細は次のルーティング設定で解説)。
### ブラウザから画像にアクセスするためのルーティング設定
プロジェクト作成時に自動生成されるurls.pyに下記を記述する。
```python: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('admin/', admin.site.urls),
    path('app/',include("yourapp.urls")),　#この行は自分のアプリケーションに応じて変更
]
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
```
#### ◆コードの挙動
* if settings.DEBUG:に関して、settings.pyのDEBUGがTRUEである場合に実行される。Djangoでは通常、開発環境ではDEBUG=TRUEとし、本番環境ではDEBUG=FALSEと設定する。開発環境で動作させたいだけの場合は不要だが、デプロイするときに追加するのを忘れないためにあらかじめ記述するほうが良い。
* static()関数の動作、MEDIA_URL で始まるリクエストが来た場合、そのリクエストが指すファイルを MEDIA_ROOT に指定されたディレクトリから探して返すルールを作成する。たとえばMEDIA_URL (/media/) で始まるリクエストが来た場合、そのファイルを MEDIA_ROOT (BASE_DIR/media/) に保存されているファイルから探してレスポンスを返すようになります。
### 画像アップロードのためのモデル作成
models.pyに下記を記述します。
```python:models.py
from django.db import models

class MediaUploadModel(models.Model):
    title = models.CharField(max_length=50)
    timestamp = models.DateField(auto_now_add=True)
    memo = models.TextField(blank=True)
    img = models.ImageField()
     
    def __str__(self):
        return self.title
```
ImageFieldの引数にupload_toを設定しなければMEDIA_ROOTに画像が保存される。
### フォームの作成
forms.pyに下記を記述します。
```python:models.py
from django import forms 
from .models import MediaUploadModel

class MediaUploadForm(forms.ModelForm):
    class Meta:
        model = MediaUploadModel
        fields = "__all__"
```
### ビューを作成
views.pyに以下を記述します。
```python:views.py
from django.views.generic import CreateView
from django.urls import reverse_lazy
from .forms import MediaUploadForm

class MediaUploadView(CreateView):
    template_name = "mediaupload.html"
    form_class = MediaUploadForm
    success_url = reverse_lazy("teethapp:home")
```
画像のアップロードが正常に行われると、success_urlで指定したurlに遷移する。
### フォームを表示するテンプレートを作成
viewa.pyにtemplate_name = "mediaupload.html"と記述しているのでhtmlファイル名はmediaupload.htmlにする
```html:mediaupload.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <form method="POST" enctype="multipart/form-data">{% csrf_token %}
    {{ form.as_p }}
    <button type="submit">送信</button>
  </form> 
</body>
</html>
```
#### ◆enctype="multipart/form-data"
ファイルを含むデータを送信するためのエンコーディングタイプです。フォームのデータは、テキストデータとファイルデータが混在している場合でも、各部分が適切に分割され、サーバーに送信されます。
### ビューを呼び出すためのルーティング設定
urls.pyに以下を記述する
```python:urls.py
from django.urls import path
from .views import MediaUploadView

app_name = 'teethapp'

urlpatterns = [
    path("mediaupload/",MediaUploadView.as_view(),name="mediaupload"),
]
```
#### ◆app_name = 'yourapp'とname="mediaupload"
app_nameは、DjangoのURL設定で使用される名前空間を指定するためのオプションで,URLを逆引きするときにアプリ名を指定することで、どのアプリケーションのURLかを明確にすることができます。
#### ◆URLの逆引き
```html:example.html
<a href="{% url 'yourapp:mediaupload' %}">画像をアップロード</a>
```
とhtmlファイルに記述し画像をアップロードというアンカーを押すと、画像アップロードのためのview(MediaUploadView)を呼びだすためのurl(mediaupload/)が呼び出される。
### フォームの送信
mediaupload/にアクセスするとフォームが表示される。実際に画像を選択して送信を押し、正常にアップロードできていれば、選択した画像がmediaフォルダ(MEDIA_ROOT)に保存されているはずである。

### アップロードされた画像を表示する
画像のアップロード機能は完了したので、ここからはアップロードされた画像を画面に表示する機能に取り掛かる。
### 画像を表示するためのテンプレートを作成
画像を表示するためのhome.htmlをtemplatesフォルダの中に作成する。home.htmlを表示するためのurls.pyやviews.pyの記述は省略する。