# 目標

解決原本圖片路徑只有純文字的情況:

before_image_upload.png

# 後端

# views

  • 要得知是「哪一個產品」需要更新圖片,所以需傳入 product_idrequest.FILES.get('image') 取得上傳的圖片檔案
  • 先不用規範 permission_classes,這樣前端才不用在 config 額外取 token
@api_view(['POST'])
def uploadImage(request):
    data = request.data
    product_id = data['product_id']
    product = Product.objects.get(id=product_id)
    product.image = request.FILES.get('image')
    product.save()
    return Response('圖片上傳成功!')

# urls

path('upload/', views.uploadProductImage,name="product_image_upload"),

# 前端

# 產品編輯前端頁面

  • 上傳 Handler 函式必須得是 async,因為要等待上傳完成後再執行下一步
  • <Form.File 相關寫法已在 react-bootstrap V5 版本以上被棄用,改用 <Form.Control type='file' />
// ProductEditPage.js
const uploadImageHandler = async (e) => {
  console.log(e.target.value);
};

return(
  ...
  <Form.Group controlId='image' className='mb-3'>
    <Form.Label>產品圖片</Form.Label>
    <Form.Control required type='text' placeholder='請輸入產品圖片' value={image} onChange={(e) => setImage(e.target.value)}></Form.Control>
    <Form.Control type='file' onChange={uploadImageHandler} />
  </Form.Group>
  {ImageUploading && <Loader />}
)

逐步慢慢測試,先查看上傳檔案後,主控台是否有印出檔案名稱

upload_test.png

確定有擷取到檔案後,用 e.target.files[0]; 取得檔案陣列中的第一個檔案,並宣告一個 FormData 物件,將檔案名稱、產品 ID 一併帶入 FormData 物件傳給後端

// ProductEditPage.js
const uploadImageHandler = async (e) => {
 const file = e.target.files[0];
 const formData = new FormData();

 formData.append("product_image", file); // product_image 後端 uploadProductImage view 接收的參數名稱
 formData.append("product_id", productID);
 try {
  const { data } = await axios.post("/api/products/upload/", formData); // 呼叫後端 uploadProductImage view
  setImage(data); // 後端的 return Response 值
  setImageUploading(false); // 避免重複點擊
 } catch (error) {
  setImageUploading(false);
 }
};
  • 使用 FormData 作為請求體時,Axios 會自動檢測並設置適當的 Content-Type,不需要自己寫 config 設定,若後端有額外規定 permission_classes 就需要在config 設定 token
  • /api/products/upload/ 的路徑一定要跟後端 urls.py 設定的一模一樣,原本在 upload 這少加了 /,一直報 405 Error

完成後,應就能看到產品成功更換圖片,去專案的 static 資料夾下,也能看到上傳的新圖片檔

image_upload_done.png

# 更改上傳圖片目的地

假設原本上傳圖片的位置是存放到 niceshop/backend/static/images 資料夾下,但經過考量想改將存放用戶上傳圖片的位置改成 niceshop/backend/media/

  • STATIC_ROOT(和 STATICFILES_DIRS)主要存放「前端靜態資源」(JS、CSS、圖片)。
  • MEDIA_ROOT 主要存放「用戶上傳的檔案」,如:產品圖片、頭像、附件等。

此時需要更改的地方有:

# settings.py
# MEDIA_URL = '/images/' # 改成 -> MEDIA_URL = '/media/'
MEDIA_URL = '/media/'  # 圖片的網址,需要去urls.py新增pattern指定此url
...
# MEDIA_ROOT = BASE_DIR / 'static/images'  # 改成 -> MEDIA_ROOT = BASE_DIR / 'media/'
MEDIA_ROOT = BASE_DIR / 'media'  # 存放用戶上傳的檔案,沒有的話就會直接上傳在根目錄 backend/xxx.png

另外產品圖片預設上傳路徑若是如下:

# models.py
class Product(models.Model):
    image = models.ImageField(upload_to='products/', null=True, blank=True,default='products/placeholder.jpg')
...

此時用戶上傳產品圖就會存到 niceshop/backend/media/products/ 資料夾下,能有效區分前端靜態資源與用戶上傳檔案