python3.doc_62
R e i - D r e a m
for Laravel
TOP > Python3 > Django(画像アップロード)
Guest
login

最終投稿日:2022年6月18日

Django(画像アップロード)
ファイルのアップロード
サンプルとして .txt ファイルのアップロード画面を作成します
アプリ名を uploads とします
[要件]
    .txt のファイルをアップロードできます。
    ファイルサイズは1M未満とします。
    アップロードファイルには任意の名前を付ける事ができます。
    アップロードファイルは日付単位のディレクトリに保管されます。
    同じディレクトリに同名のファイルは保管できません。

[ディレクトリ構成]
    【プロジェクト】
            |
            +---【config】
            |          |
            |          +---【settings】
            |          |           |
            |          |           +--- [base.py] ※本来の settings.py
            |          |           +--- [develop.py] ※開発環境様に拡張した settings.py
            |          +--- [urls.py]
            +---【apps】
            |       |
            |       +---【uploads】
            |                  |
            |                  +--- [urls.py]
            |                  +--- [forms.py] ※フォーム
            |                  +--- [views.py] ※ビジネスロジック
            +---【my_lib】
            |         |
            |         +---【my_common】
            |                      |
            |                      +--- [file_common.py] ※共通ファイル
            +---【templates】
            |          |
            |          +---【layout】
            |          |          |
            |          |          +--- [base.html] ※静的ファイルの共通レイアウト
            |   【uploads】
            |          |
            |          +--- [index.html] ※アップロード画面
            |          +--- [complate.html] ※アップロード終了画面
            +---【tmp】
                    |
                    +---【uploads】
                            |
                            +---【files】 ※アップロード先ディレクトリのルート

    ※ディレクトリ構成はカスタマイズしています

● base.py
    import os
    import sys
    ~ 色々省略 ~
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    BASE_DIR = os.path.dirname(BASE_DIR)
    ※共通ライブラリのパスを通す
    sys.path.append(os.path.join(BASE_DIR, 'my_lib'))
    ~ 色々省略 ~

● develop.py
    from .base import *
    ~ 色々省略 ~
    # ファイルアップロード先ディレクトリのルート
    UPLOAD_OF_FILE_BASE_DIR = os.path.join(BASE_DIR, 'tmp', 'uploads', 'files')

● プロジェクト/urls.py
    from django.contrib import admin
    from django.urls import path
    from django.conf.urls import include, url
    from django.views.generic import RedirectView
    urlpatterns = [
        url(r'^$', RedirectView.as_view(url='uploads/')),
        url(r'^uploads/', include('uploads.urls', namespace='uploads')),
        url('admin/'  , admin.site.urls),
    ]

● アプリ/urls.py
    from django.conf.urls import url
    from django.views.generic import RedirectView
    from .views import *
    app_name = 'uploads'
    urlpatterns = [
        url(r'^$', RedirectView.as_view(url='index/')),
        url(r'^index/$', IndexView.as_view(), name='index'),
    ]

● forms.py
    from django import forms
    class UploadForm(forms.Form):
        ※ファイル名設定用項目
        title = forms.CharField(
            label = 'ファイルタイトル',
            required = False,
            max_length = 25,
            widget = forms.TextInput(
                attrs = {'size':'30'}
            )
        )
        ※ファイル選択項目
        file = forms.FileField(
            label = 'ファイル選択',
            required = False,
        )
        """ バリデーション """
        def clean(self):
            title = self.cleaned_data['title']
            if title == '':
                self.add_error('title', 'タイトルは必須です')
            return self.cleaned_data

