iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0

在實作上傳檔案的功能之前,我們先幫文章模型加上封面圖片的欄位:

# server/app/blog/models.py
# ... 省略
class Post(BaseModel):
    # ... 省略
+   cover_image = models.ImageField(
+       "封面圖片",
+       upload_to="post/cover_image/%Y%m%d/",
+       null=True,
+       blank=True,
+   )
		# ... 省略

# ... 省略

上面我們設定一個圖片欄位,所以需要再安裝處理圖片的套件:

$ poetry add Pillow

產生新的資料庫遷移檔:

$ poetry shell
$ python manage.py makemigrations
Migrations for 'blog':
  server/app/blog/migrations/0002_post_cover_image.py
    - Add field cover_image to post

更新資料庫 Schema:

$ python manage.py migrate       
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
  Applying blog.0002_post_cover_image... OK

設定 Django 上傳檔案路徑:

# server/settings.py
STATIC_URL = "static/"

+MEDIA_URL = "media/"

+MEDIA_ROOT = BASE_DIR / "media"

圖片或是檔案都是 Binary 形式的資料,前面所做的 GraphQL 都是文字形式的資料,那麼在 GraphQL 該如何做到上傳檔案呢?

其實就跟傳統的 API 一樣是 HTTP Post,並使用Content-Type: multipart/form-data;,只是我個人覺得用起來麻煩許多。

下面我們先新增上傳文章封面的功能:

# server/app/blog/graph/types.py
# ... 省略
@strawberry_django.type(blog_models.Post)
class Post:
    # ... 省略
+   cover_image: strawberry.auto
		# ... 省略

# ... 省略
# server/app/blog/graph/mutations.py
# ... 省略
import strawberry
import strawberry_django
from django.core.exceptions import ValidationError
from django.utils import timezone
+from strawberry.file_uploads import Upload
from strawberry.utils.str_converters import to_camel_case
from strawberry_django import mutations

# ... 省略

@strawberry.type
class Mutation:
		# ... 省略
+   @strawberry_django.mutation(handle_django_errors=True)
+   def upload_post_cover_image(
+       self,
+       post_id: uuid.UUID,
+       file: Upload,
+   ) -> blog_types.Post:
+       post = blog_models.Post.objects.get(pk=post_id)
+       post.cover_image = file  # type: ignore
+       post.save()
+       return typing.cast(blog_types.Post, post)

接著到 GraphiQL 頁面設定格式:

https://ithelp.ithome.com.tw/upload/images/20231006/20161957KcbK9NqIo1.png

mutation PostImageMutation(
  $file: Upload!, $postId: UUID!
) {
  uploadPostCoverImage(
    file: $file, postId: $postId
  ) {
    ... on Post {
      id
      coverImage {
        height
        name
        path
        size
        url
        width
      }
    }
    ... on OperationInfo {
      __typename
      messages {
        code
        field
        kind
        message
      }
    }
  }
}

然後會發現沒辦法測試,但是沒關係,我們打開瀏覽器(我是使用 Brave)的檢查,選到Network的頁籤,接著再執行一次 GraphQL 操作,就會看請求的紀錄,點開 GraphQL 請求紀錄,選到 Payload,點擊view source,最後將 Request Payload 的字串內容複製起來。

https://ithelp.ithome.com.tw/upload/images/20231006/201619578rVr76Wa8L.png

{"query":"mutation PostImageMutation(\n  $file: Upload!, $postId: UUID!\n) {\n  uploadPostCoverImage(\n    file: $file, postId: $postId\n  ) {\n    ... on Post {\n      id\n      coverImage {\n        height\n        name\n        path\n        size\n        url\n        width\n      }\n    }\n    ... on OperationInfo {\n      __typename\n      messages {\n        code\n        field\n        kind\n        message\n      }\n    }\n  }\n}","variables":{"file":null,"postId":"4406cce1-bbbc-4788-8086-d33fcc16ee97"},"operationName":"PostImageMutation"}

GraphQL 使用multipart/form-data主要會有三個部分:

  1. operations:GraphQL 操作格式,如上面複製的 Request Payload 的字串內容。
  2. map:GraphQL 變數與表單檔案欄位的對應。
  3. 表單檔案欄位,可以一個或多個。

下面使用 Postman 來測試文章上傳封面圖片:

https://ithelp.ithome.com.tw/upload/images/20231006/20161957SkChITWQe7.png

在 Postman 的介面上有 12 個步驟,打開新的請求畫面做以下操作:

  1. HTTP 方法選到POST
  2. 填入請求網址,這邊我們 GraphQL 的 URL 是http://127.0.0.1:8000/graphql/
  3. 選到 Body 頁籤。
  4. 點選 form-data,Postman 會自動幫我們設定 Headers 裡加上Content-Type: multipart/form-data;
  5. 設定第一個表單欄位,operations
  6. 設定第一個表單欄位值,貼上複製的 Request Payload 字串。
  7. 設定第二個表單欄位,map
  8. 設定第二個表單欄位值,{ "file": ["variables.file"]},代表下面要設定一個file欄位,它對應到 GraphQL 變數 filevariables.file)。
  9. 設定第二個表單欄位,file,是根據上面map的值所定義的欄位新增的。
  10. 點開 Postman 表單欄位類型選單。
  11. 欄位類型選擇 File
  12. 選擇要上傳的圖片。
  13. 執行請求。

Postman 可以看到回應的 JSON 資料。

最後回到 Django 專案目錄下看:

├── media
│   └── post
│       └── cover_image
│           └── 20231005
│               └── hpath.png

確認圖片有存在我們指定的路徑下。

上傳多個檔案的範例可以參考 Strawberry 官方文件 [1]。

雖然 GraphQL 有支援multipart/form-data,我自己還是比較喜歡另外寫檔案相關處理的 REST API。

這次修改內容可以參考 Git commit:https://github.com/JiaWeiXie/django-graphql-tutorial/commit/c1c9394128fab8b97c752138ff04cbcbbaf97f9f

參考資料


上一篇
Day 20:Strawberry Django 資料驗證與錯誤處理
下一篇
Day 22:Strawberry Django Relay
系列文
Django 與 Strawberry GraphQL:探索現代 API 開發之路30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言