Custom fields
GraphQL types can have non-model fields using custom resolvers.
import graphene
from example_project.app.models import HousingCompany
from query_optimizer import DjangoObjectType
class HousingCompanyType(DjangoObjectType):
class Meta:
model = HousingCompany
greeting = graphene.String()
def resolve_greeting(root: HousingCompany, info) -> str:
return f"Hello World!"
If the custom type requires fields from its related models to resolve, you have a few options.
AnnotatedField
This field can be used to add annotations to the queryset when the field is requested.
import graphene
from django.db.models import F, Value
from example_project.app.models import HousingCompany
from query_optimizer import DjangoObjectType, AnnotatedField # new import
class HousingCompanyType(DjangoObjectType):
class Meta:
model = HousingCompany
greeting = AnnotatedField(graphene.String, expression=Value("Hello ") + F("name"))
Note that only a single annotation can be added, however, you can use the aliases
parameter to help with more complex annotations.
import graphene
from django.db.models import F, Value
from example_project.app.models import HousingCompany
from query_optimizer import DjangoObjectType, AnnotatedField
class HousingCompanyType(DjangoObjectType):
class Meta:
model = HousingCompany
greeting = AnnotatedField(
graphene.String,
expression=F("hello") + F("name"),
aliases={"hello": Value("Hello ")}, # very complex!
)
MultiField
This field can be used to add multiple fields to the queryset when the field is requested.
import graphene
from example_project.app.models import HousingCompany
from query_optimizer import DjangoObjectType, MultiField # new import
class HousingCompanyType(DjangoObjectType):
class Meta:
model = HousingCompany
greeting = MultiField(graphene.String, fields=["pk", "name"])
def resolve_greeting(root: HousingCompany, info) -> str:
return f"Hello {root.name} ({root.pk})!"
Note that this can only be used for fields on the same model.
ManuallyOptimizedField
This is the most powerful custom field type, which allows defining a custom method to manually optimize the queryset when the field is requested. This allows for optimization strategies that are not possible with the other fields.
Note: You shouldn't default to using this field, as it can break the optimization if you are not careful in considering other optimization already in the optimizer.
import graphene
from django.db.models import QuerySet
from example_project.app.models import HousingCompany
from query_optimizer import DjangoObjectType, ManuallyOptimizedField # new import
from query_optimizer.optimizer import QueryOptimizer # for type hinting
class HousingCompanyType(DjangoObjectType):
class Meta:
model = HousingCompany
extra = ManuallyOptimizedField(graphene.String)
@staticmethod
def optimize_extra(queryset: QuerySet, optimizer: QueryOptimizer, **kwargs) -> QuerySet:
# Do any optimizations here, returning the queryset.
return queryset
def resolve_extra(root: HousingCompany, info) -> str:
# Still needs a resolver.
return ...
Note that this can only be used for fields on the same model.
The field_name
argument
RelatedField
, DjangoListField
, DjangoConnectionField
have a field_name
argument that can be used to specify the field name in the queryset if it's
different from the field name in the model.
from example_project.app.models import HousingCompany
from query_optimizer import DjangoObjectType, DjangoListField # new import
class HousingCompanyType(DjangoObjectType):
class Meta:
model = HousingCompany
developers_alt = DjangoListField("...", field_name="developers")
This marks the field as being for the same relation as the field_name
is on the model,
and it will resolve the field as if it was that relation. This is achieved by using the
Prefetch("developers", qs, to_attr="developers_alt")
feature from Django.