diff --git a/graphene_django/tests/models.py b/graphene_django/tests/models.py index 180acc527..b244294d4 100644 --- a/graphene_django/tests/models.py +++ b/graphene_django/tests/models.py @@ -115,5 +115,9 @@ class Article(models.Model): def __str__(self): # __unicode__ on Python 2 return self.headline + @property + def headline_with_lang(self): + return "{} - {}".format(self.lang, self.headline) + class Meta: ordering = ("headline",) diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py index 699814d2c..f94129d69 100644 --- a/graphene_django/tests/test_query.py +++ b/graphene_django/tests/test_query.py @@ -10,10 +10,11 @@ import graphene from graphene.relay import Node +from graphene.types.utils import yank_fields_from_attrs from ..compat import IntegerRangeField, MissingType -from ..fields import DjangoConnectionField -from ..types import DjangoObjectType +from ..fields import DjangoConnectionField, DjangoListField +from ..types import DjangoObjectType, DjangoObjectTypeOptions from ..utils import DJANGO_FILTER_INSTALLED from .models import Article, CNNReporter, Film, FilmDetails, Reporter @@ -1586,3 +1587,82 @@ class Query(graphene.ObjectType): "allReporters": {"edges": [{"node": {"firstName": "Jane", "lastName": "Roe"}},]} } assert result.data == expected + + +def test_should_query_django_objecttype_fields_custom_meta(): + class ArticleBaseType(DjangoObjectType): + class Meta: + abstract = True + + @classmethod + def __init_subclass_with_meta__(cls, _meta=None, **options): + if _meta is None: + _meta = DjangoObjectTypeOptions(cls) + + _meta.fields = yank_fields_from_attrs( + {"headline_with_lang": graphene.String()}, _as=graphene.Field, + ) + + super(ArticleBaseType, cls).__init_subclass_with_meta__( + _meta=_meta, **options + ) + + class ArticleCustomType(ArticleBaseType): + class Meta: + model = Article + fields = ( + "headline", + "lang", + "headline_with_lang", + ) + + class Query(graphene.ObjectType): + all_articles = DjangoListField(ArticleCustomType) + + r = Reporter.objects.create( + first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1 + ) + Article.objects.create( + headline="Article Node 1", + pub_date=datetime.date.today(), + pub_date_time=datetime.datetime.now(), + reporter=r, + editor=r, + lang="es", + ) + Article.objects.create( + headline="Article Node 2", + pub_date=datetime.date.today(), + pub_date_time=datetime.datetime.now(), + reporter=r, + editor=r, + lang="en", + ) + + schema = graphene.Schema(query=Query) + query = """ + query GetAllArticles { + allArticles { + headline + lang + headlineWithLang + } + } + """ + result = schema.execute(query) + assert not result.errors + expected = { + "allArticles": [ + { + "headline": "Article Node 1", + "lang": "ES", + "headlineWithLang": "es - Article Node 1", + }, + { + "headline": "Article Node 2", + "lang": "EN", + "headlineWithLang": "en - Article Node 2", + }, + ] + } + assert result.data == expected diff --git a/graphene_django/tests/test_types.py b/graphene_django/tests/test_types.py index cb653e1cf..ffc18fd0e 100644 --- a/graphene_django/tests/test_types.py +++ b/graphene_django/tests/test_types.py @@ -114,6 +114,33 @@ class Meta: assert isinstance(Article._meta, ArticleTypeOptions) +def test_django_objecttype_with_custom_meta_fields(): + class ArticleTypeOptions(DjangoObjectTypeOptions): + """Article Type Options with extra fields""" + + fields = {"headline_with_lang": String()} + + class ArticleType(DjangoObjectType): + class Meta: + abstract = True + + @classmethod + def __init_subclass_with_meta__(cls, **options): + options.setdefault("_meta", ArticleTypeOptions(cls)) + super(ArticleType, cls).__init_subclass_with_meta__(**options) + + class Article(ArticleType): + class Meta: + model = ArticleModel + fields = "__all__" + + headline_with_lang_field = Article._meta.fields.get("headline_with_lang") + + assert isinstance(Article._meta, ArticleTypeOptions) + assert headline_with_lang_field is not None + assert isinstance(headline_with_lang_field, String) + + def test_schema_representation(): expected = dedent( """\ @@ -278,6 +305,81 @@ class Meta: assert fields == ["id", "email", "films"] +@with_local_registry +def test_django_objecttype_fields_custom_meta_fields(): + class ArticleTypeOptions(DjangoObjectTypeOptions): + """Article Type Options with extra fields""" + + fields = {"headline_with_lang": String()} + + class ArticleType(DjangoObjectType): + class Meta: + abstract = True + + @classmethod + def __init_subclass_with_meta__(cls, **options): + options.setdefault("_meta", ArticleTypeOptions(cls)) + super(ArticleType, cls).__init_subclass_with_meta__(**options) + + class Article(ArticleType): + class Meta: + model = ArticleModel + fields = ("editor", "lang", "importance") + + fields = list(Article._meta.fields.keys()) + assert fields == ["editor", "lang", "importance"] + + +@with_local_registry +def test_django_objecttype_fields_custom_meta_fields_include(): + class ArticleTypeOptions(DjangoObjectTypeOptions): + """Article Type Options with extra fields""" + + fields = {"headline_with_lang": String()} + + class ArticleType(DjangoObjectType): + class Meta: + abstract = True + + @classmethod + def __init_subclass_with_meta__(cls, **options): + options.setdefault("_meta", ArticleTypeOptions(cls)) + super(ArticleType, cls).__init_subclass_with_meta__(**options) + + class Article(ArticleType): + class Meta: + model = ArticleModel + fields = ("headline_with_lang", "editor", "lang", "importance") + + fields = list(Article._meta.fields.keys()) + assert fields == ["headline_with_lang", "editor", "lang", "importance"] + + +@with_local_registry +def test_django_objecttype_fields_custom_meta_fields_all(): + class ArticleTypeOptions(DjangoObjectTypeOptions): + """Article Type Options with extra fields""" + + fields = {"headline_with_lang": String()} + + class ArticleType(DjangoObjectType): + class Meta: + abstract = True + + @classmethod + def __init_subclass_with_meta__(cls, **options): + options.setdefault("_meta", ArticleTypeOptions(cls)) + super(ArticleType, cls).__init_subclass_with_meta__(**options) + + class Article(ArticleType): + class Meta: + model = ArticleModel + fields = "__all__" + + fields = list(Article._meta.fields.keys()) + assert len(fields) == len(ArticleModel._meta.get_fields()) + 1 + + @with_local_registry def test_django_objecttype_fields(): class Reporter(DjangoObjectType): diff --git a/graphene_django/types.py b/graphene_django/types.py index 53c4d235d..7ef70074c 100644 --- a/graphene_django/types.py +++ b/graphene_django/types.py @@ -256,13 +256,26 @@ def __init_subclass_with_meta__( if not _meta: _meta = DjangoObjectTypeOptions(cls) + elif _meta.fields: + # Exclude previous meta fields that are not in fields or are in exclude + only_fields = fields is not None and fields != ALL_FIELDS + exclude_fields = exclude is not None + if only_fields or exclude_fields: + for name in list(_meta.fields.keys()): + if (only_fields and name not in fields) or ( + exclude_fields and name in exclude + ): + _meta.fields.pop(name) _meta.model = model _meta.registry = registry _meta.filter_fields = filter_fields _meta.filterset_class = filterset_class - _meta.fields = django_fields _meta.connection = connection + if _meta.fields: + _meta.fields.update(django_fields) + else: + _meta.fields = django_fields super(DjangoObjectType, cls).__init_subclass_with_meta__( _meta=_meta, interfaces=interfaces, **options