● my_lib/my_common/file_common.py
    import os
    import sys
    """
    ファイルチェック
     引数1:obj    ファイルオブジェクト
     引数2:int    最大バイト数
     引数3:list   使用可能な拡張子(.txt 等)
    """
    def checkFile( obj, max_byte, extension = []):
        err_msg = ''
        try:
            if obj.size > max_byte:
                err_msg = 'ファイルサイズは' + str(max_byte) + 'byte以下にしてください。'
            if not obj.name[obj.name.find('.'):] in extension:
                err_msg = '使えない拡張子です。'
        except:
            return '予期せぬエラーです。。'
        return err_msg
    """
    ディレクトリの有無チェック。無い場合は作成。成功で True 返却
     引数1:チェックまたは作成したいディレクトリのパス
    """
    def dirCheckCreate( my_path ):
        if my_path == '':
            return False
        if not os.path.exists(my_path):
            try:
                os.mkdir(my_path)
            except:
                print('ディレクトリの作成に失敗しました。。')
                sys.exit()
        return True
    """
    ファイルの保存
     引数1:obj    ファイルオブジェクト
     引数2:str    ファイル名までの絶対パス
     引数3:str    オプション
    """
    def writeFile( obj, fPath = '', op = '' ):
        if fPath == '' or op == '':
            return False
        try:
            f = open(fPath, op)
            for chunk in obj.chunks():
                f.write(chunk)
        except:
            print('ファイルの書き込みに失敗しました。。')
            sys.exit()
        return True

● views.py
    from django.shortcuts import render
    from django.http.response import HttpResponse
    from django.views.generic import View
    from .forms import *
    from django.conf import settings
    from datetime import date, time, datetime, timedelta, timezone
    import os
    import sys
    from my_common import file_common as fcmmon
    class IndexView(View):
        def get(self,request):
            #GET 遷移時はフォーム定義をテンプレートへ渡すのみ
            form = UploadForm()
            d = {'form':form}
            return render(request, 'uploads/index.html', d)
        def post(self,request):
            #サブミットされた場合はフォーム定義を POST 値で初期化する
            form = UploadForm(request.POST)
            #forms.py のバリデーションが成功の場合の処理
            if form.is_valid():
                """ 各種パス設定 """
                #日付名の文字列作成
                target_dir = datetime.now().strftime('%Y%m%d')
                #上記日付名のディレクトリをアップロードディレクトリに設定する
                target_path = os.path.join(settings.UPLOAD_OF_FILE_BASE_DIR, target_dir)
                #ファイル名付きの絶対パスを定義する
                full_path = os.path.join(target_path, request.POST['title'] + '.txt')
                """ ファイルバリデーション """
                file_valid = False
                #テンポラリファイルの中に項目属性名 file があるか確認
                if 'file' in request.FILES:
                    #ファイルオブジェクト作成
                    up_file = request.FILES['file']
                    #共通処理(file_common.py)にてファイルをチェック
                    err = fcmmon.checkFile( up_file, 1000000, ['.csv','.txt'])
                    if err != '':
                        #エラーがあった場合は form オブジェクトにエラー内容を追加
                        form.add_error('file', err)
                    #保管先ディレクトリに対象のファイル名があるかチェック
                    elif os.path.exists(full_path):
                        #エラーがあった場合は form オブジェクトにエラー内容を追加
                        form.add_error('file', 'そのファイル名は既に存在します')
                    else:
                        #エラーがなかった場合は保存フラグを True にする
                        file_valid = True
                else:
                    #テンポラリファイルの中に項目属性名 file がない場合はここに処理がきます
                    form.add_error('file', 'ファイルが選択されていません')
                #エラーがなく、保管先ディレクトリの作成に成功した場合は保存処理をする
                if file_valid and fcmmon.dirCheckCreate(target_path):
                    """ 保存処理 """
                    #共通処理(file_common.py)にてファイルを保存する
                    fcmmon.writeFile(up_file, full_path, 'wb+')
                    #complate 画面を描画して一連の処理は終了です!
                    return render(request, 'uploads/complate.html')
                d = {'form':form}
                return render(request, 'uploads/index.html', d)
            #forms.py のバリデーションで失敗した場合はここに処理がきます
            else:
                d = {'form':form}
                return render(request, 'uploads/index.html', d)

