iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 8
0
Modern Web

慢慢帶你了解Flask系列 第 8

慢慢帶你了解Flask - Day8 網路相簿(4):上傳檔案

大家好,我是長風青雲,今天是鐵人賽的第八天。
今天的主題就是上傳,我們先想想上傳應該是要有怎麼樣的路徑呢?照片該存在哪裡呢?
正如我之前說過的,static常常被用來放css和圖片,所以我打算把上傳的檔案放在static的uploads裡面,然而要區分是哪一個人所上傳的,又要區分上傳的是影片還是圖片,所以我們的資料夾變成這樣。
https://ithelp.ithome.com.tw/upload/images/20190905/201201168lgDwHujZq.png
等等!難道這些都要用手建立嗎?
當然不是,照理來說,我們應該是只需要動手建立uploads,剩下使用者在註冊的時候就會自動生成屬於他的資料夾,再看未來他上傳時自己決定的相簿名稱,才會生成folder_name和對應的photo、video。
所以我們必須要回到register的部分。

要用python生成一個資料夾需要import os
在將資料加入json後面我們加上

basepath = os.path.join(os.path.dirname(__file__), 'static','uploads')
os.mkdir(os.path.join(basepath,request.values['userid']))

basepath是生成到uploads的絕對路徑,方便未來書寫所以這樣做
mkdir我們就創建一個以使用者名稱為名字的目錄在uploads裡面

然後現在進行測試
Yes
看完影片我們已經學會成功在這註冊後生成個人資料夾,那現在我們來寫upload.html吧!
(upload.html)重要部分

<div>
  <h2>Upload</h2>   
  <table>
    <tr><td><span>{{alert}}</span></td></tr>
    <tr><td>File Path:</td></tr>
  <form action="" enctype='multipart/form-data' method='POST'>
    <tr><td><input type="file" name="file[]"  accept=".jpg,.jpeg,.png,.jfif,.HEIC,.mp4,.wmv,.flv,.mov,.avi" multiple></td>
    <tr><td>Choose Folder:
          <select id="folder" name=folder onchange="Choose(this.value)">
            {% for dir in dirs %}
              <option value={{dirs.index(dir)}}>{{dir}}</option>
            {% endfor %}
          </select>
    </td></tr>
    <tr><td><input type="hidden" id="foldername" name="foldername" value=""></td></tr>
    <tr><td><input type="submit" value="upload" name="upload"></td></tr>
  </form>
</table>
</div>

(upload.html<script>)

function Choose(number){
    var choose=document.getElementById("folder").innerHTML;
    if(number==1)
    {
      document.getElementById("foldername").type="text";
    }
}

我把實現上傳的部分截取出來,首先,因為我們要允許檔案能夠一次全部上傳(要不然一個一個上傳豈不是很麻煩?)
所以我們的上傳檔案的<input>後方要加上multiple,這樣就能實現多檔一次上傳了。
此外,accept是只他可以接受上傳的檔案。我放了一些我看過的圖片影片檔名~如果有其他的你們可以自行設定。
<form>enctype=multipart/form-data是指不對這些檔案進行編碼,所以文件上傳的時候必須使用這個。
然後select那裡的dir我的預設(在後台),他的第一個選項是Not Choose、第二個選項是New Folder,剩下就是用戶之前所擁有的相簿。
既然我都說第二個選項是New Folder了~
依照我們程式邏輯的習慣、再看向 value={{dirs.index(dir)}}……對,我們的1指的並不是Not Choose 而是 New Folder。
((今天完結我會說我前陣子才發生的小故事!))
所以我們的Choose(this.value),當this.value為1時,就是說我要創建新的資料夾~
既然要創建新資料夾,那名稱要命名啊!
所以我原本有寫一個<input type=hidden>他其實是一直存在的,但他不會顯示在使用者頁面上,除非使用者說要創建新資料夾,那他就會執行把type改為text這項動作。
那html的部分說完了~終於要來到我們今天的主題?flask如何上傳檔案

首先我們要import time,至於為什麼就讓我們看下去吧!
(app.py)

