自訂上傳圖片目的地
透過 imgur 來管理專案圖片是不理想的且 imgur 的圖片連結也會隨著時間而失效
MARTOR_UPLOAD_PATH
: 儲存圖片的資料夾路徑目的地
MARTOR_UPLOAD_URL
: 上傳圖片的 API URL(views)
1 2 3 4 5 6
| ...
MARTOR_UPLOAD_PATH = 'images/uploads/{}'.format(time.strftime("%Y/%m/%d/")) MARTOR_UPLOAD_URL = '/api/blog/image-uploader/' MAX_IMAGE_UPLOAD_SIZE = 5 * 1024 * 1024
|
views - 第一版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @csrf_exempt def test_view(request): if request.method == 'POST': if 'markdown-image-upload' in request.FILES: image = request.FILES['markdown-image-upload'] filename = image.name
img_uuid = "{0}-{1}".format(uuid.uuid4().hex[:10], filename.replace(' ', '-'))
upload_path = os.path.join(settings.MEDIA_ROOT, 'images/uploads/') if not os.path.exists(upload_path): os.makedirs(upload_path)
file_path = 'images/uploads/' + img_uuid saved_path = default_storage.save(file_path, ContentFile(image.read()))
return JsonResponse({ 'status': 200, 'link': settings.MEDIA_URL + saved_path, 'name': filename }) else: return JsonResponse({ 'status': 400, 'error': 'No image found' }) return HttpResponse(f"Method {request.method} received!")
|
urls
1 2 3 4 5 6
| urlpatterns = [ path('image-uploader/', test_view, name='test_image_uploader'), ... ]
|
結果:

views - 第二版本
那確定上傳至本地資料夾能成功運作後,就來升級一下功能,從原本的「生成一個隨機的文件名」- img_uuid = "{0}-{1}".format(uuid.uuid4().hex[:10], filename.replace(' ', '-'))
改成「根據文章的 slug 來生成對應的資料夾名稱」,這樣就能將同一個文章的所有圖片都放在同一資料夾中,提高日後圖片管理的效率跟方便性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| @csrf_exempt def test_view(request): if request.method == 'POST': if 'markdown-image-upload' in request.FILES: image = request.FILES['markdown-image-upload']
post_slug = None referer = request.META.get('HTTP_REFERER', '')
if 'admin/blog/post/' in referer: try: post_id = referer.split('/admin/blog/post/')[1].split('/')[0] if post_id.isdigit(): post = Post.objects.get(id=int(post_id)) post_slug = post.slug except (IndexError, ValueError, Post.DoesNotExist): pass
filename = image.name
img_uuid = "{0}-{1}".format(uuid.uuid4().hex[:10], filename.replace(' ', '-'))
if post_slug: relative_path = f'posts_images/{post_slug}/' else: relative_path = 'posts_images/uploads/' upload_path = os.path.join(settings.MEDIA_ROOT, relative_path) if not os.path.exists(upload_path): os.makedirs(upload_path)
file_path = os.path.join(relative_path, img_uuid) saved_path = default_storage.save(file_path, ContentFile(image.read())) return JsonResponse({ 'status': 200, 'link': settings.MEDIA_URL + saved_path, 'name': filename }) else: return JsonResponse({ 'status': 400, 'error': '未找到上傳的圖片' }) return HttpResponse(f"收到 {request.method} 請求,但上傳圖片需要 POST 請求")
|
可以看到相較與第一個版本的,這一段多了:
- 獲取當前編輯的文章的 slug
HTTP_REFERER
- 從 referer 中獲取文章 ID 的程式
- 保存文件的路徑
file_path
從 ‘images/uploads/’ + img_uuid 改成 posts_images/{post_slug}/
,這樣就能將同一篇文章的所有圖片都放在同一個資料夾中
這邊要注意為何是要先從 referer 中獲取文章 ID,而不是 slug?我們不是要創建以該文章的 slug 命名的資料夾嗎?
A:因為 Django Admin URL 結構的關係,Django Admin 預設使用主鍵 ID 而非 slug,slug 反而是後來透過 models.py 新增的自訂欄位,如圖:

所以我們要先從 referer 中獲取文章 ID,然後再透過 Post.objects.get(id=int(post_id))
來獲取該篇文章的 slug,這樣即使在創建新文章時(slug 可能還沒有設置),也能通過 ID 找到正確的文章
成功示意圖:

settings 更新 MEDIA_ROOT
重點是原本由於 MEDIA_ROOT
初始設定的關係,導致上傳的圖片會存放在 backend/media/
目錄下,前端會無法正確顯示圖片,因為前端的圖片路徑是直接從 media/
目錄下讀取的

所以在 settings.py
中將 MEDIA_ROOT
更新成:
1 2
| MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'media')
|
這樣未來用戶上傳圖片就不會存放在 backend/media/
目錄下了,而是直接存放在 media/
目錄下,這樣前端的圖片即可正常顯示

成功畫面
這樣特定的文章的圖片就會存放在以該文章的 slug 命名的資料夾中了,也不怕所有文章的圖片都混雜在同一個資料夾中,不好管理的問題

若您覺得這篇文章對您有幫助,歡迎分享出去讓更多人看到⊂◉‿◉つ~
留言版