【Django】modelを使わない画像ファイルアップロード機能を実装する

#はじめに
Djangoアプリで画像ファイルをアップロードする場合ImageFieldを使用してDBに保存するのが定石だと思います。
しかし、先日実務で作ったアプリは他システムとの統合の都合上**DBではなく特定のフォルダにファイルを保存する必要があり**、その方法を調べるのに少し時間がかかったので記事にしておきます。

#結論
**forms.ImageField**を使用して実現可能です。

#サンプル
以下、最小構成のサンプルです。

トップページ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663118/2a73ef48-ab98-7f31-9737-3a8d59df6fa4.png)

適当な画像ファイルを選択して送信すると画面が変わります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663118/f2f5fe25-8b08-e0f0-0e2e-5c04393453c1.png)

プロジェクトのベースディレクトリを見ると「media_root」フォルダが作成されており、中にアップロードしたファイルが保存されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663118/1384e236-1439-9a7d-c770-554edc049f79.png)


画像以外のファイルをアップロードした場合エラーメッセージが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663118/32500f35-eb44-e474-556e-22baf59ab230.png)




#ソース

```forms.py
import datetime
from django import forms
from django.core.files.storage import default_storage


class FileUploadSampleForm(forms.Form):
    file = forms.ImageField()

    def save(self):
        """ファイルを保存するメソッド"""
        now_date = datetime.datetime.now().strftime('%Y%m%d%H%M%S')  # ファイル名に現在時刻を付与するため取得
        upload_file = self.files['file']  # フォームからアップロードファイルを取得
        file_name = default_storage.save(now_date + "_" + upload_file.name, upload_file)  # ファイルを保存 戻り値は実際に保存したファイル名
        return default_storage.url(file_name)
```

```views.py
from django.shortcuts import render
from django.views.generic import View
from .forms import FileUploadSampleForm


class IndexView(View):
    def get(self, request, *args, **kwargs):
        """GETリクエスト用のメソッド"""
        form = FileUploadSampleForm
        return render(request, 'fileUploadSample/index.html', {"form": form})

    def post(self, request, *args, **kwargs):
        """POSTリクエスト用のメソッド"""
        form = FileUploadSampleForm(request.POST, request.FILES)

        if not form.is_valid():
            return render(request, 'fileUploadSample/index.html', {"form": form})

        filename_save = form.save()

        context = {
            'form': form,
            'filename_save': filename_save,
        }
        return render(request, 'fileUploadSample/index.html', context)


index = IndexView.as_view()

```

```index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="{% url 'fileUploadSample:index' %}" method="post" enctype="multipart/form-data">
    {% csrf_token %}

    {#forms.ImageFieldを表示#}
    {{ form.file }}
    <br><br>

    {#エラーメッセージを表示#}
    {% for field in form %}
        {% if form.errors %}
            <font color=#CC3300>{{ field.errors }}</font><br>
        {% endif %}
    {% endfor %}

    {#保存されたファイル名を表示#}
    {% if filename_save %}
        ファイル名 {{ filename_save }} で保存されました。
        <br><br>
    {% endif %}

    <input type="submit" value="送信"/>
</form>
</body>
</html>
```

```urls.py
from django.urls import path
from . import views

app_name = 'fileUploadSample'
urlpatterns = [
    path('', views.index, name='index'),
]
```

settings.pyには以下を追記しておいてください。

```settings.py
MEDIA_ROOT = os.path.join(BASE_DIR, 'media_root')
```
　　
　
**forms.FileField**を使用することで画像以外のファイルのアップロードに対応することもできます（画像ファイルで無い場合もform.is_valid()でエラーが出なくなる）。

以上です。お疲れ様でした:cat2: