From aed2f78bdb06b156d22c8342e7407292fe76af9b Mon Sep 17 00:00:00 2001 From: Sebastian Hernandez <sebastian@rhinoafrica.com> Date: Mon, 22 Feb 2021 19:42:25 +0100 Subject: [PATCH 1/2] Merge fields used in meta attribute passed on DjangoObjectType --- graphene_django/tests/models.py | 4 ++ graphene_django/tests/test_query.py | 83 +++++++++++++++++++++- graphene_django/tests/test_types.py | 102 ++++++++++++++++++++++++++++ graphene_django/types.py | 15 +++- 4 files changed, 201 insertions(+), 3 deletions(-) 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..164e0bbdc 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,81 @@ 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 ArticleTypeOptions(DjangoObjectTypeOptions): + """Article Type Options with extra fields""" + + fields = yank_fields_from_attrs( + {"headline_with_lang": graphene.String()}, _as=graphene.Field, + ) + + class ArticleBaseType(DjangoObjectType): + class Meta: + abstract = True + + @classmethod + def __init_subclass_with_meta__(cls, **options): + options.setdefault("_meta", ArticleTypeOptions(cls)) + super(ArticleBaseType, cls).__init_subclass_with_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 From 8fbadee60c78ae1492640cc8eebd03b896c316ce Mon Sep 17 00:00:00 2001 From: Sebastian Hernandez <sebastian@rhinoafrica.com> Date: Tue, 23 Feb 2021 19:31:16 +0100 Subject: [PATCH 2/2] Update test to a more real case scenario --- graphene_django/tests/test_query.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py index 164e0bbdc..f94129d69 100644 --- a/graphene_django/tests/test_query.py +++ b/graphene_django/tests/test_query.py @@ -1590,21 +1590,22 @@ class Query(graphene.ObjectType): def test_should_query_django_objecttype_fields_custom_meta(): - class ArticleTypeOptions(DjangoObjectTypeOptions): - """Article Type Options with extra fields""" - - fields = yank_fields_from_attrs( - {"headline_with_lang": graphene.String()}, _as=graphene.Field, - ) - class ArticleBaseType(DjangoObjectType): class Meta: abstract = True @classmethod - def __init_subclass_with_meta__(cls, **options): - options.setdefault("_meta", ArticleTypeOptions(cls)) - super(ArticleBaseType, cls).__init_subclass_with_meta__(**options) + 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: