Djangoで画像をアップロードしてConoHaオブジェクトストレージに登録する方法
Djangoで画像をアップロードしてConoHaオブジェクトストレージに登録する方法をご紹介します。
目次
条件
- Django 2.2.5
- Python 3.7.0
前提
当該記事は、以下の記事の改修版です。
以下の記事で作成したサンプルソースを基にして、変更点の差分をご紹介します。
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
アップロード処理の流れは以下の通りです。
- アップロードファイルをサーバーに一旦保存する。
- オブジェクトストレージのtokenを取得する。
- オブジェクトストレージへファイルをアップロードする。
- 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
お世話になっております。
日頃よりブログを参考にさせて頂いております。
大変勉強になる記事をいつもありがとうございます。
今回の記事で何点か質問があります。
◯headersにX-Container-Readを追加しても公開出来ないのですが、他にやるべき事はありますでしょうか?
◯今回の記事は画像のアップロードについてですが、動画アップロードに対応させるにはどのようにしたら良いでしょうか?
お忙しいところ大変恐縮ですが、お時間のある時で構いませんので回答頂けますと幸いです。
よろしくお願いします。
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オブジェクトストレージへのアップロードおよび公開が出来ることは確認しております。