mirror of
				https://github.com/graphql-python/graphene-django.git
				synced 2025-10-31 16:07:36 +03:00 
			
		
		
		
	fix: unit test for graphene pr#1412 (#1315)
* Issue #1111: foreign key should also call get_queryset method * fix: test for graphene PR https://github.com/graphql-python/graphene/pull/1412 Co-authored-by: Thomas Leonard <thomas@loftorbital.com>
This commit is contained in:
		
							parent
							
								
									b2f83eb277
								
							
						
					
					
						commit
						5d81ba04f9
					
				|  | @ -308,7 +308,24 @@ def convert_field_to_djangomodel(field, registry=None): | ||||||
|         if not _type: |         if not _type: | ||||||
|             return |             return | ||||||
| 
 | 
 | ||||||
|         return Field( |         class CustomField(Field): | ||||||
|  |             def wrap_resolve(self, parent_resolver): | ||||||
|  |                 """ | ||||||
|  |                 Implements a custom resolver which go through the `get_node` method to insure that | ||||||
|  |                 it goes through the `get_queryset` method of the DjangoObjectType. | ||||||
|  |                 """ | ||||||
|  |                 resolver = super().wrap_resolve(parent_resolver) | ||||||
|  | 
 | ||||||
|  |                 def custom_resolver(root, info, **args): | ||||||
|  |                     fk_obj = resolver(root, info, **args) | ||||||
|  |                     if fk_obj is None: | ||||||
|  |                         return None | ||||||
|  |                     else: | ||||||
|  |                         return _type.get_node(info, fk_obj.pk) | ||||||
|  | 
 | ||||||
|  |                 return custom_resolver | ||||||
|  | 
 | ||||||
|  |         return CustomField( | ||||||
|             _type, |             _type, | ||||||
|             description=get_django_field_description(field), |             description=get_django_field_description(field), | ||||||
|             required=not field.null, |             required=not field.null, | ||||||
|  |  | ||||||
|  | @ -13,6 +13,9 @@ class Person(models.Model): | ||||||
| class Pet(models.Model): | class Pet(models.Model): | ||||||
|     name = models.CharField(max_length=30) |     name = models.CharField(max_length=30) | ||||||
|     age = models.PositiveIntegerField() |     age = models.PositiveIntegerField() | ||||||
|  |     owner = models.ForeignKey( | ||||||
|  |         "Person", on_delete=models.CASCADE, null=True, blank=True, related_name="pets" | ||||||
|  |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class FilmDetails(models.Model): | class FilmDetails(models.Model): | ||||||
|  |  | ||||||
							
								
								
									
										355
									
								
								graphene_django/tests/test_get_queryset.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										355
									
								
								graphene_django/tests/test_get_queryset.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,355 @@ | ||||||
|  | import pytest | ||||||
|  | 
 | ||||||
|  | import graphene | ||||||
|  | from graphene.relay import Node | ||||||
|  | 
 | ||||||
|  | from graphql_relay import to_global_id | ||||||
|  | 
 | ||||||
|  | from ..fields import DjangoConnectionField | ||||||
|  | from ..types import DjangoObjectType | ||||||
|  | 
 | ||||||
|  | from .models import Article, Reporter | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TestShouldCallGetQuerySetOnForeignKey: | ||||||
|  |     """ | ||||||
|  |     Check that the get_queryset method is called in both forward and reversed direction | ||||||
|  |     of a foreignkey on types. | ||||||
|  |     (see issue #1111) | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     @pytest.fixture(autouse=True) | ||||||
|  |     def setup_schema(self): | ||||||
|  |         class ReporterType(DjangoObjectType): | ||||||
|  |             class Meta: | ||||||
|  |                 model = Reporter | ||||||
|  | 
 | ||||||
|  |             @classmethod | ||||||
|  |             def get_queryset(cls, queryset, info): | ||||||
|  |                 if info.context and info.context.get("admin"): | ||||||
|  |                     return queryset | ||||||
|  |                 raise Exception("Not authorized to access reporters.") | ||||||
|  | 
 | ||||||
|  |         class ArticleType(DjangoObjectType): | ||||||
|  |             class Meta: | ||||||
|  |                 model = Article | ||||||
|  | 
 | ||||||
|  |             @classmethod | ||||||
|  |             def get_queryset(cls, queryset, info): | ||||||
|  |                 return queryset.exclude(headline__startswith="Draft") | ||||||
|  | 
 | ||||||
|  |         class Query(graphene.ObjectType): | ||||||
|  |             reporter = graphene.Field(ReporterType, id=graphene.ID(required=True)) | ||||||
|  |             article = graphene.Field(ArticleType, id=graphene.ID(required=True)) | ||||||
|  | 
 | ||||||
|  |             def resolve_reporter(self, info, id): | ||||||
|  |                 return ( | ||||||
|  |                     ReporterType.get_queryset(Reporter.objects, info) | ||||||
|  |                     .filter(id=id) | ||||||
|  |                     .last() | ||||||
|  |                 ) | ||||||
|  | 
 | ||||||
|  |             def resolve_article(self, info, id): | ||||||
|  |                 return ( | ||||||
|  |                     ArticleType.get_queryset(Article.objects, info).filter(id=id).last() | ||||||
|  |                 ) | ||||||
|  | 
 | ||||||
|  |         self.schema = graphene.Schema(query=Query) | ||||||
|  | 
 | ||||||
|  |         self.reporter = Reporter.objects.create(first_name="Jane", last_name="Doe") | ||||||
|  | 
 | ||||||
|  |         self.articles = [ | ||||||
|  |             Article.objects.create( | ||||||
|  |                 headline="A fantastic article", | ||||||
|  |                 reporter=self.reporter, | ||||||
|  |                 editor=self.reporter, | ||||||
|  |             ), | ||||||
|  |             Article.objects.create( | ||||||
|  |                 headline="Draft: My next best seller", | ||||||
|  |                 reporter=self.reporter, | ||||||
|  |                 editor=self.reporter, | ||||||
|  |             ), | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |     def test_get_queryset_called_on_field(self): | ||||||
|  |         # If a user tries to access an article it is fine as long as it's not a draft one | ||||||
|  |         query = """ | ||||||
|  |             query getArticle($id: ID!) { | ||||||
|  |                 article(id: $id) { | ||||||
|  |                     headline | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         """ | ||||||
|  |         # Non-draft | ||||||
|  |         result = self.schema.execute(query, variables={"id": self.articles[0].id}) | ||||||
|  |         assert not result.errors | ||||||
|  |         assert result.data["article"] == { | ||||||
|  |             "headline": "A fantastic article", | ||||||
|  |         } | ||||||
|  |         # Draft | ||||||
|  |         result = self.schema.execute(query, variables={"id": self.articles[1].id}) | ||||||
|  |         assert not result.errors | ||||||
|  |         assert result.data["article"] is None | ||||||
|  | 
 | ||||||
|  |         # If a non admin user tries to access a reporter they should get our authorization error | ||||||
|  |         query = """ | ||||||
|  |             query getReporter($id: ID!) { | ||||||
|  |                 reporter(id: $id) { | ||||||
|  |                     firstName | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |         result = self.schema.execute(query, variables={"id": self.reporter.id}) | ||||||
|  |         assert len(result.errors) == 1 | ||||||
|  |         assert result.errors[0].message == "Not authorized to access reporters." | ||||||
|  | 
 | ||||||
|  |         # An admin user should be able to get reporters | ||||||
|  |         query = """ | ||||||
|  |             query getReporter($id: ID!) { | ||||||
|  |                 reporter(id: $id) { | ||||||
|  |                     firstName | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |         result = self.schema.execute( | ||||||
|  |             query, variables={"id": self.reporter.id}, context_value={"admin": True}, | ||||||
|  |         ) | ||||||
|  |         assert not result.errors | ||||||
|  |         assert result.data == {"reporter": {"firstName": "Jane"}} | ||||||
|  | 
 | ||||||
|  |     def test_get_queryset_called_on_foreignkey(self): | ||||||
|  |         # If a user tries to access a reporter through an article they should get our authorization error | ||||||
|  |         query = """ | ||||||
|  |             query getArticle($id: ID!) { | ||||||
|  |                 article(id: $id) { | ||||||
|  |                     headline | ||||||
|  |                     reporter { | ||||||
|  |                         firstName | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |         result = self.schema.execute(query, variables={"id": self.articles[0].id}) | ||||||
|  |         assert len(result.errors) == 1 | ||||||
|  |         assert result.errors[0].message == "Not authorized to access reporters." | ||||||
|  | 
 | ||||||
|  |         # An admin user should be able to get reporters through an article | ||||||
|  |         query = """ | ||||||
|  |             query getArticle($id: ID!) { | ||||||
|  |                 article(id: $id) { | ||||||
|  |                     headline | ||||||
|  |                     reporter { | ||||||
|  |                         firstName | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |         result = self.schema.execute( | ||||||
|  |             query, variables={"id": self.articles[0].id}, context_value={"admin": True}, | ||||||
|  |         ) | ||||||
|  |         assert not result.errors | ||||||
|  |         assert result.data["article"] == { | ||||||
|  |             "headline": "A fantastic article", | ||||||
|  |             "reporter": {"firstName": "Jane"}, | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         # An admin user should not be able to access draft article through a reporter | ||||||
|  |         query = """ | ||||||
|  |             query getReporter($id: ID!) { | ||||||
|  |                 reporter(id: $id) { | ||||||
|  |                     firstName | ||||||
|  |                     articles { | ||||||
|  |                         headline | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |         result = self.schema.execute( | ||||||
|  |             query, variables={"id": self.reporter.id}, context_value={"admin": True}, | ||||||
|  |         ) | ||||||
|  |         assert not result.errors | ||||||
|  |         assert result.data["reporter"] == { | ||||||
|  |             "firstName": "Jane", | ||||||
|  |             "articles": [{"headline": "A fantastic article"}], | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TestShouldCallGetQuerySetOnForeignKeyNode: | ||||||
|  |     """ | ||||||
|  |     Check that the get_queryset method is called in both forward and reversed direction | ||||||
|  |     of a foreignkey on types using a node interface. | ||||||
|  |     (see issue #1111) | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     @pytest.fixture(autouse=True) | ||||||
|  |     def setup_schema(self): | ||||||
|  |         class ReporterType(DjangoObjectType): | ||||||
|  |             class Meta: | ||||||
|  |                 model = Reporter | ||||||
|  |                 interfaces = (Node,) | ||||||
|  | 
 | ||||||
|  |             @classmethod | ||||||
|  |             def get_queryset(cls, queryset, info): | ||||||
|  |                 if info.context and info.context.get("admin"): | ||||||
|  |                     return queryset | ||||||
|  |                 raise Exception("Not authorized to access reporters.") | ||||||
|  | 
 | ||||||
|  |         class ArticleType(DjangoObjectType): | ||||||
|  |             class Meta: | ||||||
|  |                 model = Article | ||||||
|  |                 interfaces = (Node,) | ||||||
|  | 
 | ||||||
|  |             @classmethod | ||||||
|  |             def get_queryset(cls, queryset, info): | ||||||
|  |                 return queryset.exclude(headline__startswith="Draft") | ||||||
|  | 
 | ||||||
|  |         class Query(graphene.ObjectType): | ||||||
|  |             reporter = Node.Field(ReporterType) | ||||||
|  |             article = Node.Field(ArticleType) | ||||||
|  | 
 | ||||||
|  |         self.schema = graphene.Schema(query=Query) | ||||||
|  | 
 | ||||||
|  |         self.reporter = Reporter.objects.create(first_name="Jane", last_name="Doe") | ||||||
|  | 
 | ||||||
|  |         self.articles = [ | ||||||
|  |             Article.objects.create( | ||||||
|  |                 headline="A fantastic article", | ||||||
|  |                 reporter=self.reporter, | ||||||
|  |                 editor=self.reporter, | ||||||
|  |             ), | ||||||
|  |             Article.objects.create( | ||||||
|  |                 headline="Draft: My next best seller", | ||||||
|  |                 reporter=self.reporter, | ||||||
|  |                 editor=self.reporter, | ||||||
|  |             ), | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |     def test_get_queryset_called_on_node(self): | ||||||
|  |         # If a user tries to access an article it is fine as long as it's not a draft one | ||||||
|  |         query = """ | ||||||
|  |             query getArticle($id: ID!) { | ||||||
|  |                 article(id: $id) { | ||||||
|  |                     headline | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         """ | ||||||
|  |         # Non-draft | ||||||
|  |         result = self.schema.execute( | ||||||
|  |             query, variables={"id": to_global_id("ArticleType", self.articles[0].id)} | ||||||
|  |         ) | ||||||
|  |         assert not result.errors | ||||||
|  |         assert result.data["article"] == { | ||||||
|  |             "headline": "A fantastic article", | ||||||
|  |         } | ||||||
|  |         # Draft | ||||||
|  |         result = self.schema.execute( | ||||||
|  |             query, variables={"id": to_global_id("ArticleType", self.articles[1].id)} | ||||||
|  |         ) | ||||||
|  |         assert not result.errors | ||||||
|  |         assert result.data["article"] is None | ||||||
|  | 
 | ||||||
|  |         # If a non admin user tries to access a reporter they should get our authorization error | ||||||
|  |         query = """ | ||||||
|  |             query getReporter($id: ID!) { | ||||||
|  |                 reporter(id: $id) { | ||||||
|  |                     firstName | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |         result = self.schema.execute( | ||||||
|  |             query, variables={"id": to_global_id("ReporterType", self.reporter.id)} | ||||||
|  |         ) | ||||||
|  |         assert len(result.errors) == 1 | ||||||
|  |         assert result.errors[0].message == "Not authorized to access reporters." | ||||||
|  | 
 | ||||||
|  |         # An admin user should be able to get reporters | ||||||
|  |         query = """ | ||||||
|  |             query getReporter($id: ID!) { | ||||||
|  |                 reporter(id: $id) { | ||||||
|  |                     firstName | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |         result = self.schema.execute( | ||||||
|  |             query, | ||||||
|  |             variables={"id": to_global_id("ReporterType", self.reporter.id)}, | ||||||
|  |             context_value={"admin": True}, | ||||||
|  |         ) | ||||||
|  |         assert not result.errors | ||||||
|  |         assert result.data == {"reporter": {"firstName": "Jane"}} | ||||||
|  | 
 | ||||||
|  |     def test_get_queryset_called_on_foreignkey(self): | ||||||
|  |         # If a user tries to access a reporter through an article they should get our authorization error | ||||||
|  |         query = """ | ||||||
|  |             query getArticle($id: ID!) { | ||||||
|  |                 article(id: $id) { | ||||||
|  |                     headline | ||||||
|  |                     reporter { | ||||||
|  |                         firstName | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |         result = self.schema.execute( | ||||||
|  |             query, variables={"id": to_global_id("ArticleType", self.articles[0].id)} | ||||||
|  |         ) | ||||||
|  |         assert len(result.errors) == 1 | ||||||
|  |         assert result.errors[0].message == "Not authorized to access reporters." | ||||||
|  | 
 | ||||||
|  |         # An admin user should be able to get reporters through an article | ||||||
|  |         query = """ | ||||||
|  |             query getArticle($id: ID!) { | ||||||
|  |                 article(id: $id) { | ||||||
|  |                     headline | ||||||
|  |                     reporter { | ||||||
|  |                         firstName | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |         result = self.schema.execute( | ||||||
|  |             query, | ||||||
|  |             variables={"id": to_global_id("ArticleType", self.articles[0].id)}, | ||||||
|  |             context_value={"admin": True}, | ||||||
|  |         ) | ||||||
|  |         assert not result.errors | ||||||
|  |         assert result.data["article"] == { | ||||||
|  |             "headline": "A fantastic article", | ||||||
|  |             "reporter": {"firstName": "Jane"}, | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         # An admin user should not be able to access draft article through a reporter | ||||||
|  |         query = """ | ||||||
|  |             query getReporter($id: ID!) { | ||||||
|  |                 reporter(id: $id) { | ||||||
|  |                     firstName | ||||||
|  |                     articles { | ||||||
|  |                         edges { | ||||||
|  |                             node { | ||||||
|  |                                 headline | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |         result = self.schema.execute( | ||||||
|  |             query, | ||||||
|  |             variables={"id": to_global_id("ReporterType", self.reporter.id)}, | ||||||
|  |             context_value={"admin": True}, | ||||||
|  |         ) | ||||||
|  |         assert not result.errors | ||||||
|  |         assert result.data["reporter"] == { | ||||||
|  |             "firstName": "Jane", | ||||||
|  |             "articles": {"edges": [{"node": {"headline": "A fantastic article"}}]}, | ||||||
|  |         } | ||||||
|  | @ -15,7 +15,7 @@ from ..compat import IntegerRangeField, MissingType | ||||||
| from ..fields import DjangoConnectionField | from ..fields import DjangoConnectionField | ||||||
| from ..types import DjangoObjectType | from ..types import DjangoObjectType | ||||||
| from ..utils import DJANGO_FILTER_INSTALLED | from ..utils import DJANGO_FILTER_INSTALLED | ||||||
| from .models import Article, CNNReporter, Film, FilmDetails, Reporter | from .models import Article, CNNReporter, Film, FilmDetails, Person, Pet, Reporter | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_query_only_fields(): | def test_should_query_only_fields(): | ||||||
|  | @ -251,8 +251,8 @@ def test_should_node(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_should_query_onetoone_fields(): | def test_should_query_onetoone_fields(): | ||||||
|     film = Film(id=1) |     film = Film.objects.create(id=1) | ||||||
|     film_details = FilmDetails(id=1, film=film) |     film_details = FilmDetails.objects.create(id=1, film=film) | ||||||
| 
 | 
 | ||||||
|     class FilmNode(DjangoObjectType): |     class FilmNode(DjangoObjectType): | ||||||
|         class Meta: |         class Meta: | ||||||
|  | @ -1697,3 +1697,67 @@ def test_connection_should_succeed_if_last_higher_than_number_of_objects(): | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     assert result.data == expected |     assert result.data == expected | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_should_query_nullable_foreign_key(): | ||||||
|  |     class PetType(DjangoObjectType): | ||||||
|  |         class Meta: | ||||||
|  |             model = Pet | ||||||
|  | 
 | ||||||
|  |     class PersonType(DjangoObjectType): | ||||||
|  |         class Meta: | ||||||
|  |             model = Person | ||||||
|  | 
 | ||||||
|  |     class Query(graphene.ObjectType): | ||||||
|  |         pet = graphene.Field(PetType, name=graphene.String(required=True)) | ||||||
|  |         person = graphene.Field(PersonType, name=graphene.String(required=True)) | ||||||
|  | 
 | ||||||
|  |         def resolve_pet(self, info, name): | ||||||
|  |             return Pet.objects.filter(name=name).first() | ||||||
|  | 
 | ||||||
|  |         def resolve_person(self, info, name): | ||||||
|  |             return Person.objects.filter(name=name).first() | ||||||
|  | 
 | ||||||
|  |     schema = graphene.Schema(query=Query) | ||||||
|  | 
 | ||||||
|  |     person = Person.objects.create(name="Jane") | ||||||
|  |     pets = [ | ||||||
|  |         Pet.objects.create(name="Stray dog", age=1), | ||||||
|  |         Pet.objects.create(name="Jane's dog", owner=person, age=1), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     query_pet = """ | ||||||
|  |         query getPet($name: String!) { | ||||||
|  |             pet(name: $name) { | ||||||
|  |                 owner { | ||||||
|  |                     name | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     """ | ||||||
|  |     result = schema.execute(query_pet, variables={"name": "Stray dog"}) | ||||||
|  |     assert not result.errors | ||||||
|  |     assert result.data["pet"] == { | ||||||
|  |         "owner": None, | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     result = schema.execute(query_pet, variables={"name": "Jane's dog"}) | ||||||
|  |     assert not result.errors | ||||||
|  |     assert result.data["pet"] == { | ||||||
|  |         "owner": {"name": "Jane"}, | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     query_owner = """ | ||||||
|  |         query getOwner($name: String!) { | ||||||
|  |             person(name: $name) { | ||||||
|  |                 pets { | ||||||
|  |                     name | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     """ | ||||||
|  |     result = schema.execute(query_owner, variables={"name": "Jane"}) | ||||||
|  |     assert not result.errors | ||||||
|  |     assert result.data["person"] == { | ||||||
|  |         "pets": [{"name": "Jane's dog"}], | ||||||
|  |     } | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								setup.py
									
									
									
									
									
								
							|  | @ -59,7 +59,8 @@ setup( | ||||||
|     keywords="api graphql protocol rest relay graphene", |     keywords="api graphql protocol rest relay graphene", | ||||||
|     packages=find_packages(exclude=["tests", "examples", "examples.*"]), |     packages=find_packages(exclude=["tests", "examples", "examples.*"]), | ||||||
|     install_requires=[ |     install_requires=[ | ||||||
|         "graphene>=3.0,<4", |         # "graphene>=3.0,<4", | ||||||
|  |         "graphene @ git+https://github.com/loft-orbital/graphene.git@loft-v3-1.0#egg=graphene", | ||||||
|         "graphql-core>=3.1.0,<4", |         "graphql-core>=3.1.0,<4", | ||||||
|         "graphql-relay>=3.1.1,<4", |         "graphql-relay>=3.1.1,<4", | ||||||
|         "Django>=3.2", |         "Django>=3.2", | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user