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
This commit is contained in:
Jonathan Mares 2018-01-14 22:36:52 -05:00
parent b54e02c9ba
commit 0b103417f9
6 changed files with 209 additions and 9 deletions

View File

@ -5,7 +5,13 @@ from django.db import models
class Character(models.Model): class Character(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
ship = models.ForeignKey('Ship', blank=True, null=True, related_name='characters') ship = models.ForeignKey(
'Ship',
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name='characters'
)
def __str__(self): def __str__(self):
return self.name return self.name
@ -13,7 +19,10 @@ class Character(models.Model):
class Faction(models.Model): class Faction(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
hero = models.ForeignKey(Character) hero = models.ForeignKey(
Character,
on_delete=models.SET_NULL,
)
def __str__(self): def __str__(self):
return self.name return self.name
@ -21,7 +30,11 @@ class Faction(models.Model):
class Ship(models.Model): class Ship(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
faction = models.ForeignKey(Faction, related_name='ships') faction = models.ForeignKey(
Faction,
on_delete=models.SET_NULL,
related_name='ships'
)
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -15,7 +15,11 @@ class Pet(models.Model):
class FilmDetails(models.Model): class FilmDetails(models.Model):
location = models.CharField(max_length=30) location = models.CharField(max_length=30)
film = models.OneToOneField('Film', related_name='details') film = models.OneToOneField(
'Film',
on_delete=models.CASCADE,
related_name='details'
)
class Film(models.Model): class Film(models.Model):
@ -30,15 +34,49 @@ class Reporter(models.Model):
pets = models.ManyToManyField('self') pets = models.ManyToManyField('self')
a_choice = models.CharField(max_length=30, choices=CHOICES) 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 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)
pub_date = models.DateField() pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, related_name='articles') reporter = models.ForeignKey(
editor = models.ForeignKey(Reporter, related_name='edited_articles_+') Reporter,
on_delete=models.SET_NULL,
related_name='articles'
)
editor = models.ForeignKey(
Reporter,
on_delete=models.SET_NULL,
related_name='edited_articles_+'
)
lang = models.CharField(max_length=2, help_text='Language', choices=[ lang = models.CharField(max_length=2, help_text='Language', choices=[
('es', 'Spanish'), ('es', 'Spanish'),
('en', 'English') ('en', 'English')

View File

@ -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
@ -689,6 +693,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(),
@ -780,3 +785,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

View File

@ -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:]) == [

View File

@ -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']
@ -124,6 +124,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
} }
@ -132,6 +133,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
} }

View File

@ -108,7 +108,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