|
7 | 7 | import graphene
|
8 | 8 | from graphene.relay import Connection, Node
|
9 | 9 | from graphene.types.objecttype import ObjectType, ObjectTypeOptions
|
| 10 | +from graphene.types.union import Union |
10 | 11 | from graphene.types.utils import yank_fields_from_attrs
|
11 | 12 |
|
12 | 13 | from .converter import convert_django_field_with_choices
|
@@ -293,6 +294,137 @@ def get_node(cls, info, id):
|
293 | 294 | return None
|
294 | 295 |
|
295 | 296 |
|
| 297 | +class DjangoUnionTypeOptions(ObjectTypeOptions): |
| 298 | + model = None # type: Type[Model] |
| 299 | + registry = None # type: Registry |
| 300 | + connection = None # type: Type[Connection] |
| 301 | + |
| 302 | + filter_fields = () |
| 303 | + filterset_class = None |
| 304 | + |
| 305 | + |
| 306 | +class DjangoUnionType(Union): |
| 307 | + """ |
| 308 | + A Django specific Union type that allows to map multiple Django object types |
| 309 | + One use case is to handle polymorphic relationships for a Django model, using a library like django-polymorphic. |
| 310 | +
|
| 311 | + Can be used in combination with DjangoConnectionField and DjangoFilterConnectionField |
| 312 | +
|
| 313 | + Args: |
| 314 | + Meta (class): The meta class of the union type |
| 315 | + model (Model): The Django model that represents the union type |
| 316 | + types (tuple): A tuple of DjangoObjectType classes that represent the possible types of the union |
| 317 | +
|
| 318 | + Example: |
| 319 | + ```python |
| 320 | + from graphene_django.types import DjangoObjectType, DjangoUnionType |
| 321 | +
|
| 322 | + class AssessmentUnion(DjangoUnionType): |
| 323 | + class Meta: |
| 324 | + model = Assessment |
| 325 | + types = (HomeworkAssessmentNode, QuizAssessmentNode) |
| 326 | + interfaces = (graphene.relay.Node,) |
| 327 | + filter_fields = ("id", "title", "description") |
| 328 | +
|
| 329 | + @classmethod |
| 330 | + def resolve_type(cls, instance, info): |
| 331 | + if isinstance(instance, HomeworkAssessment): |
| 332 | + return HomeworkAssessmentNode |
| 333 | + elif isinstance(instance, QuizAssessment): |
| 334 | + return QuizAssessmentNode |
| 335 | +
|
| 336 | + class Query(graphene.ObjectType): |
| 337 | + all_assessments = DjangoFilterConnectionField(AssessmentUnion) |
| 338 | + ``` |
| 339 | + """ |
| 340 | + |
| 341 | + class Meta: |
| 342 | + abstract = True |
| 343 | + |
| 344 | + @classmethod |
| 345 | + def __init_subclass_with_meta__( |
| 346 | + cls, |
| 347 | + model=None, |
| 348 | + types=None, |
| 349 | + registry=None, |
| 350 | + skip_registry=False, |
| 351 | + _meta=None, |
| 352 | + fields=None, |
| 353 | + exclude=None, |
| 354 | + convert_choices_to_enum=None, |
| 355 | + filter_fields=None, |
| 356 | + filterset_class=None, |
| 357 | + connection=None, |
| 358 | + connection_class=None, |
| 359 | + use_connection=None, |
| 360 | + interfaces=(), |
| 361 | + **options, |
| 362 | + ): |
| 363 | + django_fields = yank_fields_from_attrs( |
| 364 | + construct_fields(model, registry, fields, exclude, convert_choices_to_enum), |
| 365 | + _as=graphene.Field, |
| 366 | + ) |
| 367 | + |
| 368 | + if use_connection is None and interfaces: |
| 369 | + use_connection = any( |
| 370 | + issubclass(interface, Node) for interface in interfaces |
| 371 | + ) |
| 372 | + |
| 373 | + if not registry: |
| 374 | + registry = get_global_registry() |
| 375 | + |
| 376 | + assert isinstance(registry, Registry), ( |
| 377 | + f"The attribute registry in {cls.__name__} needs to be an instance of " |
| 378 | + f'Registry, received "{registry}".' |
| 379 | + ) |
| 380 | + |
| 381 | + if filter_fields and filterset_class: |
| 382 | + raise Exception("Can't set both filter_fields and filterset_class") |
| 383 | + |
| 384 | + if not DJANGO_FILTER_INSTALLED and (filter_fields or filterset_class): |
| 385 | + raise Exception( |
| 386 | + "Can only set filter_fields or filterset_class if " |
| 387 | + "Django-Filter is installed" |
| 388 | + ) |
| 389 | + |
| 390 | + if not _meta: |
| 391 | + _meta = DjangoUnionTypeOptions(cls) |
| 392 | + |
| 393 | + _meta.model = model |
| 394 | + _meta.types = types |
| 395 | + _meta.fields = django_fields |
| 396 | + _meta.filter_fields = filter_fields |
| 397 | + _meta.filterset_class = filterset_class |
| 398 | + _meta.registry = registry |
| 399 | + |
| 400 | + if use_connection and not connection: |
| 401 | + # We create the connection automatically |
| 402 | + if not connection_class: |
| 403 | + connection_class = Connection |
| 404 | + |
| 405 | + connection = connection_class.create_type( |
| 406 | + "{}Connection".format(options.get("name") or cls.__name__), node=cls |
| 407 | + ) |
| 408 | + |
| 409 | + if connection is not None: |
| 410 | + assert issubclass( |
| 411 | + connection, Connection |
| 412 | + ), f"The connection must be a Connection. Received {connection.__name__}" |
| 413 | + |
| 414 | + _meta.connection = connection |
| 415 | + |
| 416 | + super().__init_subclass_with_meta__( |
| 417 | + types=types, _meta=_meta, interfaces=interfaces, **options |
| 418 | + ) |
| 419 | + |
| 420 | + if not skip_registry: |
| 421 | + registry.register(cls) |
| 422 | + |
| 423 | + @classmethod |
| 424 | + def get_queryset(cls, queryset, info): |
| 425 | + return queryset |
| 426 | + |
| 427 | + |
296 | 428 | class ErrorType(ObjectType):
|
297 | 429 | field = graphene.String(required=True)
|
298 | 430 | messages = graphene.List(graphene.NonNull(graphene.String), required=True)
|
|
0 commit comments