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: