這篇我們就來實作 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 新增文章:
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
,另外可以看到多對多的欄位裡面還會多出add
跟remove
欄位,在多對多的欄位中set
欄位會幫我們處理Django ManyToManyField 的資料add
跟remove
,ManyToManyInput
的add
跟remove
欄位是各自處理的欄位,並且add
跟remove
欄位有使用到的時候就不能使用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)
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)
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)
strawberry_django 提供變更欄位的定義方法,strawberry_django.input_mutation
與 strawberry_django.mutation
,input_mutation
會將引數用input
引數包起來,而handle_django_errors
會幫我們把 Django 的ValidationError
、PermissionDenied
、ObjectDoesNotExist
這幾種例外錯誤做處理。
這次修改內容可以參考 Git commit:https://github.com/JiaWeiXie/django-graphql-tutorial/commit/6142fc5543a891e8a8d9615b75603b8abc34f2a4