mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-07-03 20:03:30 +03:00
Merge 0328b86e2a
into c52cf2b045
This commit is contained in:
commit
0042334576
|
@ -1,5 +1,5 @@
|
||||||
from .fields import DjangoConnectionField, DjangoListField
|
from .fields import DjangoConnectionField, DjangoListField
|
||||||
from .types import DjangoObjectType
|
from .types import DjangoObjectType, DjangoUnionType
|
||||||
from .utils import bypass_get_queryset
|
from .utils import bypass_get_queryset
|
||||||
|
|
||||||
__version__ = "3.2.3"
|
__version__ = "3.2.3"
|
||||||
|
@ -7,6 +7,7 @@ __version__ = "3.2.3"
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"__version__",
|
"__version__",
|
||||||
"DjangoObjectType",
|
"DjangoObjectType",
|
||||||
|
"DjangoUnionType",
|
||||||
"DjangoListField",
|
"DjangoListField",
|
||||||
"DjangoConnectionField",
|
"DjangoConnectionField",
|
||||||
"bypass_get_queryset",
|
"bypass_get_queryset",
|
||||||
|
|
|
@ -92,7 +92,7 @@ class DjangoConnectionField(ConnectionField):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
from .types import DjangoObjectType
|
from .types import DjangoObjectType, DjangoUnionType
|
||||||
|
|
||||||
_type = super(ConnectionField, self).type
|
_type = super(ConnectionField, self).type
|
||||||
non_null = False
|
non_null = False
|
||||||
|
@ -100,8 +100,8 @@ class DjangoConnectionField(ConnectionField):
|
||||||
_type = _type.of_type
|
_type = _type.of_type
|
||||||
non_null = True
|
non_null = True
|
||||||
assert issubclass(
|
assert issubclass(
|
||||||
_type, DjangoObjectType
|
_type, (DjangoObjectType, DjangoUnionType)
|
||||||
), "DjangoConnectionField only accepts DjangoObjectType types"
|
), "DjangoConnectionField only accepts DjangoObjectType or DjangoUnionType types"
|
||||||
assert _type._meta.connection, "The type {} doesn't have a connection".format(
|
assert _type._meta.connection, "The type {} doesn't have a connection".format(
|
||||||
_type.__name__
|
_type.__name__
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,11 +4,11 @@ class Registry:
|
||||||
self._field_registry = {}
|
self._field_registry = {}
|
||||||
|
|
||||||
def register(self, cls):
|
def register(self, cls):
|
||||||
from .types import DjangoObjectType
|
from .types import DjangoObjectType, DjangoUnionType
|
||||||
|
|
||||||
assert issubclass(
|
assert issubclass(
|
||||||
cls, DjangoObjectType
|
cls, (DjangoObjectType, DjangoUnionType)
|
||||||
), f'Only DjangoObjectTypes can be registered, received "{cls.__name__}"'
|
), f'Only DjangoObjectTypes or DjangoUnionType can be registered, received "{cls.__name__}"'
|
||||||
assert cls._meta.registry == self, "Registry for a Model have to match."
|
assert cls._meta.registry == self, "Registry for a Model have to match."
|
||||||
# assert self.get_type_for_model(cls._meta.model) == cls, (
|
# assert self.get_type_for_model(cls._meta.model) == cls, (
|
||||||
# 'Multiple DjangoObjectTypes registered for "{}"'.format(cls._meta.model)
|
# 'Multiple DjangoObjectTypes registered for "{}"'.format(cls._meta.model)
|
||||||
|
|
|
@ -11,9 +11,15 @@ from graphene.relay import Node
|
||||||
|
|
||||||
from .. import registry
|
from .. import registry
|
||||||
from ..filter import DjangoFilterConnectionField
|
from ..filter import DjangoFilterConnectionField
|
||||||
from ..types import DjangoObjectType, DjangoObjectTypeOptions
|
from ..types import (
|
||||||
|
DjangoObjectType,
|
||||||
|
DjangoObjectTypeOptions,
|
||||||
|
DjangoUnionType,
|
||||||
|
)
|
||||||
from .models import (
|
from .models import (
|
||||||
|
APNewsReporter as APNewsReporterModel,
|
||||||
Article as ArticleModel,
|
Article as ArticleModel,
|
||||||
|
CNNReporter as CNNReporterModel,
|
||||||
Reporter as ReporterModel,
|
Reporter as ReporterModel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -832,3 +838,47 @@ def test_django_objecttype_name_connection_propagation():
|
||||||
assert "type Reporter implements Node {" not in schema
|
assert "type Reporter implements Node {" not in schema
|
||||||
assert "type ReporterConnection {" not in schema
|
assert "type ReporterConnection {" not in schema
|
||||||
assert "type ReporterEdge {" not in schema
|
assert "type ReporterEdge {" not in schema
|
||||||
|
|
||||||
|
|
||||||
|
@with_local_registry
|
||||||
|
def test_django_uniontype_name_connection_propagation():
|
||||||
|
class CNNReporter(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = CNNReporterModel
|
||||||
|
name = "CNNReporter"
|
||||||
|
fields = "__all__"
|
||||||
|
filter_fields = ["email"]
|
||||||
|
interfaces = (Node,)
|
||||||
|
|
||||||
|
class APNewsReporter(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = APNewsReporterModel
|
||||||
|
name = "APNewsReporter"
|
||||||
|
fields = "__all__"
|
||||||
|
filter_fields = ["email"]
|
||||||
|
interfaces = (Node,)
|
||||||
|
|
||||||
|
class ReporterUnion(DjangoUnionType):
|
||||||
|
class Meta:
|
||||||
|
model = ReporterModel
|
||||||
|
types = (CNNReporter, APNewsReporter)
|
||||||
|
interfaces = (Node,)
|
||||||
|
filter_fields = ("id", "first_name", "last_name")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resolve_type(cls, instance, info):
|
||||||
|
if isinstance(instance, CNNReporterModel):
|
||||||
|
return CNNReporter
|
||||||
|
elif isinstance(instance, APNewsReporterModel):
|
||||||
|
return APNewsReporter
|
||||||
|
return None
|
||||||
|
|
||||||
|
class Query(ObjectType):
|
||||||
|
reporter = Node.Field(ReporterUnion)
|
||||||
|
reporters = DjangoFilterConnectionField(ReporterUnion)
|
||||||
|
|
||||||
|
schema = str(Schema(query=Query))
|
||||||
|
|
||||||
|
assert "union ReporterUnion = CNNReporter | APNewsReporter" in schema
|
||||||
|
assert "CNNReporter implements Node" in schema
|
||||||
|
assert "ReporterUnionConnection" in schema
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django.db.models import Model # noqa: F401
|
||||||
import graphene
|
import graphene
|
||||||
from graphene.relay import Connection, Node
|
from graphene.relay import Connection, Node
|
||||||
from graphene.types.objecttype import ObjectType, ObjectTypeOptions
|
from graphene.types.objecttype import ObjectType, ObjectTypeOptions
|
||||||
|
from graphene.types.union import Union, UnionOptions
|
||||||
from graphene.types.utils import yank_fields_from_attrs
|
from graphene.types.utils import yank_fields_from_attrs
|
||||||
|
|
||||||
from .converter import convert_django_field_with_choices
|
from .converter import convert_django_field_with_choices
|
||||||
|
@ -293,6 +294,137 @@ class DjangoObjectType(ObjectType):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoUnionTypeOptions(UnionOptions, ObjectTypeOptions):
|
||||||
|
model = None # type: Type[Model]
|
||||||
|
registry = None # type: Registry
|
||||||
|
connection = None # type: Type[Connection]
|
||||||
|
|
||||||
|
filter_fields = ()
|
||||||
|
filterset_class = None
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoUnionType(Union):
|
||||||
|
"""
|
||||||
|
A Django specific Union type that allows to map multiple Django object types
|
||||||
|
One use case is to handle polymorphic relationships for a Django model, using a library like django-polymorphic.
|
||||||
|
|
||||||
|
Can be used in combination with DjangoConnectionField and DjangoFilterConnectionField
|
||||||
|
|
||||||
|
Args:
|
||||||
|
Meta (class): The meta class of the union type
|
||||||
|
model (Model): The Django model that represents the union type
|
||||||
|
types (tuple): A tuple of DjangoObjectType classes that represent the possible types of the union
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
from graphene_django.types import DjangoObjectType, DjangoUnionType
|
||||||
|
|
||||||
|
class AssessmentUnion(DjangoUnionType):
|
||||||
|
class Meta:
|
||||||
|
model = Assessment
|
||||||
|
types = (HomeworkAssessmentNode, QuizAssessmentNode)
|
||||||
|
interfaces = (graphene.relay.Node,)
|
||||||
|
filter_fields = ("id", "title", "description")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resolve_type(cls, instance, info):
|
||||||
|
if isinstance(instance, HomeworkAssessment):
|
||||||
|
return HomeworkAssessmentNode
|
||||||
|
elif isinstance(instance, QuizAssessment):
|
||||||
|
return QuizAssessmentNode
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
all_assessments = DjangoFilterConnectionField(AssessmentUnion)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __init_subclass_with_meta__(
|
||||||
|
cls,
|
||||||
|
model=None,
|
||||||
|
types=None,
|
||||||
|
registry=None,
|
||||||
|
skip_registry=False,
|
||||||
|
_meta=None,
|
||||||
|
fields=None,
|
||||||
|
exclude=None,
|
||||||
|
convert_choices_to_enum=None,
|
||||||
|
filter_fields=None,
|
||||||
|
filterset_class=None,
|
||||||
|
connection=None,
|
||||||
|
connection_class=None,
|
||||||
|
use_connection=None,
|
||||||
|
interfaces=(),
|
||||||
|
**options,
|
||||||
|
):
|
||||||
|
django_fields = yank_fields_from_attrs(
|
||||||
|
construct_fields(model, registry, fields, exclude, convert_choices_to_enum),
|
||||||
|
_as=graphene.Field,
|
||||||
|
)
|
||||||
|
|
||||||
|
if use_connection is None and interfaces:
|
||||||
|
use_connection = any(
|
||||||
|
issubclass(interface, Node) for interface in interfaces
|
||||||
|
)
|
||||||
|
|
||||||
|
if not registry:
|
||||||
|
registry = get_global_registry()
|
||||||
|
|
||||||
|
assert isinstance(registry, Registry), (
|
||||||
|
f"The attribute registry in {cls.__name__} needs to be an instance of "
|
||||||
|
f'Registry, received "{registry}".'
|
||||||
|
)
|
||||||
|
|
||||||
|
if filter_fields and filterset_class:
|
||||||
|
raise Exception("Can't set both filter_fields and filterset_class")
|
||||||
|
|
||||||
|
if not DJANGO_FILTER_INSTALLED and (filter_fields or filterset_class):
|
||||||
|
raise Exception(
|
||||||
|
"Can only set filter_fields or filterset_class if "
|
||||||
|
"Django-Filter is installed"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not _meta:
|
||||||
|
_meta = DjangoUnionTypeOptions(cls)
|
||||||
|
|
||||||
|
_meta.model = model
|
||||||
|
_meta.types = types
|
||||||
|
_meta.fields = django_fields
|
||||||
|
_meta.filter_fields = filter_fields
|
||||||
|
_meta.filterset_class = filterset_class
|
||||||
|
_meta.registry = registry
|
||||||
|
|
||||||
|
if use_connection and not connection:
|
||||||
|
# We create the connection automatically
|
||||||
|
if not connection_class:
|
||||||
|
connection_class = Connection
|
||||||
|
|
||||||
|
connection = connection_class.create_type(
|
||||||
|
"{}Connection".format(options.get("name") or cls.__name__), node=cls
|
||||||
|
)
|
||||||
|
|
||||||
|
if connection is not None:
|
||||||
|
assert issubclass(
|
||||||
|
connection, Connection
|
||||||
|
), f"The connection must be a Connection. Received {connection.__name__}"
|
||||||
|
|
||||||
|
_meta.connection = connection
|
||||||
|
|
||||||
|
super().__init_subclass_with_meta__(
|
||||||
|
types=types, _meta=_meta, interfaces=interfaces, **options
|
||||||
|
)
|
||||||
|
|
||||||
|
if not skip_registry:
|
||||||
|
registry.register(cls)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_queryset(cls, queryset, info):
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class ErrorType(ObjectType):
|
class ErrorType(ObjectType):
|
||||||
field = graphene.String(required=True)
|
field = graphene.String(required=True)
|
||||||
messages = graphene.List(graphene.NonNull(graphene.String), required=True)
|
messages = graphene.List(graphene.NonNull(graphene.String), required=True)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user