React × DRFで画像アップロード機能作成 (バックエンド実装編)
前回までで、環境構築・フロントエンド側の実装が完了したので、続いてバックエンド側の実装に移りたいと思います。
前回までの記事
実行環境
- Mac OS X 11.2.1
- Docker 20.10.5
- docker-compose version 1.28.5
- node v15.10.0
- npm 7.9.0
- react 17.2.0
コンテナに入る
今回は、server用のコンテナに入って作業をしていきます。下記コマンドを実行して、serverコンテナに入ります。
$ docker exec -it server bash
Django Projectの作成
コンテナに入ったら、まず初めにDjangoのProjectを作成していきます。下記コマンドで、Django Projectを作成します。
$ django-admin startproject server
上記コマンドを実行すると下記のようなファイルたちが生成されます。
server/
manage.py
server/
__init__.py
settings.py
urls.py
asgi.py
wsgi.py
Django Appの作成
Projectの作成が完了したら、続いてはAppの作成をしていきます。
下記コマンドで、Django Appを作成します。
$ python manage.py startapp api
上記コマンドを実行することで、apiというディレクトリを作成されます。
server/
manage.py
server/
__init__.py
settings.py
urls.py
asgi.py
wsgi.py
polls/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
views.py
Djangoの開発サーバで確認してみる
正常にDjangoのファイルたちが作成されているか確認するために、Djangoが用意している開発サーバを立てて、確認をしてみます。下記コマンドを実行して、サーバを立てます。
$ python manage.py runserver 0.0.0.0:8000
サーバが立ち上がったら、ブラウザからhttp://localhost:8000/へアクセスします。
下記画面が表示されたら、確認は完了です。

ここまでの手順は、Djangoの公式サイトのチュートリアルに沿った形で実行しています。各コマンドの詳細が気になる方は、公式サイトを確認してみてください。
実装
設定ファイルの修正
早速実装に移っていこうと思いますが、その前に実装を始める前の準備段階として設定ファイル等を修正していきます。
/server/server/settings.pyをエディターで開きます。
ALLOWED_HOSTS = ['*'] # 修正
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework', # 追記
'api.apps.ApiConfig', # 追記
'corsheaders', # 追記
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware' # 追記
]
# 追記
CORS_ORIGIN_WHITELIST = (
'http://localhost:3000',
)
LANGUAGE_CODE = 'ja' # 修正
TIME_ZONE = 'Asia/Tokyo' # 修正
# 追記
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
上記のようにsettings.pyを修正します。
※関係のない部分は省略しています。
mediaディレクトリの用意
上記のsettings.pyでMEDIA_URLとMEDIA_ROOTを定義しました。
こちらは、画像の保存先を指定しており、画像が保存されると指定したmediaディレクトリ内に保存しようとします。そのため、事前に空のmediaディレクトリを用意しておいてください。
ルーティングの設定
続いて、ルーティングの設定を行っていきます。まず、/server/server/urls.pyをエディターで開きます。
from django.contrib import admin
from django.urls import path, include
from django.contrib.staticfiles.urls import static
from . import settings
urlpatterns = [
path('api/', include('api.urls')),
path('admin/', admin.site.urls),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
次に、前回作成したapiアプリ内にurls.pyを作成して下記コードを記述します。
from django.db import router
from django.urls import path, include
from . import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
urlpatterns = [
path('image/', views.ImageFileView.as_view(), name='images'),
path('', include(router.urls)),
]
これでルーティングの設定が完了しました。
流れとしては、example.com/api/imageというURLがあったとして、はじめに、server/urls.pyをみて、api/ というパスが存在するので、includeしてあるapi/urls.pyに飛びます。
そして、api/urls.pyでは、image/ というパスに対して、ImageFileViewというviewの処理へ促すようルーティングしています。
Viewの実装
先程のルーティングでimage/ に対してのルーティングが完了したので、このままimage/ が来たときのViewの処理を実装します。
/server/api/views.pyを編集します。
from django.shortcuts import render
from rest_framework import status, generics, permissions
from .models import *
from .serializers import *
class ImageFileView (generics.ListCreateAPIView):
permission_classes = (permissions.AllowAny,)
queryset = UploadFile.objects.all()
serializer_class = UploadFileSerializer
ここでは、Django REST frameworkのgenerics.ListCreateAPIViewを使用してViewの処理を記述しています。
こちらのViewは、HTTPメソッドがGET or POST で来た場合、GET なら該当Modelのデータを返却し、POST なら、該当Modelにデータを登録してくれます。
詳しい仕様は、公式ドキュメントを参考にしてみてください。
該当Modelとは、後述するUploadFileModelになります。
Modelの定義
Viewの処理の実装が完了したので、続いてModelの定義を行っていきます。
from django.db import models
import os
def upload_image (instance, filename):
return 'images/{0}/'.format(filename)
class UploadFile (models.Model):
file = models.ImageField('画像ファイル', upload_to=upload_image)
def __str__ (self):
return self.file.url
@property
def filename (self):
return os.path.basename(self.file.name)
ここでは、「file」名前のという画像専用のフィールドを用意しています。
upload_toには、データ登録時の保存先のパスを指定しています。
@propertyにて、新たに当Modelにプロパティを追加することで、画像名の取得を容易にしています。
Serializerの定義
続いて、Serializerを定義していきます。Serializerは、データの入出力を扱い、それをモデルに渡してくれるためのものです。
from rest_framework import serializers
from .models import *
class UploadFileSerializer (serializers.ModelSerializer):
file = serializers.ImageField()
file_name = serializers.SerializerMethodField()
class Meta:
model = UploadFile
fields = '__all__'
def get_file_name (self, obj):
return obj.filename
migrationとmigrate
ここまでの実装が完了したら、Modelの変更点等を反映するために、一度マイグレーションとマイグレートを行います。
$ python manage.py makemigrations api
$ python manage.py migrate
動作確認
ここまでで、すべての実装は完了です。動作確認をするため、client、server両方のサーバを起動して、実際に画面からファイルをUploadしてみます。
無事にUploadが完了したら、server/media/imageディレクトリ内に、アップロードした画像が保存されていることが確認できるかと思います。
ディレクトリ構成
ここまでの作業が完了したら、下記のようなディレクトリ構成になっているかと思います。
├── client
│ ├── Dockerfile
│ └── img_uploader
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── src
│ │ ├── App.css
│ │ ├── App.test.tsx
│ │ ├── App.tsx
│ │ ├── assets
│ │ │ └── image
│ │ │ └── upload_bg.jpg
│ │ ├── index.css
│ │ ├── index.tsx
│ │ ├── react-app-env.d.ts
│ │ ├── reportWebVitals.ts
│ │ └── setupTests.ts
│ ├── tsconfig.json
│ └── yarn.lock
├── docker-compose.yml
└── server
├── api
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── serializers.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── db.sqlite3
├── manage.py
├── media
│ └── images
│ └── back_vOylmoT.jpg # アップロードされた画像ファイル
└── server
├── __init__.py
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py
実際のコードはこちらになります。
https://github.com/yoshihiko555/img-uploader
まとめ
今回は全3回に渡って、React × DRFを用いた簡単な画像アップロードアプリを作成してみました。拙い文章が多い中最後まで読んでくださった方には感謝です。
Django REST frameworkを用いることで、非常に簡単にRESTAPIの作成が可能です。Reactに関してはまだまだ、スキル不足な部分が多いので、もっと邁進していきたいと感じました。
Comments