@app.route('/upload/',methods=['GET','POST'])
def upload():

	basepath = os.path.join(os.path.dirname(__file__), 'static','uploads')
	dirs=os.listdir(os.path.join(basepath,session.get('username')))
	dirs.insert(0,'New Folder')
	dirs.insert(0,'Not Choose')

	if request.method == 'POST':
		flist = request.files.getlist("file[]")

		for f in flist:
			format=f.filename[f.filename.index('.'):]
			fileName=time.time()
			if format in ('.jpg','.png','.jpeg','.HEIC','.jfif'):
				format='.jpg'
			else:
				format='.mp4'
				

			if request.values['folder']=='0':
				return render_template('upload.html',alert='Please choose a folder or creat a folder',dirs=dirs)

			elif request.values['folder']=='1':
				if not os.path.isdir(os.path.join(basepath,session.get('username'),request.values['foldername'])):
					os.mkdir(os.path.join(basepath,session.get('username'),request.values['foldername']))
					os.mkdir(os.path.join(basepath,session.get('username'),request.values['foldername'],'video'))
					os.mkdir(os.path.join(basepath,session.get('username'),request.values['foldername'],'photo'))
				
				if format == '.mp4':
					upload_path = os.path.join(basepath,session.get('username'),request.values['foldername'],'video',str(fileName).replace('.','')+str(format))
				else:
					upload_path = os.path.join(basepath,session.get('username'),request.values['foldername'],'photo',str(fileName).replace('.','')+str(format))
				 
			else:
				if format == '.mp4':
					upload_path = os.path.join(basepath,session.get('username'),dirs[int(request.values['folder'])],'video',str(fileName).replace('.','')+str(format))
				else:
					upload_path = os.path.join(basepath,session.get('username'),dirs[int(request.values['folder'])],'photo',str(fileName).replace('.','')+str(format))

			f.save(upload_path)

		return redirect(url_for('upload'))
	return render_template('upload.html',dirs=dirs)

正如我剛剛前面所說的,我們必須先將使用者所擁有的相簿讓使用者選擇。
所以os.listdir就是在做把路徑內的檔案(包含資料夾)顯示的意思,然後就到我前面的預設,還沒選資料夾和創建新資料夾~
flist = request.files.getlist("file[]")這一行是將剛才我們所選取的檔案放到flist中。順帶一提,如果你是只允許一個檔案的話則使用file=request.files["file"]

接著就是逐一上傳了,首先我做出的行為就是先分清楚他們是什麼檔案,是影片?還是圖片?
然後無論是影片還是圖片,強制轉為mp4和jpg。(如果這樣不好,請跟我說。)
filename=time.time()這是為了避免檔案名稱重複

((插個話,我要說說我之前做過的蠢事。
一開始我還很興奮地把他們直接命名為0 1 2 3 4,現在檔案有幾個就命名為多少。
但,我發現了一個大問題。
當我開始時做刪除時,我發現如果我刪了0 1兩張圖片,下次我再上傳到這個資料夾的時候檔案數為3(剩下234),那我上傳時圖片就會從3 4 5這樣命名,然後我的圖片!就被!刷掉了!
我當時多難過阿,走來走去晃呀晃的,突然想到如果我用時間命名的話,就不會有這樣的問題啦~
但是這樣在讀取照片的時候不是會很麻煩嗎?但我還是辦到了!
蠢事說完畢,我們繼續))

0是沒選資料夾,1是新建資料夾。所以如果你選0,當然是要回傳說忘了選啦~
1的話則開始增加資料夾,並在下面加入photo和video兩個資料夾提供存儲。
其他的就對應到該資料夾囉~
upload_path這是個string,我一開始眼殘看錯,還以為他是什麼特別的東西呢……反正設定好這個檔案的絕對路徑後,就f.save(upload_path)啦!
f.save(upload_path)這意思就是把f這個檔存到upload_path這個路徑

那接下來我們來看實作影片吧~
Yes
剛剛的影片,你們看到了,我也看到了,我忘記處理如果忘記選擇檔案會出錯。那該怎麼辦呢?
我將出錯的flist列出來,他並不為空,他是有預設檔案的長相。
[<FileStorage: '' ('application/octet-stream')>]
上網查後
https://ithelp.ithome.com.tw/upload/images/20190905/201201169WzUdKSteU.png
了解了以後,我們難道不行寫if flist==[<FileStorage: '' ('application/octet-stream')>]就說沒有檔案嗎?
當然不行!你看過list裡面有這種東西嗎!(其實我也是試過才知道)
那怎麼辦呢?只能用try except了
所以我們真正的代碼如下(其實明後天還會做更改XD):

