iT邦幫忙

2023 iThome 鐵人賽

DAY 19
0

這篇我們就來實作 strawberry_django 的變更(Mutations)相關功能。

strawberry_django 內建模型新增(Ctreate)、修改(Update)、刪除(Delete)方法的變更功能。

在實作之前,先新增一個放變更相關程式的 Python 檔案:

$ touch server/app/blog/graph/mutations.py

接下來來實作文章的新增功能,首先我們先在server/app/blog/graph/types.py增加文章的輸入型態:

# server/app/blog/graph/types.py
# ... 省略
+import strawberry
import strawberry_django

# ... 省略

+@strawberry_django.input(blog_models.Post)
+class PostInput:
+   slug: str
+   title: str
+   content: str
+   author: strawberry.auto
+   tags: strawberry.auto
+   categories: strawberry.auto

上面增加了PostInput,其中有關聯的欄位都先透過strawberry.auto來自動推斷型態。

然後在server/app/blog/graph/mutations.py新增Mutation型態,後面加進Schema中:

# server/app/blog/graph/mutations.py
import strawberry
from strawberry_django import mutations

from server.app.blog.graph import types as blog_types

@strawberry.type
class Mutation:
    create_post: blog_types.Post = mutations.create(blog_types.PostInput)
# server/schema.py
from server.app.authentication.graph import queries as auth_queries
+from server.app.blog.graph import mutations as blog_mutations
from server.app.blog.graph import queries as blog_queries

__all__ = ("schema",)

query = strawberry.tools.merge_types(
    "Query",
    (blog_queries.Query, auth_queries.Query),
)
+mutation = strawberry.tools.merge_types(
+   "Mutation",
+   (blog_mutations.Mutation,),
+)

-schema = strawberry.Schema(query=query)
+schema = strawberry.Schema(query=query, mutation=mutation)

接著就可以試著使用 GraphQL 新增文章:

https://ithelp.ithome.com.tw/upload/images/20231004/20161957cRbjCUseVl.png

mutation MyMutation($postData: PostInput!) {
  createPost(data: $postData) {
    slug
    publishedAt
    published
    id
    content
    title
    tags {
      name
    }
    categories {
      name
      path
    }
  }
}
{
  "postData": {
    "slug": "testing02",
    "title": "第二篇測試",
    "content": "這是第二篇測試文章",
    "author": {
      "set": "user_id"
    },
    "categories": {
      "set": [
        "category_uuid"
      ]
    },
    "tags": {
      "set": [
        "tag_uuid"
      ]
    }
  }
}

上面的新增文章變更,可以看到PostInput中跟有關聯的欄位都會需要再多給set欄位值,而且set欄位的輸入都只能給pk,另外可以看到多對多的欄位裡面還會多出addremove欄位,在多對多的欄位中set欄位會幫我們處理Django ManyToManyField 的資料addremoveManyToManyInputaddremove欄位是各自處理的欄位,並且addremove欄位有使用到的時候就不能使用set欄位。

接下來是使用 strawberry_django 內建的更新功能,下面實作更新文章:

# server/app/blog/graph/types.py
# ... 省略
+@strawberry_django.partial(blog_models.Post)
+class PostInputPartial:
+   id: strawberry.auto  # noqa: A003
+   slug: strawberry.auto
+   title: strawberry.auto
+   content: strawberry.auto
+   tags: strawberry.auto
+   categories: strawberry.auto
+   published_at: datetime.datetime | None
+   published: bool | None
# server/app/blog/graph/mutations.py
# ... 省略
@strawberry.type
class Mutation:
    create_post: blog_types.Post = mutations.create(blog_types.PostInput)
+   update_post: blog_types.Post = mutations.update(blog_types.PostInputPartial)

https://ithelp.ithome.com.tw/upload/images/20231004/20161957FhPI0K1WJM.png

mutation MyMutation($postData: PostInputPartial!) {
  updatePost(data: $postData) {
    content
    id
    published
    publishedAt
    slug
    title
    content
    author {
      id
      username
    }
    tags {
      name
    }
    comments {
      content
    }
    categories {
      name
    }
  }
}
{
  "postData": {
    "id": "post_uuid",
    "slug": "testing02",
    "title": "第二篇測試",
    "content": "修改-這是第二篇測試文章!!!"
    
  }
}

內建的變更功能,對於模型的資料查詢都是使用pk,所以要能夠更新目標資料就一定要傳pk

使用strawberry_django.partial定義的輸入型態,裡面定義的欄位型態使用strawberry.auto,正常來說都會將原來的型態轉成可選型態(Optional),另外strawberry_django.partial等於strawberry_django.input(partial=True)

再接下來是內建的刪除功能:

# server/app/blog/graph/types.py
# ... 省略
+@strawberry_django.input(blog_models.Post)
+class PostIdInput:
+   id: strawberry.auto  # noqa: A003
# server/app/blog/graph/mutations.py
# ... 省略
@strawberry.type
class Mutation:
    create_post: blog_types.Post = mutations.create(blog_types.PostInput)
    update_post: blog_types.Post = mutations.update(blog_types.PostInputPartial)
+   delete_post: blog_types.Post = mutations.delete(blog_types.PostIdInput)

https://ithelp.ithome.com.tw/upload/images/20231004/20161957UjAaLmL3gV.png

mutation MyMutation($postId: UUID) {
  deletePost(data: {id: $postId}) {
    id
    published
    publishedAt
    slug
    title
  }
}

如同前面更新功能所說,內建的變更功能都是基於pk做查詢,所以刪除功能的輸入就是帶入id,進行刪除資料。

前面所提到的內建變更功能,雖然在一些單純的情境下有它的方便的地方,但是可以看到它缺乏自定義的彈性,而且在有關聯的欄位上,輸入的格式會比較多階層,那如果我們不想用內建的變更那該怎麼做?
下面實作一個發布文章功能當作範例:

# server/app/blog/graph/mutations.py
+import typing
+import uuid

import strawberry
+import strawberry_django
+from django.utils import timezone
from strawberry_django import mutations

+from server.app.blog import models as blog_models
from server.app.blog.graph import types as blog_types

@strawberry.type
class Mutation:
    create_post: blog_types.Post = mutations.create(blog_types.PostInput)
    update_post: blog_types.Post = mutations.update(blog_types.PostInputPartial)
    delete_post: blog_types.Post = mutations.delete(blog_types.PostIdInput)

+   @strawberry_django.input_mutation(handle_django_errors=True)
+   def publish_post(self, id: uuid.UUID) -> blog_types.Post:  # noqa: A002
+       post = blog_models.Post.objects.get(pk=id)
+       if not post.published:
+           post.published = True
+           post.published_at = timezone.now()
+           post.save()
+       return typing.cast(blog_types.Post, post)

https://ithelp.ithome.com.tw/upload/images/20231004/20161957Py7SQUbzgq.png

strawberry_django 提供變更欄位的定義方法,strawberry_django.input_mutationstrawberry_django.mutationinput_mutation會將引數用input引數包起來,而handle_django_errors會幫我們把 Django 的ValidationErrorPermissionDeniedObjectDoesNotExist這幾種例外錯誤做處理。

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

參考資料


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

尚未有邦友留言

立即登入留言