Djangoで画像をアップロードしてConoHaオブジェクトストレージに登録する方法

Djangoで画像をアップロードしてConoHaオブジェクトストレージに登録する方法をご紹介します。

条件

  • Django 2.2.5
  • Python 3.7.0

前提

当該記事は、以下の記事の改修版です。
以下の記事で作成したサンプルソースを基にして、変更点の差分をご紹介します。

Djangoで画像ファイルをアップロードする方法

ConoHaオブジェクトストレージについては、以下の記事で紹介したPythonの関数を使用します。

PythonでConoHaのオブジェクトストレージ操作を行う方法2

また、ConoHaオブジェクトストレージの「コンテナ」は作成済み、かつWeb公開設定済みの状態であるものとします。
(オブジェクトストレージにアップロードするだけであれば、Web公開設定は不要です。ブラウザで参照したい場合は、Web公開設定は必須です。)

アップロード画像のConoHaオブジェクトストレージへの登録

urls.py

従来のアップロード画面のパスをコメントアウトし、代わりに「ConoHaオブジェクトストレージに登録」用のパスを記述します。
今回はviews.pyにおいて、クラスベースではなく、関数ベースでの実装となります。

from django.urls import path
from . import views

app_name = 'pictures'

urlpatterns = [
    # トップ画面
    path('', views.IndexView.as_view(), name='index'),

    # 詳細画面
    path('pictures/<int:pk>/', views.DetailView.as_view(), name='detail'),

    # ファイルアップロード用
    # path('pictures/<int:pk>/upload/', views.Upload.as_view(), name='upload'),
    path('pictures/<int:pk>/upload/', views.uploadImage, name='upload'),  # ConoHaオブジェクトストレージに登録
    path('pictures/<int:pk>/upload_complete/', views.UploadComplete.as_view(), name='upload_complete'),
]

views.py

アップロード処理の流れは以下の通りです。

  1. アップロードファイルをサーバーに一旦保存する。
  2. オブジェクトストレージのtokenを取得する。
  3. オブジェクトストレージへファイルをアップロードする。
  4. DBにアップロードしたファイル情報のレコードを追加する。
from django.shortcuts import redirect
from django.views import generic
from .models import Work, Image
from django.contrib.auth.mixins import LoginRequiredMixin
import logging
from .forms import UploadFileForm
from django.views.generic.edit import FormView
import requests
import json
from works import settings
import os
from django.shortcuts import render

logger = logging.getLogger('development')

# アップロードした画像を保存するディレクトリ
UPLOAD_IMG_DIR = settings.MEDIA_ROOT + '/image/'

# オブジェクトストレージ接続パラメータ
USER_NAME = "gncu00000000"  # ユーザー名
PASSWORD = "passpasspass"  # パスワード
TENANT_ID = "9999999999999999999999999"  # テナントID
URL = 'https://identity.tyo2.conoha.io/v2.0/tokens'  # トークン取得用URL
OBJECT_STORAGE = 'https://object-storage.tyo2.conoha.io/v1/nc_9999999999999999999999999999'  # オブジェクトストレージURL
CONTAINER = 'sample1'  # コンテナ名


# 一覧画面
class IndexView(LoginRequiredMixin, generic.ListView):
    model = Work
    paginate_by = 5
    ordering = ['-updated_at']
    template_name = 'pictures/index.html'


# 詳細画面
class DetailView(generic.DetailView):
    model = Work
    template_name = 'pictures/detail.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        self.request.session.modified = True  # セッション更新
        context['work_pk'] = self.kwargs['pk']

        return context


# # ファイルアップロード
# class Upload(FormView):
#     template_name = 'pictures/upload.html'
#     form_class = UploadFileForm
#
#     def get_context_data(self, **kwargs):
#         context = super().get_context_data(**kwargs)
#         form = self.get_form()
#         context = {
#             'work_pk': self.kwargs['pk'],
#             'form': form,
#         }
#         return context
#
#     def form_valid(self, form):
#
#         image = Image()  # DBへの保存
#         image.work_id = self.kwargs['pk']  # 作品ID
#         image.image = form.cleaned_data['image']  # アップロードしたイメージパス
#         image.save()
#
#         return redirect('pictures:upload_complete', self.kwargs['pk'])  # アップロード完了画面にリダイレクト


def getToken(userName, password, tenantId, url):
    """
    tokenの取得
    :param userName:
    :param password:
    :param tenantId:
    :param url:
    :return:
    """

    headers = {
        'Accept': 'application/json',
    }

    data = '{"auth":{"passwordCredentials":{"username":"%s","password":"%s"},"tenantId":"%s"}}' % (
    userName, password, tenantId)

    response = requests.post(url, headers=headers, data=data)
    data = response.json()

    return json.dumps(data["access"]["token"]["id"], indent=4)


def uploadObject(token, path, objectStorageUrl, container, fileName):
    """
    オブジェクトのアップロード
    :param token:
    :param path:
    :param objectStorageUrl:
    :param container:
    :param fileName:
    :return:
    """

    headers = {
        'Accept': 'application/json',
        'X-Auth-Token': token,
    }

    with open(path, 'rb') as image:
        response = requests.put(objectStorageUrl + '/' + container + '/' + fileName, headers=headers, data=image)

    return response


