Django でファイルのアップロードを処理する

World Wide Web は、ネットワークに接続されたコンピュータ間で膨大な量のデータの転送を容易にし、データを大量に作成し共有するコミュニティとなった。

このデータは様々な形態や形をとるが、人間が解釈できる一般的な形式としては、画像、動画、音声ファイルなどがある。

ユーザーは、さまざまなソフトウェアでファイル共有に慣れているため、目新しさはなく、その機能は標準的であると考えられていることが多い。

ということです。

このガイドでは、Pythonを使って、DjangoベースのWebアプリケーションにファイルをアップロードする方法を見ていきます。

アップロードされたファイルは、様々な形で追加処理されたり、そのままの状態で残されたりすることがあります。

ファイルをアップロードすると、保存 (ファイルが最終的に置かれる場所) や表示 (どのように取得し表示するか) の問題が発生します。

このガイドでは、これらの問題を考慮しながら、ユーザが Django の Web アプリケーションにファイルをアップロードする機能を提供する、小さなプロジェクトを構築します。

プロジェクトセットアップ

>
Djangoのファイルアップロード、保存、表示機能をデータベースで実装する小さなプロジェクトを構築する予定です。

私たちがハリー・ポッターの本の魔法生物と一緒に暮らす想像上の宇宙に住んでいて、私たちの世界の魔法動物学者が、彼らが研究するそれぞれの魔法生物に関する情報を追跡するためのアプリケーションを必要としていると仮定してみましょう。

そして、そのフォームをレンダリングし、情報を保存して、必要なときにユーザーに表示します。

というものです。

Django と django-admin などのモジュールに慣れていない場合は、 Django の基本的な要素を網羅した REST API の作成に関する一般的なガイドを読むとよいでしょう。

このガイドでは、Django の基本的な知識を前提に、セットアップのプロセスを手早く説明しますが、プロジェクトの作成プロセスをより深く理解したい場合は、 Python で Django を使って REST API を作成するガイドを読んでください
>
>
まず、仮想環境を作成します。

まず、仮想環境を作成して、依存関係が他のプロジェクトとバージョンの不一致を起こすのを防ぎます。

このステップはオプションですが、強く推奨され、 Python の環境をきれいに保つためのグッドプラクティスと考えられています。

環境のコンテナとして機能するディレクトリを作成しましょう。

コマンドプロンプト/シェルを開き、先ほど作成したディレクトリの中で、以下を実行してください。

$ mkdir fileupload
$ cd fileupload
$ python -m venv ./myenv
# OR
$ python3 -m venv ./myenv


これで仮想環境が作成されました。

あとは activate スクリプトを実行して、仮想環境を有効にするだけです。

# Windows
$ myenv/Scripts/activate.bat
# Linux
$ source myenv/Scripts/activate
# MacOS
$ source env/bin/activate


一旦環境が有効になると、依存関係をインストールしても、その環境のみに適用され、他の環境、あるいはシステム環境と衝突することはありません。

ここでは、 pip 経由で Django をインストールすることができます。

$ pip install "Django==3.0.*"


では、 django-admin モジュールの startproject コマンドを使って、 fantasticbeasts という名前のプロジェクトを作成してみましょう。

プロジェクトのスケルトンが作成されたら、そのディレクトリに移動して、 startapp コマンドでアプリを起動します。

$ django-admin startproject fantasticbeasts
$ cd fantasticbeasts
$ django-admin startapp beasts


最後に、このアプリを fantasticbeasts/settings.py ファイルに登録し、INSTALLED_APPS のリストに追加しましょう。

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


すごい これで準備は完了です。

Beast`のシンプルなモデルを定義して、エンドユーザーに表示するためのフォームとテンプレートを作成し、フォームから送信されるファイルを処理することができます。

Django でファイルをアップロードする

モデルの作成

まず最初に、データベースのテーブルに直接マッチする Beast というモデルを定義しましょう。

そして、このモデルの白紙の状態を表すフォームを作成し、ユーザーが詳細を記入できるようにします。

beasts/models.pyファイルでは、models.Model` クラスを継承したモデルを定義し、データベースに保存する機能を継承しています。

from django.db import models


class Beast(models.Model):
    MOM_CLASSIFICATIONS = [
    ('XXXXX', 'Known Wizard  Killer'),
    ('XXXX', 'Dangerous'),
    ('XXX', 'Competent wizard should cope'),
    ('XX', 'Harmless'),
    ('X', 'Boring'),
 ]
    name = models.CharField(max_length=60)
    mom_classification = models.CharField(max_length=5, choices=MOM_CLASSIFICATIONS)
    description = models.TextField()
    media = models.FileField(null=True, blank=True)


各獣は name, description, media (獣の目撃情報) と mom_classification (M.O.M は Ministry of Magic の略) を持っています。