● layout/base.html
    <html>
        <head>
            <title>{{my_title}}</title>
            <meta http-equiv="content-type" content="text/html; charset=utf-8">
        </head>
        <body>
            {% block my_body %}{% endblock %}
        </body>
    </html>

● uploads/index.html
    {% extends 'layout/base.html' %}
    {% block my_body %}
    あっぷろーど画面<br>
    <form method='POST' action='' enctype="multipart/form-data"  >
        {% if form.title.errors.as_text != '' %}
            {{form.title.errors.as_text|cut:'* '}}<br>
        {% endif %}
        {{form.title.label}} {{form.title}}
        <br><br>
        {% if form.file.errors.as_text != '' %}
            {{form.file.errors.as_text|cut:'* '}}<br>
        {% endif %}
        {{form.file.label}} {{form.file}}
        <br>
        {% csrf_token %}
        <input type='submit' value='登録'>
    </form>
    {% endblock %}

● uploads/complate.html
    あんがとん。。。
            
メモ UploadedFile オブジェクト
request.FILES['hoge'] から取得できるオブジェクトです

● ファイルがアップロードされたか確認
    アップロードされたファイルは、request.FILES にリストとして格納されるので、
    ファイル項目で使用した name 属性名で検索できます。
    例)
        if 'hoge' in request.FILES:
            # ある
        else:
            # ない

● UploadedFile.read()
    アップロードされたデータ全体をファイルから読み出します。(バイナリで読み出す)
    このメソッ ドを使うときに十分注意してください。というのも、アップロードされた ファイルが巨大だと、
    メモリに読み込む際にシステムの容量を越してしま うかもしれないからです。

● UploadedFile.multiple_chunks()
    ファイルが大きくて、複数のチャンクに分けて読み出すべきである場合に True を返します。
    デフォルトの設定では、 2.5 Mbytes より大きなファ イルに対して True を返します。
    ○ FILE_UPLOAD_MAX_MEMORY_SIZE
        アップロードされたファイルをメモリに保存する上限のサイズです。
        設定値よりも大きなファイルがアップロードされると、ファイルデータは ディスクに書き込まれます。
        デフォルト値は 2.5 Mbytes です。

● UploadedFile.chunk()
    ファイルのチャンクを返すジェネレータです。 multiple_chunks() が True の場合には、 
    read() ではなくこのメソッドを使ってください。
    実際には、常に chunks() を使うのがよいでしょう。
    例)
        サーバの hoge.txt に上書き
        f = request.FILES['foo']
        destination = open('some/file/hoge.txt', 'wb+')
        for chunk in f.chunks():
            destination.write(chunk)

● UploadedFile.name
    hoge.txt のような、アップロードされたファイルの名前です。

● UploadedFile.size
    アップロードファイルのサイズ(単位バイト)です。
            
画像のアップロード
サンプルとして 画像のアップロード画面を作成します
アプリ名を uploads とします
[要件]
    .jpg .gif のファイルをアップロードできます。
    ファイルサイズは1M未満とします。
    アップロードファイルにはランダムなファイル名(日時形式)が割り当てられます。
    アップロードファイルはユーザー名単位のディレクトリに保管されます。
    (ログインしていない場合は nouser ディレクトリ)
    確認画面を挟みます

