mirror of
https://github.com/graphql-python/graphene-django.git
synced 2024-11-29 04:53:43 +03:00
Merge pull request #373 from jm2242/proxy-model-support
Basic Proxy model support
This commit is contained in:
commit
c0edb0c927
|
@ -35,9 +35,36 @@ class Reporter(models.Model):
|
||||||
objects = models.Manager()
|
objects = models.Manager()
|
||||||
doe_objects = DoeReporterManager()
|
doe_objects = DoeReporterManager()
|
||||||
|
|
||||||
|
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
|
def __str__(self): # __unicode__ on Python 2
|
||||||
return "%s %s" % (self.first_name, self.last_name)
|
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):
|
class Article(models.Model):
|
||||||
headline = models.CharField(max_length=100)
|
headline = models.CharField(max_length=100)
|
||||||
|
|
|
@ -13,7 +13,11 @@ from ..compat import MissingType, JSONField
|
||||||
from ..fields import DjangoConnectionField
|
from ..fields import DjangoConnectionField
|
||||||
from ..types import DjangoObjectType
|
from ..types import DjangoObjectType
|
||||||
from ..settings import graphene_settings
|
from ..settings import graphene_settings
|
||||||
from .models import Article, Reporter
|
from .models import (
|
||||||
|
Article,
|
||||||
|
CNNReporter,
|
||||||
|
Reporter,
|
||||||
|
)
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
@ -844,6 +848,7 @@ def test_should_query_dataloader_fields():
|
||||||
email='johndoe@example.com',
|
email='johndoe@example.com',
|
||||||
a_choice=1
|
a_choice=1
|
||||||
)
|
)
|
||||||
|
|
||||||
Article.objects.create(
|
Article.objects.create(
|
||||||
headline='Article Node 1',
|
headline='Article Node 1',
|
||||||
pub_date=datetime.date.today(),
|
pub_date=datetime.date.today(),
|
||||||
|
@ -937,3 +942,139 @@ def test_should_handle_inherited_choices():
|
||||||
'''
|
'''
|
||||||
result = schema.execute(query)
|
result = schema.execute(query)
|
||||||
assert not result.errors
|
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
|
||||||
|
|
|
@ -35,6 +35,7 @@ def test_should_map_fields_correctly():
|
||||||
'email',
|
'email',
|
||||||
'pets',
|
'pets',
|
||||||
'a_choice',
|
'a_choice',
|
||||||
|
'reporter_type'
|
||||||
]
|
]
|
||||||
|
|
||||||
assert sorted(fields[-2:]) == [
|
assert sorted(fields[-2:]) == [
|
||||||
|
|
|
@ -58,7 +58,7 @@ def test_django_get_node(get):
|
||||||
def test_django_objecttype_map_correct_fields():
|
def test_django_objecttype_map_correct_fields():
|
||||||
fields = Reporter._meta.fields
|
fields = Reporter._meta.fields
|
||||||
fields = list(fields.keys())
|
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']
|
assert sorted(fields[-2:]) == ['articles', 'films']
|
||||||
|
|
||||||
|
|
||||||
|
@ -147,6 +147,7 @@ type Reporter {
|
||||||
email: String!
|
email: String!
|
||||||
pets: [Reporter]
|
pets: [Reporter]
|
||||||
aChoice: ReporterAChoice!
|
aChoice: ReporterAChoice!
|
||||||
|
reporterType: ReporterReporterType
|
||||||
articles(before: String, after: String, first: Int, last: Int): ArticleConnection
|
articles(before: String, after: String, first: Int, last: Int): ArticleConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +156,11 @@ enum ReporterAChoice {
|
||||||
A_2
|
A_2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ReporterReporterType {
|
||||||
|
A_1
|
||||||
|
A_2
|
||||||
|
}
|
||||||
|
|
||||||
type RootQuery {
|
type RootQuery {
|
||||||
node(id: ID!): Node
|
node(id: ID!): Node
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,8 @@ class DjangoObjectType(ObjectType):
|
||||||
raise Exception((
|
raise Exception((
|
||||||
'Received incompatible instance "{}".'
|
'Received incompatible instance "{}".'
|
||||||
).format(root))
|
).format(root))
|
||||||
model = root._meta.model
|
|
||||||
|
model = root._meta.model._meta.concrete_model
|
||||||
return model == cls._meta.model
|
return model == cls._meta.model
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
Loading…
Reference in New Issue
Block a user