mediaFileFieldのインスタンスで、null引数をTrueに設定して初期化されたものです。

この初期化により、データを入力するユーザーが添付するメディアを持っていない場合、mediaフィールドが NULL であっても問題ないことをデータベースに知らせることができます。

このモデルをフォームにマッピングし、Django がバリデーションを行うので、フォームのmediaへの入力が空白でも良いことを Django に知らせ、バリデーションの際に例外を発生させないようにする必要があります。

null はデータベースを参照し、 blank はユーザ側の検証を参照します。

一般的には、一貫性を保つために、この2つを同じ値に設定したいでしょう。

注意: もし、ユーザーによるメディアの追加を強制したい場合は、これらの引数を False に設定してください。

デフォルトの FileField は1つのファイルのみを処理し、ユーザーがファイルシステムから1つのアイテムをアップロードすることを許可します。

後のセクションでは、複数のファイルをアップロードする方法について見ていきます。

モデルフォームの作成

モデルを定義したら、それをフォームにバインドします。

Django がこの機能をブートストラップしてくれるので、フロントエンドでこれを手動で行う必要はありません。

from django.forms import ModelForm
from .models import Beast


class BeastForm(ModelForm):
    class Meta: 
        model = Beast
        fields = '__all__'


BeastFormを作成し、Beastモデルをそれにバインドしました。

また、fieldsall` に設定し、 HTML ページで使用する際にモデルの全てのフィールドが表示されるようにしました。

フィールドの一部を非表示にしたい場合は、ここでフィールドを個別に調整することができますが、今回のシンプルなモデルでは、すべてを表示することにします。

Adminにモデルを登録する

Django は、開発者が開発プロセスを通じて使用するための admin サイトを自動的に作成します。

ここで、自分たちでページをスピンアップすることなく、モデルやフィールドをテストすることができます。

しかし、ユーザに対しては、ユーザを作成し、本番前に admin サイトを無効にしたいでしょう。

モデルを beasts/admin.py ファイルに追加して、管理者用ウェブサイトに登録しましょう。

from django.contrib import admin
from .models import Beast


admin.site.register(Beast)


URLパスの登録

アプリケーションの構造、モデルの定義と登録、フォームへのバインドが完了したので、ユーザーがこのアプリケーションを使用できるようにするための URL パスを設定しましょう。

これを行うには、アプリ内に urls.py ファイルを作成します。

そして、その内容をプロジェクトレベルの urls.py ファイルに “include” します。

私たちの beasts/urls.py はこのようなものになります。

from django.urls import path
from .import views


urlpatterns = [
    path("", views.addbeast,  name='addbeast')
 ]


そして、プロジェクトレベルの[urls.py]には、次のように追加されます。

urlpatterns = [
    path("", include("reviews.urls"))
]


URLに空の文字列を追加しているのは、これがポケットサイズのプロジェクトであり、複雑にする必要がないためです。

ビューはまだ作成していませんが、作成前にここで名前を登録しておきます。

次にHTMLテンプレートとviews.addbeastビューを作成しましょう。

フォームを表示するテンプレートの作成

テンプレートを保存するために、 beasts ディレクトリの下に templates フォルダを作りましょう。

Django は HTML テンプレートを templates という名前のフォルダの下でのみ探すので、この名前は譲れません。

新しいフォルダの中に entry.html ファイルを追加しましょう。

このファイルには <form> があり、 Beast に関連するフィールドを受け取ります。

<!DOCTYPE html>

<html lang="en">
<head>
<meta charset="utf-8"/>
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Fantastic Beasts</title>
</head>
<body>
<form action="/" enctype="multipart/form-data" method="POST">
        {% csrf_token %}
        {% for entry in form %}
           <div>
                {{ entry.label_tag }}
           </div>
<div>
               {{entry}}
           </div>
        {% endfor %}
        <button>
            Save!
        </button>
</form>
</body>
</html>


action=”/”属性は、ユーザーが "Save!"ボタンを選択したときに呼び出されるリクエストハンドラを示しています。

フォームの入力は、データをどのようにエンコードするかを決定します。

そこで、ファイルのアップロードを可能にするために、enctypemultipart/form-data型に設定しました。

Django のフォームに”file”型の入力を追加するときは、必ずenctypemultipart/form-data` に設定しなければなりません。

csrf_token %}action = “POST”を持つフォームには必須です。

これは Django が親切にも各クライアントに対して作成するユニークなトークンで、 リクエストを受け付ける際のセキュリティを確保するためのものです。

