【免费下载链接】howtographql
The Fullstack Tutorial for GraphQL
本教程基于 howtographql 仓库的 graphql-python 系列 中的核心章节,讲解在 Django 与 Graphene 搭建的 GraphQL 服务器中,如何把用户(User)挂接到链接(Link)上、为链接添加投票(Vote)功能,并打通三者之间的查询关系。读完本文,你将掌握:通过settings.AUTH_USER_MODEL建立模型外键、在 Graphene Mutation 中读写当前登录用户、构造带权限校验的CreateVote投票 Mutation,以及用DjangoObjectType暴露关联查询。
该章节承接 用户认证章节(使用django-graphql-jwt实现 Token 认证),也为此后章节中的 过滤、分页 与 Relay 重构 打好了数据模型基础。
一、整体思路:Hackernews 的两大核心功能
在系列教程的 Getting Started 章节中已经明确,这个 Hackernews 克隆需要具备三大能力:用户与链接列表、用户创建与认证、用户创建链接并为其投票。本章解决的正是最后一项:
- 让
Link记录是谁发布的(posted_by外键指向用户); - 新增
Vote模型,记录"哪个用户投了哪个链接"; - 提供
CreateLink(带作者)与CreateVote两个 Mutation; - 通过
VoteType与votes字段,让客户端能查询到投票数据及每个链接的关联投票。
在此之前,你已经完成了 基础查询(LinkType、links查询)、基础 Mutation(CreateLink)与 用户认证(usersapp、UserType、tokenAuth等 JWT Mutation)。本章就是把这些能力串起来:让链接归属到用户,让用户能对链接投票。
二、把用户挂到链接上:posted_by外键
1. 修改模型:引入 Django 内置用户模型
认证章节里,我们通过django.contrib.auth的get_user_model()创建了用户。要把链接与用户关联,最规范的方式不是硬编码自定义用户模型名,而是引用settings.AUTH_USER_MODEL,这样后续即使替换了自定义用户模型,代码也无需改动。
在links/models.py顶部导入 Django 设置:
from django.conf import settings # ...code然后在Link模型末尾追加posted_by字段:
posted_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)这里有两个参数值得注意:
null=True:允许posted_by为空。这意味着匿名(未登录)用户创建链接时,该字段可存NULL,不会因为缺少用户而报错;on_delete=models.CASCADE:当关联的用户被删除时,其名下所有链接一并删除,保持数据一致性(Django 2.x 之后on_delete为必填参数)。
2. 同步数据库
模型变更后,需要让 Django 生成迁移并应用到数据库:
python manage.py makemigrations python manage.py migrate第一条命令扫描模型变化生成迁移文件,第二条命令将其写入数据库(本系列使用开箱即用的 SQLite)。这与 Queries 章节 中创建Link模型后的流程完全一致。
3. 改造CreateLink:返回发布者
接下来修改links/schema.py。先导入认证章节定义的UserType:
from users.schema import UserType然后把CreateLink改为带posted_by输出的完整版本:
class CreateLink(graphene.Mutation): id = graphene.Int() url = graphene.String() description = graphene.String() posted_by = graphene.Field(UserType) class Arguments: url = graphene.String() description = graphene.String() def mutate(self, info, url, description): user = info.context.user or None link = Link( url=url, description=description, posted_by=user, ) link.save() return CreateLink( id=link.id, url=link.url, description=link.description, posted_by=link.posted_by, )对比 Mutations 章节 中的原始版本,核心变化有两处:
- 输出字段新增
posted_by = graphene.Field(UserType),客户端可以像查询User类型一样自由选择username、email等字段——这正是 认证章节 中强调的"返回完整对象而非逐字段拼接"; mutate方法通过info.context.user读取当前请求的用户。这里的info是 GraphQL 解析上下文,info.context.user由django-graphql-jwt的JSONWebTokenMiddleware在认证章节的配置中注入:请求携带合法 Token 时它就是当前登录用户;未携带时则为AnonymousUser。or None保证匿名用户场景下posted_by写入NULL,与模型上的null=True呼应。
测试时记得带上 Token:在 Insomnia 的Header选项卡中添加AUTHORIZATION请求头,值为JWT <token>(详见 认证章节的测试部分),再发送createLinkMutation。若一切正常,返回结果中会包含postedBy对象,展示当前用户的字段信息。
三、添加投票:Vote模型与CreateVoteMutation
Hackernews 的核心玩法是投票:链接被投得越多就越热门。投票涉及两个主体——用户和链接,因此需要一张关联表。
1. 定义Vote模型
在links/models.py中追加:
class Vote(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) link = models.ForeignKey('links.Link', related_name='votes', on_delete=models.CASCADE)解读:
user外键指向用户模型,on_delete=CASCADE表示用户删除时投票记录一并删除;link外键指向links.Link(注意这里使用字符串引用'links.Link'也是 Django 支持的写法),related_name='votes'非常关键——它定义了反向关系名,使得Link对象可以通过link.votes访问其全部投票记录,后续查询章节会用到。
同样执行数据库同步:
python manage.py makemigrations python manage.py migrate2. 定义CreateVoteMutation
在links/schema.py中,先把模型导入改为同时引入Vote:
from links.models import Link, Vote然后添加投票 Mutation:
class CreateVote(graphene.Mutation): user = graphene.Field(UserType) link = graphene.Field(LinkType) class Arguments: link_id = graphene.Int() def mutate(self, info, link_id): user = info.context.user if user.is_anonymous: raise Exception('You must be logged to vote!') link = Link.objects.filter(id=link_id).first() if not link: raise Exception('Invalid Link!') Vote.objects.create( user=user, link=link, ) return CreateVote(user=user, link=link)最后把新 Mutation 注册到根 Mutation 类:
class Mutation(graphene.ObjectType): create_link = CreateLink.Field() create_vote = CreateVote.Field()这个 Mutation 展示了 GraphQL 服务端的典型"三步校验"模式:
| 步骤 | 代码 | 作用 |
|---|---|---|
| 身份校验 | user.is_anonymous判断 | 匿名用户禁止投票,抛出'You must be logged to vote!' |
| 数据校验 | Link.objects.filter(id=link_id).first() | 使用first()而非get(),链接不存在时得到None而非抛DoesNotExist,随后抛出'Invalid Link!' |
| 落库 | Vote.objects.create(...) | 创建投票记录并返回CreateVote(user=user, link=link)作为输出 |
两种异常在当前章节中都通过raise Exception('message')抛出;在后续的 Error Handling 章节 中,其中一条会被替换为from graphql import GraphQLError的raise GraphQLError(...)——两者都会中止执行并把括号内的消息返回给客户端,效果等价。
测试时对第一个链接发起createVoteMutation(携带 Token),成功后会返回投票者用户对象与被投票的链接对象。
四、打通查询:VoteType与votes字段
投票能写入,但还看不到——此时需要把投票数据暴露到查询层。
1. 定义VoteType
在links/schema.py中,紧跟LinkType之后添加:
class VoteType(DjangoObjectType): class Meta: model = VoteDjangoObjectType是 Graphene-Django 提供的桥接类型,会根据 Django 模型自动生成对应的 GraphQL 字段。由于Vote模型上的user与link都是外键,VoteType会天然暴露user和link两个关联对象字段,客户端可以嵌套查询"谁投了哪个链接"。
2. 扩展 Query:新增votes字段与解析器
class Query(graphene.ObjectType): links = graphene.List(LinkType) votes = graphene.List(VoteType) def resolve_links(self, info, **kwargs): return Link.objects.all() def resolve_votes(self, info, **kwargs): return Vote.objects.all()resolve_votes返回Vote.objects.all(),即所有投票记录。查询示例:
query { votes { id user { username } link { id url } } }3. 反向关联:从链接看投票
因为模型中设置了related_name='votes',客户端还可以从链接一侧反向查询:
query { links { id url votes { id user { username } } } }这就是"Relating Links and Votes"一节要达成的目标:LinkType借助 Django ORM 的反向关系(link.votes)自动获得votes字段,无需再手写解析器。Graphene-Django 会为模型上的每个关系自动生成可查询字段,这种"模型即类型"的映射正是DjangoObjectType的核心价值。
五、本章在完整教程中的位置与延伸
投票与关联打通后,本系列后续章节都是在此基础上继续演进:
- Error Handling:把
CreateVote中的raise Exception('You must be logged to vote!')换成raise GraphQLError(...),并演示对无效链接投票时的错误响应; - Filtering:
Query在links/votes基础上增加search参数,通过django.db.models.Q实现url__icontains与description__icontains的模糊搜索; - Pagination:为
links查询增加first/skip参数,用 Python 切片对查询集分页; - Relay:在
schema_relay.py中为Link/Vote建立LinkNode/VoteNode,并用DjangoFilterConnectionField与graphene.relay.ClientIDMutation重构本节的查询与投票功能。
可以看到,本章设计的Vote模型、VoteType与"外键 + related_name"的关联方式,是整个教程数据层的中枢:过滤、分页、Relay 章节都直接复用了这里的模型结构与查询字段。
六、小结:本节的三个关键设计
- 用
settings.AUTH_USER_MODEL而非硬编码用户类,保证用户模型可替换性;null=True让匿名创建的链接合法存在。 - Mutation 的校验顺序:先验身份(
is_anonymous),再验数据(first()判空),最后落库返回完整对象——这个模式可复用于几乎所有需要用户态的写操作。 DjangoObjectType+related_name自动打通关联查询:VoteType暴露双向关系,客户端既可查"所有投票",也可从link.votes反向看每个链接的投票列表,全程无需手写关联解析器。
完成本节后,你的 Hackernews GraphQL 服务器已经具备完整的"发布—投票—查询"闭环。继续阅读 Error Handling 章节,学习如何用GraphQLError让错误信息更加规范。
【免费下载链接】howtographql
The Fullstack Tutorial for GraphQL
相关推荐
在 Graphene-Django 中为 GraphQL 查询添加搜索过滤:Hackernews 链接搜索实战(howtographql Python 教程)
在 Graphene Django 中为 GraphQL 查询添加搜索过滤:Hackernews 链接搜索实战(howtographql Python 教程)
3个场景告诉你:为什么KoboldCpp是本地AI模型部署的最佳选择?
3个场景告诉你:为什么KoboldCpp是本地AI模型部署的最佳选择? 你是否曾想过,在本地运行强大的AI模型可以像打开一个普通软件那样简单?KoboldCpp
人工智能大模型本地部署模型推理服务Convex 函数编写指南:从 query / mutation 到关系数据建模实战
Convex 函数编写指南:从 query / mutation 到关系数据建模实战 导读 本文以 convex backend 仓库中 relational
数据库后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考