[ディレクトリ構成]
    【プロジェクト】
            |
            +---【config】
            |          |
            |          +---【settings】
            |          |           |
            |          |           +--- [base.py] ※本来の settings.py
            |          |           +--- [develop.py] ※開発環境用に拡張した settings.py
            |          |           +--- [procution.py] ※本番環境用に拡張した settings.py
            |          +--- [urls.py]
            |          +--- [wsgi.py] ※本番環境のエントリポイント
            +---【apps】
            |       |
            |       +---【uploads】
            |                  |
            |                  +---【static】
            |                  |         |
            |                  |         +--- 【uploads】
            |                  |                     |
            |                  |                     +--- [base.css] ※アプリ専用の個別CSSファイル
            |                  +--- [urls.py]
            |                  +--- [forms.py] ※フォーム
            |                  +--- [views.py] ※ビジネスロジック
            +---【my_lib】
            |         |
            |         +---【my_common】
            |                      |
            |                      +--- [file_common.py] ※共通ファイル
            +---【templates】
            |          |
            |          +---【layout】
            |          |          |
            |          |          +--- [base.html] ※静的ファイルの共通レイアウト
            |   【uploads】
            |          |
            |          +--- [index.html] ※アップロード画面
            |          +--- [confirm.html] ※確認画面
            |          +--- [complate.html] ※アップロード終了画面
            +---【static】
            |       |
            |       +---【images】
            |                  |
            |                  +---【uploads】 ※アップロード先ディレクトリのルート
            |                            |
            |                            +---【tmp】
            |                            |      |
            |                            |      +--- [画像ファイル] ※仮の画像保管場所(確認画面が参照する)
            |                            +---【users】
            |                                   |
            |                                   +---【各ユーザー名】
            |                                                  |
            |                                                  +--- [画像ファイル] ※本登録用
            +--- [manage.py] ※開発用エントリポイント及び各種ユーティリティ制御ファイル

※ディレクトリ構成はカスタマイズしています
            
● base.py
    import os
    import sys
    ~ 色々省略 ~
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    BASE_DIR = os.path.dirname(BASE_DIR)
    #カスタマイズしたアプリ群をまとめるディレクトリのパスを通す
    sys.path.append(os.path.join(BASE_DIR, "apps"))                 
	#共通ライブラリのパスを通す
    sys.path.append(os.path.join(BASE_DIR, 'my_lib'))
	#本番用の static の参照先を指定
    STATIC_ROOT = 'C:/Users/hoge/Desktop/Python/Django/www/static'
    #開発用各アプリの static 参照先ディレクトリ名
    STATIC_URL = '/static/'
    STATICFILES_DIRS = [
    	#開発用の共通の static 参照先パス
        os.path.join(BASE_DIR, 'static'),
    ]
    ~ 色々省略 ~

● manage.py ~ 開発環境用 ~
    import os
    if __name__ == "__main__":
        os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.develop")
        ~ 色々省略 ~

develop.py ~ 開発環境用 ~
    from .base import *
    ~ 色々省略 ~
    # 仮画像アップロード先ディレクトリのルート
    UPLOAD_OF_IMAGE_BASE_DIR = os.path.join(BASE_DIR, 'static', 'images', 'uploads')

● wsgi.py ~ 本番環境用 ~
    import os
    from django.core.wsgi import get_wsgi_application
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.procution")
    application = get_wsgi_application()

procution.py ~ 本番環境用 ~
    from .base import *
    ~ 色々省略 ~
    # 仮画像アップロード先ディレクトリのルート
    UPLOAD_OF_IMAGE_BASE_DIR = 'C:/Users/hoge/Desktop/Python/Django/www/static/images/uploads'

● プロジェクト/urls.py
    from django.contrib import admin
    from django.urls import path
    from django.conf.urls import include, url
    from django.views.generic import RedirectView
    from django.conf.urls import include, url, handler404
    from config import backends
    urlpatterns = [
        url(r'^$', RedirectView.as_view(url='uploads/')),
        url(r'^myaccounts/', include('myaccounts.urls', namespace='myaccounts')),
        url(r'^uploads/', include('uploads.urls', namespace='uploads')),
        url('admin/'  , admin.site.urls),
    ]

● アプリ/urls.py
    from django.conf.urls import url
    from django.views.generic import RedirectView
    from .views import *
    app_name = 'uploads'
    urlpatterns = [
        url(r'^$', RedirectView.as_view(url='index/')),
        url(r'^index/$', IndexView.as_view(), name='index'),
        url(r'^confirm/$', ConfirmView.as_view(), name='confirm'),
        url(r'^complate/$', ComplateView.as_view(), name='complate'),
    ]

● forms.py
    from django import forms
    class UploadForm(forms.Form):
        img = forms.FileField(
            label = 'ファイル選択',
            required = False,
        )

