diff --git a/graphene_django/types.py b/graphene_django/types.py index bb0a2f1..26fb266 100644 --- a/graphene_django/types.py +++ b/graphene_django/types.py @@ -3,11 +3,10 @@ from collections import OrderedDict import six from django.utils.functional import SimpleLazyObject -from graphene import Field, ObjectType -from graphene.types.objecttype import ObjectTypeMeta -from graphene.types.options import Options -from graphene.types.utils import merge, yank_fields_from_attrs -from graphene.utils.is_base_type import is_base_type +from graphene import Field +from graphene.relay import Connection, Node +from graphene.types.objecttype import ObjectType, ObjectTypeOptions +from graphene.types.utils import yank_fields_from_attrs from .converter import convert_django_field_with_choices from .registry import Registry, get_global_registry @@ -15,16 +14,14 @@ from .utils import (DJANGO_FILTER_INSTALLED, get_model_fields, is_valid_django_model) -def construct_fields(options): - _model_fields = get_model_fields(options.model) - only_fields = options.only_fields - exclude_fields = options.exclude_fields +def construct_fields(model, registry, only_fields, exclude_fields): + _model_fields = get_model_fields(model) fields = OrderedDict() for name, field in _model_fields: - is_not_in_only = only_fields and name not in options.only_fields - is_already_created = name in options.fields - is_excluded = name in exclude_fields or is_already_created + is_not_in_only = only_fields and name not in only_fields + # is_already_created = name in options.fields + is_excluded = name in exclude_fields # or is_already_created # https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ForeignKey.related_query_name is_no_backref = str(name).endswith('+') if is_not_in_only or is_excluded or is_no_backref: @@ -32,74 +29,67 @@ def construct_fields(options): # in there. Or when we exclude this field in exclude_fields. # Or when there is no back reference. continue - converted = convert_django_field_with_choices(field, options.registry) + converted = convert_django_field_with_choices(field, registry) fields[name] = converted return fields -class DjangoObjectTypeMeta(ObjectTypeMeta): +class DjangoObjectTypeOptions(ObjectTypeOptions): + model = None # type: Model + registry = None # type: Registry + connection = None # type: Type[Connection] - @staticmethod - def __new__(cls, name, bases, attrs): - # Also ensure initialization is only performed for subclasses of - # DjangoObjectType - if not is_base_type(bases, DjangoObjectTypeMeta): - return type.__new__(cls, name, bases, attrs) + filter_fields = () - defaults = dict( - name=name, - description=attrs.pop('__doc__', None), - model=None, - local_fields=None, - only_fields=(), - exclude_fields=(), - interfaces=(), - skip_registry=False, - registry=None - ) - if DJANGO_FILTER_INSTALLED: - # In case Django filter is available, then - # we allow more attributes in Meta - defaults.update( - filter_fields=(), - ) - options = Options( - attrs.pop('Meta', None), - **defaults - ) - if not options.registry: - options.registry = get_global_registry() - assert isinstance(options.registry, Registry), ( - 'The attribute registry in {}.Meta needs to be an instance of ' - 'Registry, received "{}".' - ).format(name, options.registry) - assert is_valid_django_model(options.model), ( +class DjangoObjectType(ObjectType): + @classmethod + def __init_subclass_with_meta__(cls, model=None, registry=None, skip_registry=False, + only_fields=(), exclude_fields=(), filter_fields=None, connection=None, use_connection=None, interfaces=(), **options): + assert is_valid_django_model(model), ( 'You need to pass a valid Django Model in {}.Meta, received "{}".' - ).format(name, options.model) + ).format(cls.__name__, model) - cls = ObjectTypeMeta.__new__(cls, name, bases, dict(attrs, _meta=options)) + if not registry: + registry = get_global_registry() - options.registry.register(cls) + assert isinstance(registry, Registry), ( + 'The attribute registry in {} needs to be an instance of ' + 'Registry, received "{}".' + ).format(cls.__name__, registry) - options.django_fields = yank_fields_from_attrs( - construct_fields(options), + if not DJANGO_FILTER_INSTALLED and filter_fields: + raise Exception("Can only set filter_fields if Django-Filter is installed") + + django_fields = yank_fields_from_attrs( + construct_fields(model, registry, only_fields, exclude_fields), _as=Field, ) - options.fields = merge( - options.interface_fields, - options.django_fields, - options.base_fields, - options.local_fields - ) - return cls + if use_connection is None and interfaces: + use_connection = any((issubclass(interface, Node) for interface in interfaces)) + if use_connection and not connection: + # We create the connection automatically + connection = Connection.create_type('{}Connection'.format(cls.__name__), node=cls) -class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, ObjectType)): + if connection is not None: + assert issubclass(connection, Connection), "The connection must be a Connection. Received {}".format(connection.__name__) - def resolve_id(self, args, context, info): + _meta = DjangoObjectTypeOptions(cls) + _meta.model = model + _meta.registry = registry + _meta.filter_fields = filter_fields + _meta.fields = django_fields + _meta.connection = connection + + super(DjangoObjectType, cls).__init_subclass_with_meta__(_meta=_meta, interfaces=interfaces, **options) + + if not skip_registry: + registry.register(cls) + + def resolve_id(self): return self.pk @classmethod