diff --git a/graphene_django/tests/models.py b/graphene_django/tests/models.py index 0c62f28ba..03d19d4b2 100644 --- a/graphene_django/tests/models.py +++ b/graphene_django/tests/models.py @@ -30,9 +30,36 @@ class Reporter(models.Model): pets = models.ManyToManyField('self') a_choice = models.CharField(max_length=30, choices=CHOICES) + reporter_type = models.IntegerField( + 'Reporter Type', + null=True, + blank=True, + choices=[(1, u'Regular'), (2, u'CNN Reporter')] + ) + def __str__(self): # __unicode__ on Python 2 return "%s %s" % (self.first_name, self.last_name) + def __init__(self, *args, **kwargs): + """ + Override the init method so that during runtime, Django + can know that this object can be a CNNReporter by casting + it to the proxy model. Otherwise, as far as Django knows, + when a CNNReporter is pulled from the database, it is still + of type Reporter. This was added to test proxy model support. + """ + super(Reporter, self).__init__(*args, **kwargs) + if self.reporter_type == 2: # quick and dirty way without enums + self.__class__ = CNNReporter + +class CNNReporter(Reporter): + """ + This class is a proxy model for Reporter, used for testing + proxy model support + """ + class Meta: + proxy = True + class Article(models.Model): headline = models.CharField(max_length=100) diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py index 0dece3f61..a20824d4b 100644 --- a/graphene_django/tests/test_query.py +++ b/graphene_django/tests/test_query.py @@ -13,7 +13,11 @@ from ..fields import DjangoConnectionField from ..types import DjangoObjectType from ..settings import graphene_settings -from .models import Article, Reporter +from .models import ( + Article, + CNNReporter, + Reporter, +) pytestmark = pytest.mark.django_db @@ -689,6 +693,7 @@ class Query(graphene.ObjectType): email='johndoe@example.com', a_choice=1 ) + Article.objects.create( headline='Article Node 1', pub_date=datetime.date.today(), @@ -780,3 +785,139 @@ class Query(graphene.ObjectType): ''' result = schema.execute(query) assert not result.errors + + +def test_proxy_model_support(): + """ + This test asserts that we can query for all Reporters, + even if some are of a proxy model type at runtime. + """ + class ReporterType(DjangoObjectType): + + class Meta: + model = Reporter + interfaces = (Node, ) + use_connection = True + + reporter_1 = Reporter.objects.create( + first_name='John', + last_name='Doe', + email='johndoe@example.com', + a_choice=1 + ) + + reporter_2 = CNNReporter.objects.create( + first_name='Some', + last_name='Guy', + email='someguy@cnn.com', + a_choice=1, + reporter_type=2, # set this guy to be CNN + ) + + class Query(graphene.ObjectType): + all_reporters = DjangoConnectionField(ReporterType) + + schema = graphene.Schema(query=Query) + query = ''' + query ProxyModelQuery { + allReporters { + edges { + node { + id + } + } + } + } + ''' + + expected = { + 'allReporters': { + 'edges': [{ + 'node': { + 'id': 'UmVwb3J0ZXJUeXBlOjE=', + }, + }, + { + 'node': { + 'id': 'UmVwb3J0ZXJUeXBlOjI=', + }, + } + ] + } + } + + result = schema.execute(query) + assert not result.errors + assert result.data == expected + + +def test_proxy_model_fails(): + """ + This test asserts that if you try to query for a proxy model, + that query will fail with: + GraphQLError('Expected value of type "CNNReporterType" but got: + CNNReporter.',) + + This is because a proxy model has the identical model definition + to its superclass, and defines its behavior at runtime, rather than + at the database level. Currently, filtering objects of the proxy models' + type isn't supported. It would require a field on the model that would + represent the type, and it doesn't seem like there is a clear way to + enforce this pattern across all projects + """ + class CNNReporterType(DjangoObjectType): + + class Meta: + model = CNNReporter + interfaces = (Node, ) + use_connection = True + + reporter_1 = Reporter.objects.create( + first_name='John', + last_name='Doe', + email='johndoe@example.com', + a_choice=1 + ) + + reporter_2 = CNNReporter.objects.create( + first_name='Some', + last_name='Guy', + email='someguy@cnn.com', + a_choice=1, + reporter_type=2, # set this guy to be CNN + ) + + class Query(graphene.ObjectType): + all_reporters = DjangoConnectionField(CNNReporterType) + + schema = graphene.Schema(query=Query) + query = ''' + query ProxyModelQuery { + allReporters { + edges { + node { + id + } + } + } + } + ''' + + expected = { + 'allReporters': { + 'edges': [{ + 'node': { + 'id': 'UmVwb3J0ZXJUeXBlOjE=', + }, + }, + { + 'node': { + 'id': 'UmVwb3J0ZXJUeXBlOjI=', + }, + } + ] + } + } + + result = schema.execute(query) + assert result.errors diff --git a/graphene_django/tests/test_schema.py b/graphene_django/tests/test_schema.py index 32db1724a..904c04383 100644 --- a/graphene_django/tests/test_schema.py +++ b/graphene_django/tests/test_schema.py @@ -35,6 +35,7 @@ class Meta: 'email', 'pets', 'a_choice', + 'reporter_type' ] assert sorted(fields[-2:]) == [ diff --git a/graphene_django/tests/test_types.py b/graphene_django/tests/test_types.py index 83d9b4070..698502670 100644 --- a/graphene_django/tests/test_types.py +++ b/graphene_django/tests/test_types.py @@ -58,7 +58,7 @@ def test_django_get_node(get): def test_django_objecttype_map_correct_fields(): fields = Reporter._meta.fields fields = list(fields.keys()) - assert fields[:-2] == ['id', 'first_name', 'last_name', 'email', 'pets', 'a_choice'] + assert fields[:-2] == ['id', 'first_name', 'last_name', 'email', 'pets', 'a_choice', 'reporter_type'] assert sorted(fields[-2:]) == ['articles', 'films'] @@ -124,6 +124,7 @@ def test_schema_representation(): email: String! pets: [Reporter] aChoice: ReporterAChoice! + reporterType: ReporterReporterType articles(before: String, after: String, first: Int, last: Int): ArticleConnection } @@ -132,6 +133,11 @@ def test_schema_representation(): A_2 } +enum ReporterReporterType { + A_1 + A_2 +} + type RootQuery { node(id: ID!): Node } diff --git a/graphene_django/types.py b/graphene_django/types.py index 684863a89..889620ce5 100644 --- a/graphene_django/types.py +++ b/graphene_django/types.py @@ -108,7 +108,8 @@ def is_type_of(cls, root, info): raise Exception(( 'Received incompatible instance "{}".' ).format(root)) - model = root._meta.model + + model = root._meta.model._meta.concrete_model return model == cls._meta.model @classmethod