1. 程式人生 > 程式設計 >詳解Django自定義圖片和檔案上傳路徑(upload_to)的2種方式

詳解Django自定義圖片和檔案上傳路徑(upload_to)的2種方式

最近在做一個仿知乎網站的專案了,裡面涉及很多圖片和檔案上傳。趁此機會我給大家總結下Django自定義圖片和檔案上傳路徑的2種方式吧。

方法1: 在Django模型中定義upload_to選項。

Django模型中的ImageField和FileField的upload_to選項是必填項,其儲存路徑是相對於MEIDA_ROOT而來的。

我們來看一個簡單案例(如下所示)。如果你的MEDIA_ROOT是/media/資料夾,而你的上傳資料夾upload_to=“avatar",那麼你上傳的檔案會自動儲存到/media/avatar/資料夾。

class UserProfile(models.Model):
 
  user = models.OneToOneField(User,on_delete=models.CASCADE,related_name='profile')
  avatar = models.ImageField(upload_to='avatar',verbose_name="頭像")

如果你的檔名是sky.jpg,那麼圖片上傳後資料庫中的avatar欄位為avatar/sky.jpg,該欄位指向圖片物件,而非絕對路徑。要在模板中使用該圖片,應該使用avatar.url (即/media/avatar/sky.jpg)。

但在實際應用中,請千萬別這麼做。這裡有2個嚴重問題。

  • 所有使用者都把頭像上傳到了同一個avatar檔案夾了
  • 原檔名是什麼,那麼新檔名就是什麼

試想使用者很多,很可能發生檔案重名問題,造成後來使用者上傳的檔案把前面使用者上傳的頭像覆蓋了,造成了使用者A掛使用者B頭像的狀況。

正確的做法是動態定義上傳路徑,把圖片儲存到使用者自己的資料夾下,並對其重新命名。如下圖所示。這樣圖片就會儲存在/media/1/avatar/裡了,而且檔案以uuid命名。

from django.db import models
from django.contrib.auth.models import User
import uuid
 
# Create your models here.
 
def user_directory_path(instance,filename):
  ext = filename.split('.')[-1]
  filename = '{}.{}'.format(uuid.uuid4().hex[:8],ext)
  # return the whole path to the file
  return "{0}/{1}/{2}".format(instance.user.id,"avatar",filename)
 
class UserProfile(models.Model):
  user = models.OneToOneField(User,related_name='profile')
  avatar = models.ImageField(upload_to=user_directory_path,verbose_name="頭像")

上述案例顯然還有一個問題,不同系統路徑分隔符/和\是不一樣的,為保證程式碼在不同系統中能重用,更好的方式是使用python的os模組來拼接路徑。如下圖所示。

from django.db import models
from django.contrib.auth.models import User
import uuid
import os
 
# Create your models here.
 
def user_directory_path(instance,filename):
  ext = filename.split('.')[-1]
  filename = '{}.{}'.format(uuid.uuid4().hex[:10],ext)
  # return the whole path to the file
  return os.path.join(instance.user.id,verbose_name="頭像")

使用者上傳檔案可能是圖片,也可能是pdf檔案,我們如何把它們放在同一使用者的不同資料夾下呢?實現這個很簡單,如下圖所示。

def user_directory_path(instance,ext)
  sub_folder = 'file'
  if ext.lower() in ["jpg","png","gif"]:
    sub_folder = "avatar"
  if ext.lower() in ["pdf","docx"]:
    sub_folder = "document"
  return os.path.join(instance.user.id,sub_folder,filename)

方法2: 在檢視中自定義上傳圖片或檔案路徑

方法1最簡單直白,但有一個較大缺陷,檔案上傳後未經處理就直接儲存了。假如使用者上傳了圖片,我們希望先對其壓縮或裁剪,然後再儲存,或者我們不希望上傳圖片或檔案到預設的路徑,這時我們就有必要在檢視中自定義圖片或檔案路徑了。例子如下。

@login_required
def ajax_avatar_upload(request):
  user = request.user
  user_profile = get_object_or_404(UserProfile,user=user)
 
  if request.method == "POST":
    form = AvatarUploadForm(request.POST,request.FILES)
    if form.is_valid():
      img = request.FILES['avatar_file'] # 獲取上傳圖片
      cropped_avatar = crop_image(img,user.id)
      user_profile.avatar = cropped_avatar # 將圖片路徑修改到當前會員資料庫
     user_profile.save()
  return HttpResponseRedirect(reverse('myaccount:profile'))
 
 
def crop_image(file,uid):
 
  # 隨機生成新的圖片名,自定義路徑。
  ext = file.name.split('.')[-1]
  file_name = '{}.{}'.format(uuid.uuid4().hex[:10],ext)
  cropped_avatar = os.path.join(uid,file_name)
  # 相對根目錄路徑
  file_path = os.path.join("media",uid,file_name)
 
  # 裁剪圖片,壓縮尺寸為200*200。
  img = Image.open(file)
  crop_im = img.crop((50,50,300,300)).resize((200,200),Image.ANTIALIAS)
  crop_im.save(file_path)
 
  return cropped_avatar

到此這篇關於詳解Django自定義圖片和檔案上傳路徑(upload_to)的2種方式的文章就介紹到這了,更多相關Django 上傳路徑內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!