@app.route('/upload/',methods=['GET','POST'])
def upload():

	basepath = os.path.join(os.path.dirname(__file__), 'static','uploads')
	dirs=os.listdir(os.path.join(basepath,session.get('username')))
	dirs.insert(0,'New Folder')
	dirs.insert(0,'Not Choose')

	if request.method == 'POST':
		flist = request.files.getlist("file[]")
		
		for f in flist:
			try:
				basepath = os.path.join(os.path.dirname(__file__), 'static','uploads')
				format=f.filename[f.filename.index('.'):]
				fileName=time.time()
				if format in ('.jpg','.png','.jpeg','.HEIC','.jfif'):
					format='.jpg'
				else:
					format='.mp4'
					

				if request.values['folder']=='0':
					return render_template('upload.html',alert='Please choose a folder or creat a folder',dirs=dirs)

				elif request.values['folder']=='1':
					if not os.path.isdir(os.path.join(basepath,session.get('username'),request.values['foldername'])):
						os.mkdir(os.path.join(basepath,session.get('username'),request.values['foldername']))
						os.mkdir(os.path.join(basepath,session.get('username'),request.values['foldername'],'video'))
						os.mkdir(os.path.join(basepath,session.get('username'),request.values['foldername'],'photo'))
					
					if format == '.mp4':
						upload_path = os.path.join(basepath,session.get('username'),request.values['foldername'],'video',str(fileName).replace('.','')+str(format))
					else:
						upload_path = os.path.join(basepath,session.get('username'),request.values['foldername'],'photo',str(fileName).replace('.','')+str(format))   
				else:
					if format == '.mp4':
						upload_path = os.path.join(basepath,session.get('username'),dirs[int(request.values['folder'])],'video',str(fileName).replace('.','')+str(format))
					else:
						upload_path = os.path.join(basepath,session.get('username'),dirs[int(request.values['folder'])],'photo',str(fileName).replace('.','')+str(format))
				
				
				f.save(upload_path)
			except:
				return render_template('upload.html',alert='你沒有選擇要上傳的檔案',dirs=dirs)

		return redirect(url_for('upload'))
	return render_template('upload.html',dirs=dirs)

再來看真正的影片吧~
Yes

明天要來說說顯示的問題~顯示因為個人擁有些微審美要求的緣故,可能會講個幾天?
啊啊啊!差點忘記了!我說我要跟你們分享我之前做的蠢事!
我不是在七月的時候正在開發這個網路相簿嗎?那個時候天天都在想這個東西呀,這時我媽叫我出門買香雞排來當我們的晚餐(真fashion的晚餐)
我買完了以後,走在路上很無聊,開始確認有沒有買錯(我沒有剛拿到就確認的習慣)
「0、1、2、3……嗯?我不是買四份嗎?0、1、2、3!!!怎麼只有三份!」於是我立刻折回去,想跟店員說這件事。
不過幸好,我走到一半就意識到是我出錯了,然後哭笑不得。
這就是生為資工人的二三事。

https://ithelp.ithome.com.tw/upload/images/20190905/20120116mh5kAheZdT.jpg


上一篇
慢慢帶你了解Flask - Day7 網路相簿(3):註冊登入登出(json、session)
下一篇
慢慢帶你了解Flask - Day9 網路相簿(5):album顯示(upload修改)
系列文
慢慢帶你了解Flask30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
阿瑜
iT邦研究生 4 級 ‧ 2019-09-12 13:04:31

建議這邊用uid 為名,如果遇到同名的,會覆蓋掉原本的內容。

我的帳號就是uid啊~
畢竟只有一個人可以用這個帳號~

我只是沒有用流水號~

阿瑜 iT邦研究生 4 級 ‧ 2019-09-13 16:57:58 檢舉

原來 我以為是名字 ~~~

0
gcobs2336588
iT邦新手 5 級 ‧ 2019-09-24 22:47:03

哈囉大大~
我想請問在一開始還沒替user創建資料夾的時候
執行 dirs=os.listdir(os.path.join(basepath,session.get('username')))
此時的listdir不是會報錯嗎?
另外再請教大大
format=f.filename[f.filename.index('.'):]這一行是什麼意思

是啊,如果沒有創建資料夾,listdir是會error的。
但是我在註冊的時候就幫使用者創建資料夾了,所以不會出現error。

f.filename[f.filename.index('.'):]
我覺得我直接用這個解釋會有點複雜,我舉個例子。

s = "abcde"
s.index('a') #return 0
s.index('b') #return 1
s.index('c') #return 2
s.index('d') #return 3
s.index('e') #return 4

由上面我們可以知道,index()是用來知道,字元在字串的位置。
而我們的f.filename他是你所上傳的檔案名稱,是一個string。
而我們的檔案名稱大多是123.jpg類似這樣
所以f.filename[f.filename.index('.'):]的意思就是,取得這個檔案的類型(jpg、png、mp4)。

感謝大大~
我瞭解了

0
taegdcl
iT邦新手 5 級 ‧ 2021-05-31 14:46:07

哈囉大大~
想請問大大下面這兩行程式要放在app.py中register部分的哪個位置呢?
basepath = os.path.join(os.path.dirname(file), 'static','uploads')
os.mkdir(os.path.join(basepath,request.values['userid']))
https://ithelp.ithome.com.tw/upload/images/20210531/20137972oS7m03ApyM.png
因為我放在圖片中現在這個位置會跳出錯誤~QQ

kyc1109 iT邦新手 4 級 ‧ 2021-08-10 17:00:04 檢舉

你試試看把那兩行跟上一行的return render_template('index.html')對調看看,別忘了縮排。

YUYU iT邦新手 5 級 ‧ 2022-02-25 14:36:54 檢舉

感謝樓上的大大,這個問題我卡了半小時。

我要留言

立即登入留言