CSRF トークンは、このフォームからの全てのPOST` リクエストに対して一意であり、CSRF 攻撃を不可能にするものです。

>
CSRF攻撃は、悪意のあるユーザが他のユーザの代わりに、通常は別のドメインを通してリクエストを偽造し、有効なトークンがない場合、サーバへのリクエストを拒否するものです。

for each ループ ({% for entry in form %}) で繰り返し使用している form 変数は、ビューからこの HTML テンプレートに渡されます。

この変数は BeastForm のインスタンスで、いくつかのクールなトリックを備えています。

また、フォームフィールドを div でラップして、フォームの見栄えを良くしています。

テンプレートをレンダリングするためのビューを作成する

では、このテンプレートをレンダリングするビューを作成し、バックエンドに接続してみましょう。

まず最初に、 renderHttpResponseRedirect クラスをインポートします。

どちらも Django の組み込みクラスで、 BeastForm オブジェクトと一緒に使います。

>
クラスベースのビューの代わりに、関数ベースのビューを作成することができます。

受信したリクエストが POST リクエストの場合、新しい BeastForm インスタンスが POST リクエストのボディ (フィールド) とリクエストで送られたファイルによって作成されます。

Django は自動的にボディデータをオブジェクトにデシリアライズし、 request.FILES をファイルフィールドとしてインジェクションします。

from django.shortcuts import render
from .forms import BeastForm
from django.http import HttpResponseRedirect


def entry(request):
    if request.method == 'POST':
        form = BeastForm(request.POST, request.FILES)

        if form.is_valid():
            form.save()
            return HttpResponseRedirect("/") 
    else:
        form = BeastForm()


return render(request, "entry.html", {
        "form": form
    })


入力が無効かもしれないので、それを検証するために、 BeastForm インスタンスの is_valid() メソッドを使い、入力が無効な場合はフォームをクリアすることができます。

そうでない場合は、フォームが有効であれば save() メソッドでデータベースに保存し、ユーザーをホームページ (entry.html ページでもある) にリダイレクトして、別の獣の情報を入力するよう促します。

>
ファイルはどこにあるのですか?
>
>
&gt:ファイルはどこですか?

注:この方法では、ファイルはデータベースに保存され、ファイル処理は必要ありません。

次のセクションでは、適切なファイル操作システムでこれを修正します。

とりあえず、マイグレーションを行い、モデルスキーマの変更をコミットするためにマイグレーションを行いましょう(これまで行っていなかったので)。

プロジェクトを実行したら、開発サーバー上でこれらすべてがどのように見えるかを確認できます。

ターミナルから、仮想環境がまだアクティブな状態で、実行してください。

$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py runserver


ブラウザで http://127.0.0.1:8000/ を押すと、次のような画面が表示されるはずです。

フィールド名を “media” としましたが、汎用的な FileField を割り当てたので、どんなファイルタイプでもかまいません。

注意: Django を使って、画像などの特定のファイルタイプを強制することができます。

フォームを送信した後、管理ページからデータベース上のデータを確認することができます!

データベースではなく、HDDにファイルを保存する場合

現時点では、我々のコードはファイルをデータベースに格納することが可能です。

しかし、これは望ましいやり方ではありません。

時間とともにデータベースは “肥大化 “し、速度が低下します。

画像は、ここしばらくの間、データベースにblobとして保存されておらず、通常、アプリケーションをホストしている自分のサーバーや、AWSのS3などの外部サーバーやサービスに画像を保存することになります。

>
AWS S3などのサービスへのファイルのアップロードについてもっと知りたい方は、 Python with DjangoでAWS S3にファイルをアップロードするガイドをご覧ください。

をご覧ください。

>
では、どのようにファイルを保存するか見てみましょう。

アップロードされたファイルを、プロジェクトの下にある小さなフォルダに保存する方法を見てみましょう。

アップロードされたファイルを格納するために、 beasts の下に uploads フォルダを追加し、 BeastForm のメディアフィールドをデータベースではなく、フォルダを指すように変更しましょう。

media = models.FileField(upload_to="media", null=True, blank=True)


FileFieldのターゲットフォルダを“media”に設定しましたが、このフォルダはまだ存在しません。

おそらく他のファイルもアップロードできるでしょうから、uploadsフォルダには“media”` というサブディレクトリを作り、ユーザが獣の画像をアップロードできるようにします。

この "media" ディレクトリがどこにあるかを Django に知らせるために、 settings.py ファイルにこのディレクトリを追加します。

MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads/')


os.path.join(BASE_DIR, 'uploads/')"/uploads"BASE_DIR – プロジェクトフォルダへの絶対パスを保持する組み込み変数 に追加しています。