def handle_uploaded_image(upload_image, pk):
    """
    アップロードされた画像のハンドル
    :param upload_image:
    :param pk:
    :return:
    """
    # アップロードファイルを一旦保存する。
    path = os.path.join(UPLOAD_IMG_DIR, upload_image.name)
    with open(path, 'wb+') as destination:
        for chunk in upload_image.chunks():
            destination.write(chunk)

    # オブジェクトストレージにアップロードする。
    token = getToken(USER_NAME, PASSWORD, TENANT_ID, URL).replace('\"', '')  # tokenの取得

    fileName = str(upload_image.name)  # ファイル名

    result = uploadObject(token, path, OBJECT_STORAGE, CONTAINER, fileName)  # オブジェクトのアップロード

    if result.status_code == 201:  # アップロード成功の場合

        image = Image()  # DBへの保存
        image.work_id = pk  # 作品ID
        image.image = OBJECT_STORAGE + '/' + CONTAINER + '/' + fileName  # アップロードしたイメージパス
        image.save()

        # アップロードしたファイルを削除する
        os.remove(path)

    else:  # アップロード失敗の場合
        logger.warning('オブジェクトのアップロードに失敗しました。')


def uploadImage(request, pk):
    """
    画像のアップロード
    :param request:
    :param pk:
    :return:
    """
    if request.method == "POST":
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():

            handle_uploaded_image(request.FILES['image'], pk)  # アップロードされた画像のハンドル
            return redirect('pictures:upload_complete', pk)  # アップロード完了画面にリダイレクト
    else:
        form = UploadFileForm()
    return render(request, 'pictures/upload.html', {'form': form})


class UploadComplete(FormView):
    """
    ファイルアップロード完了
    """
    template_name = 'pictures/upload_complete.html'
    form_class = UploadFileForm

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        self.request.session.modified = True  # セッション更新
        context['work_pk'] = self.kwargs['pk']

        return context

実行結果

アップロード実行前

オブジェクトストレージ

対象のコンテナ「sample1」にはオブジェクトが存在しない状態です。

DB

imageテーブルにレコードは存在しない状態です。

アップロード実行後

画面

オブジェクトストレージ

対象のコンテナ「sample1」に「tanu.jpg」が追加されました。

DB

imageテーブルにレコードが追加されました。

アップロードファイル確認

アップロードした画像のパスをブラウザで指定すると、以下のように表示することが出来ます。

サンプルソース

GitHubに当該記事のサンプルソースを公開しています。

注意:当該記事のソースは、ブランチ「feature_conoHa」です。

https://github.com/kzmrt/works

(Branch:feature_conoHa)

参考

「django-storages」を用いれば、Djangoにおけるメディアファイルの保存先を簡単にクラウドストレージに設定できるようです。
対応しているサービスは以下の通りです。
(残念ながらConoHaオブジェクトストレージは未対応のようです。)

  • Amazon S3
  • Apache Libcloud
  • Azure Storage
  • Digital Ocean
  • DropBox
  • FTP
  • Google Cloud Storage
  • SFTP

django-storages

https://django-storages.readthedocs.io/en/latest/index.html



Djangoで画像をアップロードしてConoHaオブジェクトストレージに登録する方法” に対して2件のコメントがあります。

  1. なお より:

    お世話になっております。
    日頃よりブログを参考にさせて頂いております。
    大変勉強になる記事をいつもありがとうございます。

    今回の記事で何点か質問があります。
    ◯headersにX-Container-Readを追加しても公開出来ないのですが、他にやるべき事はありますでしょうか?
    ◯今回の記事は画像のアップロードについてですが、動画アップロードに対応させるにはどのようにしたら良いでしょうか?

    お忙しいところ大変恐縮ですが、お時間のある時で構いませんので回答頂けますと幸いです。
    よろしくお願いします。

    1. 確認飛行物体 より:

      1.オブジェクトを公開する方法
      対象のコンテナに対して、1回だけWeb公開処理を実行する必要があります。
      ・curlコマンドで実行する場合、以下のサイトを参照してください。
      https://support.conoha.jp/v/objectstoragecurl/?btn_id=v-objectstorage–sidebar_v-objectstoragecurl#07
      ・Pytyonで実行する場合、以下の記事における「def webPublishing(token):」がWeb公開処理です。
      https://intellectual-curiosity.tokyo/2019/09/01/python%e3%81%a7conoha%e3%81%ae%e3%82%aa%e3%83%96%e3%82%b8%e3%82%a7%e3%82%af%e3%83%88%e3%82%b9%e3%83%88%e3%83%ac%e3%83%bc%e3%82%b8%e6%93%8d%e4%bd%9c%e3%82%92%e8%a1%8c%e3%81%86%e6%96%b9%e6%b3%95/

      2.動画のアップロード
      当記事のDjangoによるアップロード処理は「画像」限定の作りになっています。
      ・models.pyにおいて、ImageFieldで定義している。
       (image = models.ImageField(upload_to=”image/”, verbose_name=’イメージ’))
      ・forms.pyで「.jpg」のみ許容するバリデーションを行っている。
       (VALID_EXTENSIONS = [‘.jpg’])

      以下2点の対応を行えば動画もアップロードできるようになると思います。
      1.models.pyにおいて、ImageFieldではなくFileFieldで定義する。
      2.forms.pyで動画ファイルの拡張子も許容するバリデーションにする。

      ちなみに、普通のPythonプログラムで動画ファイルのConoHaオブジェクトストレージへのアップロードおよび公開が出来ることは確認しております。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です