● my_lib/my_common/file_common.py
    import os
    import sys
    import shutil
    """
    ファイルチェック
     引数1:obj    ファイルオブジェクト
     引数2:int    最大バイト数
     引数3:list   使用可能な拡張子(.txt 等)
    """
    def checkFile( obj, max_byte, extension = []):
        err_msg = ''
        try:
            if obj.size > max_byte:
                err_msg = 'ファイルサイズは' + str(max_byte) + 'byte以下にしてください。'
            if not obj.name[obj.name.find('.'):] in extension:
                err_msg = '使えない拡張子です。'
        except:
            return '予期せぬエラーです。。'
        return err_msg
    """
    ディレクトリの有無チェック。無い場合は作成。成功で True 返却
     引数1:str    チェックまたは作成したいディレクトリのパス
    """
    def dirCheckCreate( my_path ):
        if my_path == '':
            return False
        if not os.path.exists(my_path):
            try:
                os.mkdir(my_path)
            except:
                print('ディレクトリの作成に失敗しました。。')
                sys.exit()
        return True
    """
    ディレクトリの有無チェック。
     引数1:str    チェックまたは作成したいディレクトリのパス
    """
    def dirCheck( my_path ):
        if my_path == '':
            return False
        if not os.path.exists(my_path):
            return False
        return True
    """
    ファイルの保存
     引数1:obj    ファイルオブジェクト
     引数2:str    ファイル名までの絶対パス
     引数3:str    オプション
    """
    def writeFile( obj, fPath = '', op = '' ):
        if fPath == '' or op == '':
            return False
        try:
            f = open(fPath, op)
            for chunk in obj.chunks():
                f.write(chunk)
        except:
            print('ファイルの書き込みに失敗しました。。')
            sys.exit()
        return True
    """
    ファイルの拡張子を取得(.gif 等)
     引数1:obj    ファイルオブジェクト
    """
    def getExtension(obj):
        return obj.name[obj.name.find('.'):]
    """
    ファイルを削除する
     引数1:str    ファイル名までの絶対パス
    """
    def deleteFile(fPath = ''):
        if fPath == '':
            return False
        try:
            os.remove(fPath)
            return True
        except:
            print('ファイルの削除に失敗しました。')
            sys.exit()
    """
    ファイルを移動する
     引数1:str    移動元フルパス
     引数2:str    移動先フルパス
    """
    def moveFile(fFrom = '', fTo = ''):
        if fFrom == '' or fTo == '':
            return False
        try:
            shutil.move(fFrom, fTo)
            return True
        except:
            print('ファイルの移動に失敗しました。。')
            sys.exit()

