Skip to content

Commit 0b10341

Browse files
committed
on_delete param where needed due to django 2 requirements
added support for querying a model with objects that may be a proxy model, fixed and added tests a few style changes
1 parent b54e02c commit 0b10341

File tree

6 files changed

+209
-9
lines changed

6 files changed

+209
-9
lines changed

examples/starwars/models.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,36 @@
55

66
class Character(models.Model):
77
name = models.CharField(max_length=50)
8-
ship = models.ForeignKey('Ship', blank=True, null=True, related_name='characters')
8+
ship = models.ForeignKey(
9+
'Ship',
10+
blank=True,
11+
null=True,
12+
on_delete=models.SET_NULL,
13+
related_name='characters'
14+
)
915

1016
def __str__(self):
1117
return self.name
1218

1319

1420
class Faction(models.Model):
1521
name = models.CharField(max_length=50)
16-
hero = models.ForeignKey(Character)
22+
hero = models.ForeignKey(
23+
Character,
24+
on_delete=models.SET_NULL,
25+
)
1726

1827
def __str__(self):
1928
return self.name
2029

2130

2231
class Ship(models.Model):
2332
name = models.CharField(max_length=50)
24-
faction = models.ForeignKey(Faction, related_name='ships')
33+
faction = models.ForeignKey(
34+
Faction,
35+
on_delete=models.SET_NULL,
36+
related_name='ships'
37+
)
2538

2639
def __str__(self):
2740
return self.name

graphene_django/tests/models.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ class Pet(models.Model):
1515

1616
class FilmDetails(models.Model):
1717
location = models.CharField(max_length=30)
18-
film = models.OneToOneField('Film', related_name='details')
18+
film = models.OneToOneField(
19+
'Film',
20+
on_delete=models.CASCADE,
21+
related_name='details'
22+
)
1923

2024

2125
class Film(models.Model):
@@ -30,15 +34,49 @@ class Reporter(models.Model):
3034
pets = models.ManyToManyField('self')
3135
a_choice = models.CharField(max_length=30, choices=CHOICES)
3236

37+
reporter_type = models.IntegerField(
38+
'Reporter Type',
39+
null=True,
40+
blank=True,
41+
choices=[(1, u'Regular'), (2, u'CNN Reporter')] )
42+
3343
def __str__(self): # __unicode__ on Python 2
3444
return "%s %s" % (self.first_name, self.last_name)
3545

46+
def __init__(self, *args, **kwargs):
47+
"""
48+
Override the init method so that during runtime, Django
49+
can know that this object can be a CNNReporter by casting
50+
it to the proxy model. Otherwise, as far as Django knows,
51+
when a CNNReporter is pulled from the database, it is still
52+
of type Reporter. This was added to test proxy model support.
53+
"""
54+
super(Reporter, self).__init__(*args, **kwargs)
55+
if self.reporter_type == 2: # quick and dirty way without enums
56+
self.__class__ = CNNReporter
57+
58+
class CNNReporter(Reporter):
59+
"""
60+
This class is a proxy model for Reporter, used for testing
61+
proxy model support
62+
"""
63+
class Meta:
64+
proxy = True
65+
3666

