python3.doc_62
最終投稿日:2022年6月18日
サンプルとして .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
あんがとん。。。
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 %}