[メモ] Django RESTful API の POST で画像データを Upload する



|本シリーズのトップページ|
|:--|
|https://qiita.com/robozushi10/items/4559a281d0319eb62c6c|

# はじめに

CI による自動テストの中で、テスト結果の証拠としてキャプチャした画像(PNG)を
REST **POST** を使って WEBアプリに画像を Upload する機構を作成したときの抜粋である.


## ファイル構成

ここでは、次のようなファイル構成で記述している.

```terminal
home
|-- foo
    |-- mysite ................... Django プロジェクト
        |-- myapp ................ Django アプリ
        |   |-- models.py
        |   略
        |-- apiv1 ................ DRF
        |   |-- serializers.py
        |   |-- views.py
        |   |-- urls.py
        略
        |-- config
            |-- settings.py
            |-- urls.py
            |-- wsgi.py
            略
```

## myapp/models.py

次の点に留意して、モデル「`XXXX`」を実装する.

- `imgfile = models.ImageField(upload_to=メソッド)` を定義する. (下記💥)

- 上記 `upload_to` で指定されたメソッドを XXXX クラス内で実装する (下記🛑)

- フィールド「`imgfile`」(下記💥) に設定された値が、
 `get_upload_path`関数(下記🛑) の引数 `filename` に設定されている.

- `get_upload_path`関数内(下記🛑)で「`XXXX`」クラスの
 「restkey」「scenario」「testcase」を組み合わせて Upload 先のパスを組み立てている.


```python
class XXXX(models.Model):
  """ テスト結果保持 TBL """
  def 🛑get_upload_path(self, filename):
    k = self.restkey
    s = self.scenario if self.scenario else 'scenario'
    t = self.testcase if self.testcase else 'testcase'
    return '/'.join(['./images', k, s, t, filename])
  restkey   = models.CharField(verbose_name='RESTful APIキー', max_length=64)
  scenario  = models.CharField(verbose_name='シナリオ名', null=True, blank=True, max_length=40)
  testcase  = models.CharField(verbose_name='テストケース名', null=True, blank=True, max_length=64)
💥imgfile   = models.ImageField(upload_to=🛑get_upload_path, null=True, blank=True)

```

## apiv1/serializers.py

上記モデル「XXXX」に対する DRF シリアライザである.

「XXXX」クラスの「`imgfile`」のみ、別途`serializers.ImageField()`を使って定義する.(下記💚)

```python
from rest_framework import serializers
from XXXX.models import *

class XXXXSerializer(serializers.ModelSerializer):
  """ テスト結果保持 TBL """
 imgfile = serializers.ImageField(use_url=True)  💚
  class Meta:
    model  = XXXX
    fields = '__all__'  
```

### 参考にした記事

|内容|URL|
|:--|:--|
|REST によるファイルのアップロード方法|https://stackoverflow.com/questions/45559471/django-rest-framework-upload-image-api|

　

## config/urls.py

ルーティングを定義する.

エンドポイントとして「`api/v1/`」が指定された場合は、
後述の`api/v1/urls.py` でルーティングさせる.

```python
from django.contrib import admin
from django.urls import path
from django.urls import include
from django.conf.urls import url, include
from django.views.generic import TemplateView
from django.views.generic import RedirectView
from apiv2 import views

urlpatterns = [
  path('api/v1/', include('apiv1.urls')),
]
```

## apiv1/urls.py

DRF に関するルーティングを定義する.
下記🏷️と📛の詳細については後述している.

```python
from django.urls import path, include
from rest_framework import routers
from . import views
 
urlpatterns = [
  path('', include(router.urls)),
  path('myapp/XXXX/',      🏷️views.XXXXListCreateAPIView.as_view()),
  path('myapp/XXXX/<pk>/', 📛views.XXXXRetrieveUpdateDestroyAPIView.as_view()),
]
```

## apiv1/views.py

前述の 🏷️ と 📛 のクラスの実装である.
次の 🏷️ と 📛 のクラスに対して、それぞれ次の 1行以外を追加した以外は特殊な記述は無い (はず)

**・`parser_classes = (MultiPartParser, FormParser) #! 🎰引用`**


```python
from rest_framework import generics
from rest_framework import status
from rest_framework import views  #! 📘引用
from django.shortcuts import get_object_or_404  #! 📘引用
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response

from rest_framework.parsers import MultiPartParser, FormParser #! 🎰引用
from rest_framework.generics import ListAPIView 

from myapp.models import *
from .serializers import *
 
class XXXXListCreateAPIView(views.APIView):🏷️
  """ XXXX の取得(一覧)・登録 API クラス """
  parser_classes = (MultiPartParser, FormParser) #! 🎰引用

  def get(self, request, *args, **kwargs):
    l = XXXX.objects.all()
    ser = XXXXSerializer(instance=l, many=True)
    return Response(ser.data, status.HTTP_200_OK)

  def post(self, request, *args, **kwargs):
    ser = XXXXSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    ser.save()
    return Response(ser.data, status.HTTP_201_CREATED)


class XXXXRetrieveUpdateDestroyAPIView(views.APIView):
  """ XXXX の取得(詳細)・更新・一部更新・削除 API クラス """
  parser_classes = (MultiPartParser, FormParser) #! 🎰引用

  def get(self, request, pk, *args, **kwargs):
    oc = get_object_or_404(XXXX, pk=pk)
    ser = XXXXSerializer(instance=oc)
    return Response(ser.data, status.HTTP_200_OK)

  def put(self, request, pk, *args, **kwargs):
    oc = get_object_or_404(XXXX, pk=pk)
    ser = XXXXSerializer(instance=oc, data=request.data)
    ser.is_valid(raise_exception=True)
    ser.save()
    return Response(ser.data, status.HTTP_200_OK)
    
  def patch(self, request, pk, *args, **kwargs):
    oc = get_object_or_404(XXXX, pk=pk)
    ser = XXXXSerializer(instance=oc, data=request.data, partial=True)
    ser.is_valid(raise_exception=True)
    ser.save()
    return Response(ser.data, status.HTTP_200_OK)

  def delete(self, request, pk, *args, **kwargs):
    oc = get_object_or_404(XXXX, pk=pk)
    ser.delete()
    return Response(status.HTTP_204_NO_CONTENT)
```

### 参考にした記事

|内容|URL|備考|
|:--|:--|:--|
|APIView のために必要|[現場で使える Django REST Framework の教科書 （Django の教科書シリーズ）](https://www.amazon.co.jp/%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B-Django-Framework-%EF%BC%88Django-%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA%EF%BC%89-ebook/dp/B07XWL8FPM)|上記📘|
|REST によるファイルのアップロード方法|https://stackoverflow.com/questions/45559471/django-rest-framework-upload-image-api|上記🎰|

　

以上.