● views.py
    from django.shortcuts import render
    from django.urls import reverse
    from django.http.response import HttpResponse
    from django.http.response import HttpResponseRedirect
    from django.views.generic import View
    from .forms import *
    from django.conf import settings
    from datetime import date, time, datetime, timedelta, timezone
    import os
    import sys
    from my_common import file_common as fcmmon
    from django.core.files.uploadedfile import InMemoryUploadedFile
    class IndexView(View):
        def get(self,request):
            form = UploadForm()
            d = {'form':form,'img_name':'hoge.jpg'}
            return render(request, 'uploads/index.html', d)
        def post(self,request):
            # ファイルの削除処理
            target_path = os.path.join(settings.UPLOAD_OF_IMAGE_BASE_DIR, 'tmp', request.POST['img_name'])
            fcmmon.deleteFile(target_path)
            return HttpResponseRedirect(reverse('uploads:index'))

    class ConfirmView(View):
        def post(self,request):
            form = UploadForm(request.POST)
            if form.is_valid():
                """ 各種パス設定 """
                tmp_name = datetime.now().strftime('%Y%m%d%H%M%S%f')
                target_path = os.path.join(settings.UPLOAD_OF_IMAGE_BASE_DIR, 'tmp')
                """ ファイルバリデーション """
                file_valid = False
                if 'img' in request.FILES:
                    up_img = request.FILES['img']
                    err = fcmmon.checkFile( up_img, 1000000, ['.jpg','.gif'])
                    if err != '':
                        form.add_error('img', err)
                    else:
                        file_valid = True
                else:
                    form.add_error('img', 'ファイルが選択されていません')
                if file_valid and fcmmon.dirCheck(target_path):
                    """ 保存処理 """
                    full_path = os.path.join(target_path, tmp_name + fcmmon.getExtension(up_img))
                    fcmmon.writeFile(up_img, full_path, 'wb+')
                    d = {'img_name':tmp_name + fcmmon.getExtension(up_img)}
                    return render(request, 'uploads/confirm.html', d)
                d = {'form':form}
                return render(request, 'uploads/index.html', d)
            else:
                d = {'form':form}
                return render(request, 'uploads/index.html', d)

    class ComplateView(View):
        def get(self,request):
            if request.user.username != '':
                user = request.user.username
            else:
                user = 'nouser'
            target_path = os.path.join(settings.UPLOAD_OF_IMAGE_BASE_DIR, 'users', user)
            tmpFiles = os.listdir(target_path)
            images = []
            for f in tmpFiles:
                if f[f.find('.'):] in ['.jpg','.png','.gif']:
                    images.append(f)
            d = {'user':user,'images':images}
            return render(request, 'uploads/complate.html', d)

        def post(self,request):
            # ファイルの登録処理
            from_path = os.path.join(settings.UPLOAD_OF_IMAGE_BASE_DIR, 'tmp', request.POST['img_name'])
            if request.user.username != '':
                user = request.user.username
            else:
                user = 'nouser'
            to_path = os.path.join(settings.UPLOAD_OF_IMAGE_BASE_DIR, 'users', user)
            if fcmmon.dirCheckCreate(to_path):
                #ファイルの移動
                fcmmon.moveFile(from_path, to_path)
                return HttpResponseRedirect(reverse('uploads:complate'))

● layout/base.html
    {% load static %}
    <html>
        <head>
            <title>{{my_title}}</title>
            <meta http-equiv="content-type" content="text/html; charset=utf-8">
            <link href="{% static 'uploads/base.css' %}" rel="stylesheet" type="text/css">
        </head>
        <body>
            {% block my_body %}{% endblock %}
        </body>
    </html>

● uploads/index.html
    {% extends 'layout/base.html' %}
    {% block my_body %}
    あっぷろーど画面<br>
    <form method='POST' action="{% url 'uploads:confirm' %}" enctype="multipart/form-data"  >
        {% if form.img.errors.as_text != '' %}
            {{form.img.errors.as_text|cut:'* '}}<br>
        {% endif %}
        {{form.img.label}} {{form.img}}
        <br>
        {% csrf_token %}
        <input type='submit' value='登録'>
    </form>
    {% endblock %}

● uploads/confirm.html
    {% extends 'layout/base.html' %}
    {% load static %}
    {% block my_body %}
    かくにん画面<br>
    <SCRIPT>
        function send_back(){
            document.myform.action = "{% url 'uploads:index' %}";
            document.myform.submit();
        }
    </SCRIPT>
    <form name='myform' method='POST' action="{% url 'uploads:complate' %}">
        <INPUT TYPE='hidden' NAME='img_name' VALUE='{{img_name}}'>
        {% csrf_token %}
        <input type='button' name='back' onclick='send_back()' value='戻る'>
        <input type='submit' value='登録'>
    </form>
    <BR>
    <img src="{% static 'images/uploads/tmp/' %}{{img_name}}">
    {% endblock %}

● uploads/complate.html
    {% extends 'layout/base.html' %}
    {% load static %}
    {% block my_body %}
    あんがとん。。。
    <BR>
    {% for image in images %}
        <img src="{% static 'images/uploads/users/' %}{{user}}/{{image}}">
        <BR>
    {% endfor %}
    {% endblock %}
            
ログインしてコメントを残そう!!


きっぷる