基于Django多表继承与graphene搭建GraphQL接口的技术问题
搞定Django多表继承模型的Graphene GraphQL接口实现
嘿,我来帮你解决这个多表继承下的GraphQL接口问题!先把你的场景和解决方案理清楚:
你的现有Django模型
先把你的模型代码补全并格式化,方便理解:
# 核心主模型 class MainModel(models.Model): # 假设核心字段比如标题 title = models.CharField(max_length=100) # 基础父模型,关联主模型 class BaseModel(models.Model): fk = models.ForeignKey(MainModel, related_name='bases', on_delete=models.CASCADE) base_info = models.CharField(max_length=200) # 子类变体1 class ChildModel(BaseModel): name = models.CharField(max_length=100) # 肯定还有其他子类变体,比如这个示例 class AnotherChildModel(BaseModel): value = models.IntegerField()
这里你用的是Django的多表继承,每个子类都会生成独立数据表,并且通过隐式的OneToOneField和父类BaseModel关联。
推测你的Schema现状
你应该已经开始写BaseModelType了,但卡在了如何处理子类的多态识别、以及MainModel关联字段的正确解析上,大概是这样的初始代码:
import graphene from graphene_django import DjangoObjectType from .models import MainModel, BaseModel, ChildModel class BaseModelType(DjangoObjectType): class Meta: model = BaseModel fields = "__all__" # 这里不知道怎么处理子类的类型区分,也不知道怎么在MainModel里关联查询子类数据
具体解决方案
针对多表继承的多态场景,graphene需要用接口(Interface)+**联合类型(Union)**来处理,步骤如下:
1. 定义BaseModel的公共接口
先把父模型的公共字段抽象成一个Interface,让所有子类类型都实现这个接口,统一处理父类字段:
class BaseModelInterface(graphene.Interface): id = graphene.ID() fk = graphene.Field(lambda: MainModelType) base_info = graphene.String() def resolve_id(self, info): return self.id def resolve_fk(self, info): return self.fk def resolve_base_info(self, info): return self.base_info
2. 定义子类的ObjectType并实现接口
每个子类都要对应自己的ObjectType,并且实现上面的接口,同时要解析多表继承的隐式关联:
class ChildModelType(DjangoObjectType): class Meta: model = ChildModel fields = "__all__" # 绑定接口 class Interface: interfaces = (BaseModelInterface,) # 多表继承下,子类实例要通过base_ptr访问父模型字段 def resolve_id(self, info): return self.base_ptr.id def resolve_fk(self, info): return self.base_ptr.fk def resolve_base_info(self, info): return self.base_ptr.base_info # 另一个子类同理 class AnotherChildModelType(DjangoObjectType): class Meta: model = AnotherChildModel fields = "__all__" class Interface: interfaces = (BaseModelInterface,) def resolve_id(self, info): return self.base_ptr.id def resolve_fk(self, info): return self.base_ptr.fk def resolve_base_info(self, info): return self.base_ptr.base_info
3. 创建Union类型处理多态查询
搞一个Union类型,把所有子类类型都加进去,这样查询时可以返回不同类型的子类实例:
BaseModelUnion = graphene.Union( 'BaseModelUnion', types=(ChildModelType, AnotherChildModelType) ) # 给Union加类型解析器,让graphene知道怎么判断实例对应哪个类型 @BaseModelUnion.type_resolver def resolve_base_model_union_type(root, info): if isinstance(root, ChildModel): return ChildModelType if isinstance(root, AnotherChildModel): return AnotherChildModelType # 如果有更多子类,继续加判断 return None
4. 修改MainModelType的关联字段
把MainModel里的bases字段换成我们的Union类型,并且重写解析器,因为默认的bases返回的是BaseModel实例,我们要找到对应的子类实例:
class MainModelType(DjangoObjectType): class Meta: model = MainModel fields = "__all__" # 替换默认的bases字段为Union列表 bases = graphene.List(BaseModelUnion) def resolve_bases(self, info): # 遍历所有关联的BaseModel实例,找到对应的子类 base_instances = self.bases.all() child_instances = [] for base in base_instances: # 检查是否属于ChildModel try: child_instances.append(base.childmodel) except ChildModel.DoesNotExist: pass # 检查是否属于AnotherChildModel try: child_instances.append(base.anotherchildmodel) except AnotherChildModel.DoesNotExist: pass # 其他子类同理添加判断 return child_instances
5. 定义Query入口
最后把所有类型加到Query里:
class Query(graphene.ObjectType): all_main_models = graphene.List(MainModelType) main_model = graphene.Field(MainModelType, id=graphene.ID()) def resolve_all_main_models(self, info): return MainModel.objects.all() def resolve_main_model(self, info, id): return MainModel.objects.get(pk=id) schema = graphene.Schema(query=Query)
测试查询示例
现在你可以发起这样的GraphQL查询,就能正确获取不同子类的字段了:
query { allMainModels { id title bases { ... on ChildModelType { id baseInfo name } ... on AnotherChildModelType { id baseInfo value } } } }
小提示
- 如果子类很多,别手动写重复代码,可以用反射自动生成类型和解析器
- 多表继承下,父类实例访问子类的属性是
子类名小写,比如base.childmodel,记得处理DoesNotExist异常 - 接口里的字段要和父模型的字段对应,避免解析错误
内容的提问来源于stack exchange,提问作者Laurent S
相关产品推荐
相关产品推荐

