Vasyworksでのメディアファイルの表示について:Vasyworks解説

Vasyworksでログインを必要とするシステムについては、ユーザがアップロードしたメディアファイルは直接参照せずに表示用のビューアを通して参照するようにしています。DjangoのMEDIAのディレクトリをWEB公開せずにビューアを通して表示させることで、HTML内に記述されているメディア参照を直接URL指定してもログインしないと閲覧できないようにしています。 下記にサンプルコードを記載します。

■viewer/urls.py

"""
System Name: Vasyworks
Copyright (C) 2020 Yasuhiro Yamamoto
"""
from django.urls import path
from django.views.generic import TemplateView
from .views import *


urlpatterns = [
    path('media/<path:file_url>', MediaViewerView.as_view(), name='viewer_media'),

    path('', TemplateView.as_view(template_name='404.html'), name='viewer_index'),
]

■viewer/views.py

"""
System Name: Vasyworks
Copyright (C) 2020 Yasuhiro Yamamoto
"""
import os
import mimetypes
from django.conf import settings
from django.http import HttpResponse, Http404
from django.views.generic import View
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required


class MediaViewerView(View):
    """
    メディアビューア
    """
    @method_decorator(login_required)
    def get(self, request, *args, **kwargs):
        user = self.request.user
        if not user:
            raise Http404

        file_url = kwargs.get('file_url')
        if file_url:
            file_path = os.path.join(settings.MEDIA_ROOT, file_url.replace('/', os.sep))
            file_name = self.get_file_name(file_url)

            file = None
            try:
                file = open(file_path, 'rb')
                content_type = self.get_content_type(file_url)

                response = HttpResponse(file.read(), content_type=content_type)
                if self.is_attachment(content_type):
                    response['Content-Disposition'] = 'attachment; filename="{0}"'.format(file_name)

                file.close()
                return response

            except:
                if file:
                    file.close()
                raise Http404

        else:
            raise Http404

    @classmethod
    def get_content_type(cls, url: str):
        """ URLからContent-Typeを取得 """
        ans = ''
        if url:
            mimetype = mimetypes.guess_type(url)
            if mimetype:
                ans = mimetype[0]

        return ans

    @classmethod
    def get_file_name(cls, url: str):
        ans = ''
        if url:
            ans = url.rsplit('/', 1)[1]

        return ans

    @classmethod
    def is_attachment(cls, content_type):
        """ 指定のContent_Typeがダウンロード対象ならTrue  """
        ans = True
        if 'image/' in content_type:
            ans = False
        elif 'video/' in content_type:
            ans = False
        elif 'application/pdf' in content_type:
            ans = False

        return ans

urls.pyでパラメータのパスコンバータに「path」を指定してファイルパスを受け取るように指定しています。ファイルパスにはDjangoのMEDIAルート(/media/)からのパスを区切り文字に「/」を使って指定します。ちなみにURLディスパッチャで使えるデフォルトのパスコンバータはDjango3.2のドキュメントには「str」「int」「slug」「uuid」「path」の5つがあると記載されています。

views.pyのMediaViewerViewクラスのgetメソッドでlogin_requiredを指定していますので、ログインを介さずに直接ビューアを参照した場合はログインを促されます。Httpサーバ設定でDjangoのmediaディレクトリのエイリアス指定を行わないようにするなど、mediaディレクトリへのURL直接参照ができないようにしておけば、メディアファイルへのアクセスをビューア経由に制限できるので、ログインを強要することができます。

また、このMediaViewerViewクラスでは画像、動画、PDF以外のファイルが対象の場合はContent-Dispositionにattachmentを設定してダウンロードを促すように指定しています。