MEDIA_ROOT` は Django に、私たちのファイルがどこにあるのかを教えてくれます。

全ての変更を保存して、移行を適用すると、 Django は uploads の下に [upload_to="media"] のように "media" という名前のフォルダを作成します。

投稿されたファイルは全てそのフォルダに保存されます。

Django で複数のファイルをアップロードする

複数のファイルのアップロードを処理するために必要な追加作業はあまりありません。

必要なのは、メディアフィールドが複数の入力を受け取っても良いことを、モデルのフォームに知らせることだけです。

これを行うには、 BeastFormwidgets フィールドを追加します。

from django.forms import ModelForm, ClearableFileInput
from .models import Beast


class BeastForm(ModelForm):
    class Meta: 
        model = Beast
        fields = '__all__'
        widgets = {
            'media': ClearableFileInput(attrs={'multiple': True})
        }


これで entry.html ページでは、ユーザが複数のファイルを選択できるようになり、 request.FILES プロパティには1つではなく複数のファイルが格納されるようになりました。

ImageField を使った Django での画像ファイルの強制実行

Django は追加のフィールドタイプ、 ImageField を定義しており、ユーザ入力を画像ファイルに限定することができます。

私たちは獣のドキュメント用に様々な種類のファイルを集めましたが、私たちのアプリケー ションでは、ユーザに一つの特定のファイル入力を求めることが多くあります。

そこで、 FileFieldImageField を入れ替えてみましょう。

media = models.ImageField(upload_to="media", null=True, blank=True,)


ImageField` は Pillow images にあります。

これは画像を処理するために広く使われている Python ライブラリで、まだインストールしていない場合は例外が発生します。

Cannot use ImageField because Pillow is not installed.
        HINT: Get Pillow at https://pypi.org/project/Pillow/ or run command "python -m pip install Pillow".


ターミナルのアドバイスにしたがって進めてみましょう。

サーバーを一旦終了して実行します。

$ python -m pip install Pillow


さて、先に進んでマイグレーションを作成・適用し、開発用サーバーを実行すると、ファイルをアップロードしようとしたときに、選択肢が画像に限定されていることがわかります。

アップロードされた画像を表示する

We are very close to the finish line. Let’s see how we can retrieve and display our stored images and call it a day.

Go ahead and open your beasts/views.py file. We will change our if clause so that when a form is submitted successfully, the view does not reload the page but, instead it redirects us to another one, which will contain a list of all beasts and their information, alongside their associated image:

 if form.is_valid():
      form.save()
      return HttpResponseRedirect("/success")


Now let’s go ahead and create a view to render the success page. Inside our beasts/views.py file, insert:

def success(request):
    beasts = Beast.objects.order_by('name')
    return render(request, "success.html", {
        "beasts": beasts
    })


On our “success” page, we will list the names and images of the beasts in our database. To do that, we simply collect the Beast objects, order them by their name and render them in the success.html template:

<!DOCTYPE html>

<html lang="en">
<head>
<meta charset="utf-8"/>
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Fantastic Beasts</title>
</head>
<body>
    {% for beast in beasts %}
       <div>
            {{ beast.name }}
       </div>
       {% if beast.media %}
        <div>
<img alt="" height="auto" src="{{ beast.media.url }}" width="500"/>
</div>
       {% endif %}
    {% endfor %}   
</body>
</html>


We have already mentioned that the database’s job is not to store files, it’s job is storing the paths to those files. Any instance of FileField or ImageField will have a URL attribute pointing to the file’s location in the file system. In an <img/> tag, we feed this attribute to the src attribute to display the images for our beasts.

By default, Django’s security kicks in to stop us from serving any files from the project to the outside, which is a welcome security-check. しかし、私たちは "media" ファイルにあるファイルを公開したいので、メディアURLを定義して urls.py ファイルに追加する必要があります。

settings.pyファイルに、MEDIA_URL`を追加します。

MEDIA_URL = "/beast-media/"


ここで、/name-between/ は、引用符とスラッシュで囲む必要がありますが、何でもかまいません。

次に、プロジェクトレベルの urls.py ファイルを修正して、静的ファイルを提供する static フォルダを追加します。

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("", include("ency.urls"))
]  + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)


static()関数は、MEDIA_URLを、ファイルが存在する実際のパスMEDIA_ROOTにマップします。

これは、FileFieldImageFieldインスタンスの [url`] 属性に自動的に付加されます。

変更を保存して開発用サーバーにアクセスすると、すべてがスムーズに動作することが確認できます。

注意: このメソッドは開発環境でのみ使用でき、MEDIA_URLがローカルの場合のみ使用可能です。

>
> もしファイルをHDDに保存するのではなく、別のサービスに保存するのであれば、Django, AWS S3, WhiteNoiseを使ったPythonでの静的ファイル提供のガイドを読んでください!
をご覧ください。

結論

このガイドでは、Django でファイルをアップロードし、ファイルを保存し、最後に ファイルを提供する方法について説明しました。

私たちは、教育目的以外にはあまり役に立たない、小さなアプリケーションを作成しました。

しかし、ファイルアップロードの実験を始めるための頑丈な足がかりにはなるはずです。

</form

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