3767
class Article(models.Model):
3868
headline = models.CharField(max_length=100)
3969
pub_date = models.DateField()
40-
reporter = models.ForeignKey(Reporter, related_name='articles')
41-
editor = models.ForeignKey(Reporter, related_name='edited_articles_+')
70+
reporter = models.ForeignKey(
71+
Reporter,
72+
on_delete=models.SET_NULL,
73+
related_name='articles'
74+
)
75+
editor = models.ForeignKey(
76+
Reporter,
77+
on_delete=models.SET_NULL,
78+
related_name='edited_articles_+'
79+
)
4280
lang = models.CharField(max_length=2, help_text='Language', choices=[
4381
('es', 'Spanish'),
4482
('en', 'English')

graphene_django/tests/test_query.py

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
from ..fields import DjangoConnectionField
1414
from ..types import DjangoObjectType
1515
from ..settings import graphene_settings
16-
from .models import Article, Reporter
16+
from .models import (
17+
Article,
18+
CNNReporter,
19+
Reporter,
20+
)
1721

1822
pytestmark = pytest.mark.django_db
1923

@@ -689,6 +693,7 @@ class Query(graphene.ObjectType):
689693
email='johndoe@example.com',
690694
a_choice=1
691695
)
696+
692697
Article.objects.create(
693698
headline='Article Node 1',
694699
pub_date=datetime.date.today(),
@@ -780,3 +785,139 @@ class Query(graphene.ObjectType):
780785
'''
781786
result = schema.execute(query)
782787
assert not result.errors
788+
789+
790+
def test_proxy_model_support():
791+
"""
792+
This test asserts that we can query for all Reporters,
793+
even if some are of a proxy model type at runtime.
794+
"""
795+
class ReporterType(DjangoObjectType):
796+
797+
class Meta:
798+
model = Reporter
799+
interfaces = (Node, )
800+
use_connection = True
801+
802+
reporter_1 = Reporter.objects.create(
803+
first_name='John',
804+
last_name='Doe',
805+
email='johndoe@example.com',
806+
a_choice=1
807+
)
808+
809+
reporter_2 = CNNReporter.objects.create(
810+
first_name='Some',
811+
last_name='Guy',
812+
email='someguy@cnn.com',
813+
a_choice=1,
814+
reporter_type=2, # set this guy to be CNN
815+
)
816+
817+
class Query(graphene.ObjectType):
818+
all_reporters = DjangoConnectionField(ReporterType)
819+
820+
schema = graphene.Schema(query=Query)
821+
query = '''
822+
query ProxyModelQuery {
823+
allReporters {
824+
edges {
825+
node {
826+
id
827+
}
828+
}
829+
}
830+
}
831+
'''
832+
833+
expected = {
834+
'allReporters': {
835+
'edges': [{
836+
'node': {
837+
'id': 'UmVwb3J0ZXJUeXBlOjE=',
838+
},
839+
},
840+
{
841+
'node': {
842+
'id': 'UmVwb3J0ZXJUeXBlOjI=',
843+
},
844+
}
845+
]
846+
}
847+
}
848+
849+
result = schema.execute(query)
850+
assert not result.errors
851+
assert result.data == expected
852+
853+
854+
def test_proxy_model_fails():
855+
"""
856+
This test asserts that if you try to query for a proxy model,
857+
that query will fail with:
858+
GraphQLError('Expected value of type "CNNReporterType" but got:
859+
CNNReporter.',)
860+
861+
This is because a proxy model has the identical model definition
862+
to its superclass, and defines its behavior at runtime, rather than
863+
at the database level. Currently, filtering objects of the proxy models'
864+
type isn't supported. It would require a field on the model that would
865+
represent the type, and it doesn't seem like there is a clear way to
866+
enforce this pattern across all projects
867+
"""
868+
class CNNReporterType(DjangoObjectType):
869+
870+
class Meta:
871+
model = CNNReporter
872+
interfaces = (Node, )
873+
use_connection = True
874+
875+
reporter_1 = Reporter.objects.create(
876+
first_name='John',
877+
last_name='Doe',
878+
email='johndoe@example.com',
879+
a_choice=1
880+
)
881+
882+
reporter_2 = CNNReporter.objects.create(
883+
first_name='Some',
884+
last_name='Guy',
885+
email='someguy@cnn.com',
886+
a_choice=1,
887+
reporter_type=2, # set this guy to be CNN
888+
)
889+
890+
class Query(graphene.ObjectType):
891+
all_reporters = DjangoConnectionField(CNNReporterType)
892+
893+
schema = graphene.Schema(query=Query)
894+
query = '''
895+
query ProxyModelQuery {
896+
allReporters {
897+
edges {
898+
node {
899+
id
900+
}
901+
}
902+
}
903+
}
904+
'''
905+
906+
expected = {
907+
'allReporters': {
908+
'edges': [{
909+
'node': {
910+
'id': 'UmVwb3J0ZXJUeXBlOjE=',
911+
},
912+
},
913+
{
914+
'node': {
915+
'id': 'UmVwb3J0ZXJUeXBlOjI=',
916+
},
917+
}
918+
]
919+
}
920+
}
921+
922+
result = schema.execute(query)
923+
assert result.errors

graphene_django/tests/test_schema.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class Meta:
3535
'email',
3636
'pets',
3737
'a_choice',
38+
'reporter_type'
3839
]
3940

4041
assert sorted(fields[-2:]) == [

graphene_django/tests/test_types.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def test_django_get_node(get):
5858
def test_django_objecttype_map_correct_fields():
5959
fields = Reporter._meta.fields
6060
fields = list(fields.keys())
61-
assert fields[:-2] == ['id', 'first_name', 'last_name', 'email', 'pets', 'a_choice']
61+
assert fields[:-2] == ['id', 'first_name', 'last_name', 'email', 'pets', 'a_choice', 'reporter_type']
6262
assert sorted(fields[-2:]) == ['articles', 'films']
6363

6464

@@ -124,6 +124,7 @@ def test_schema_representation():
124124
email: String!
125125
pets: [Reporter]
126126
aChoice: ReporterAChoice!
127+
reporterType: ReporterReporterType
127128
articles(before: String, after: String, first: Int, last: Int): ArticleConnection
128129
}
129130
@@ -132,6 +133,11 @@ def test_schema_representation():
132133
A_2
133134
}
134135
136+
enum ReporterReporterType {
137+
A_1
138+
A_2
139+
}
140+
135141
type RootQuery {
136142
node(id: ID!): Node
137143
}

graphene_django/types.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ def is_type_of(cls, root, info):
108108
raise Exception((
109109
'Received incompatible instance "{}".'
110110
).format(root))
111-
model = root._meta.model
111+
112+
model = root._meta.model._meta.concrete_model
112113
return model == cls._meta.model
113114

114115
@classmethod

0 commit comments

Comments
 (0)