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.