mirror of
				https://github.com/graphql-python/graphene.git
				synced 2025-10-31 16:07:27 +03:00 
			
		
		
		
	Merge pull request #79 from syrusakbary/feature/django
Improved Django integration
This commit is contained in:
		
						commit
						b18fbb5ace
					
				|  | @ -58,6 +58,7 @@ def convert_field_to_float(field): | ||||||
| 
 | 
 | ||||||
| @convert_django_field.register(models.ManyToManyField) | @convert_django_field.register(models.ManyToManyField) | ||||||
| @convert_django_field.register(models.ManyToOneRel) | @convert_django_field.register(models.ManyToOneRel) | ||||||
|  | @convert_django_field.register(models.ManyToManyRel) | ||||||
| def convert_field_to_list_or_connection(field): | def convert_field_to_list_or_connection(field): | ||||||
|     from .fields import DjangoModelField, ConnectionOrListField |     from .fields import DjangoModelField, ConnectionOrListField | ||||||
|     model_field = DjangoModelField(get_related_model(field)) |     model_field = DjangoModelField(get_related_model(field)) | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ from ...core.types.base import FieldType | ||||||
| from ...core.types.definitions import List | from ...core.types.definitions import List | ||||||
| from ...relay import ConnectionField | from ...relay import ConnectionField | ||||||
| from ...relay.utils import is_node | from ...relay.utils import is_node | ||||||
| from .utils import get_type_for_model, maybe_queryset | from .utils import DJANGO_FILTER_INSTALLED, get_type_for_model, maybe_queryset | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class DjangoConnectionField(ConnectionField): | class DjangoConnectionField(ConnectionField): | ||||||
|  | @ -37,6 +37,7 @@ class DjangoConnectionField(ConnectionField): | ||||||
| class ConnectionOrListField(Field): | class ConnectionOrListField(Field): | ||||||
| 
 | 
 | ||||||
|     def internal_type(self, schema): |     def internal_type(self, schema): | ||||||
|  |         if DJANGO_FILTER_INSTALLED: | ||||||
|             from .filter.fields import DjangoFilterConnectionField |             from .filter.fields import DjangoFilterConnectionField | ||||||
| 
 | 
 | ||||||
|         model_field = self.type |         model_field = self.type | ||||||
|  |  | ||||||
|  | @ -1,11 +1,12 @@ | ||||||
|  | import warnings | ||||||
| from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED | from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED | ||||||
| 
 | 
 | ||||||
| if not DJANGO_FILTER_INSTALLED: | if not DJANGO_FILTER_INSTALLED: | ||||||
|     raise Exception( |     warnings.warn( | ||||||
|         "Use of django filtering requires the django-filter package " |         "Use of django filtering requires the django-filter package " | ||||||
|         "be installed. You can do so using `pip install django-filter`" |         "be installed. You can do so using `pip install django-filter`", ImportWarning | ||||||
|     ) |     ) | ||||||
| 
 | else: | ||||||
|     from .fields import DjangoFilterConnectionField |     from .fields import DjangoFilterConnectionField | ||||||
|     from .filterset import GrapheneFilterSet, GlobalIDFilter, GlobalIDMultipleChoiceFilter |     from .filterset import GrapheneFilterSet, GlobalIDFilter, GlobalIDMultipleChoiceFilter | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,10 +2,10 @@ import six | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.db import models | from django.db import models | ||||||
| from django.utils.text import capfirst | from django.utils.text import capfirst | ||||||
| from django_filters import Filter, MultipleChoiceFilter |  | ||||||
| from django_filters.filterset import FilterSet, FilterSetMetaclass |  | ||||||
| from graphql_relay.node.node import from_global_id | from graphql_relay.node.node import from_global_id | ||||||
| 
 | 
 | ||||||
|  | from django_filters import Filter, MultipleChoiceFilter | ||||||
|  | from django_filters.filterset import FilterSet, FilterSetMetaclass | ||||||
| from graphene.contrib.django.forms import (GlobalIDFormField, | from graphene.contrib.django.forms import (GlobalIDFormField, | ||||||
|                                            GlobalIDMultipleChoiceField) |                                            GlobalIDMultipleChoiceField) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| import django_filters | import django_filters | ||||||
| 
 |  | ||||||
| from graphene.contrib.django.tests.models import Article, Pet, Reporter | from graphene.contrib.django.tests.models import Article, Pet, Reporter | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -69,8 +69,8 @@ def assert_not_orderable(field): | ||||||
| def test_filter_explicit_filterset_arguments(): | def test_filter_explicit_filterset_arguments(): | ||||||
|     field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleFilter) |     field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleFilter) | ||||||
|     assert_arguments(field, |     assert_arguments(field, | ||||||
|                      'headline', 'headlineIcontains', |                      'headline', 'headline_Icontains', | ||||||
|                      'pubDate', 'pubDateGt', 'pubDateLt', |                      'pubDate', 'pubDate_Gt', 'pubDate_Lt', | ||||||
|                      'reporter', |                      'reporter', | ||||||
|                      ) |                      ) | ||||||
| 
 | 
 | ||||||
|  | @ -89,7 +89,7 @@ def test_filter_shortcut_filterset_arguments_dict(): | ||||||
|         'reporter': ['exact'], |         'reporter': ['exact'], | ||||||
|     }) |     }) | ||||||
|     assert_arguments(field, |     assert_arguments(field, | ||||||
|                      'headline', 'headlineIcontains', |                      'headline', 'headline_Icontains', | ||||||
|                      'reporter', |                      'reporter', | ||||||
|                      ) |                      ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,12 +1,33 @@ | ||||||
| import importlib | import importlib | ||||||
| import json | import json | ||||||
|  | from distutils.version import StrictVersion | ||||||
|  | from optparse import make_option | ||||||
| 
 | 
 | ||||||
|  | from django import get_version as get_django_version | ||||||
| from django.core.management.base import BaseCommand, CommandError | from django.core.management.base import BaseCommand, CommandError | ||||||
| 
 | 
 | ||||||
|  | LT_DJANGO_1_8 = StrictVersion(get_django_version()) < StrictVersion('1.8') | ||||||
| 
 | 
 | ||||||
| class Command(BaseCommand): | if LT_DJANGO_1_8: | ||||||
|     help = 'Dump Graphene schema JSON to file' |     class CommandArguments(BaseCommand): | ||||||
|     can_import_settings = True |         option_list = BaseCommand.option_list + ( | ||||||
|  |             make_option( | ||||||
|  |                 '--schema', | ||||||
|  |                 type=str, | ||||||
|  |                 dest='schema', | ||||||
|  |                 default='', | ||||||
|  |                 help='Django app containing schema to dump, e.g. myproject.core.schema', | ||||||
|  |             ), | ||||||
|  |             make_option( | ||||||
|  |                 '--out', | ||||||
|  |                 type=str, | ||||||
|  |                 dest='out', | ||||||
|  |                 default='', | ||||||
|  |                 help='Output file (default: schema.json)' | ||||||
|  |             ), | ||||||
|  |         ) | ||||||
|  | else: | ||||||
|  |     class CommandArguments(BaseCommand): | ||||||
| 
 | 
 | ||||||
|         def add_arguments(self, parser): |         def add_arguments(self, parser): | ||||||
|             from django.conf import settings |             from django.conf import settings | ||||||
|  | @ -24,15 +45,28 @@ class Command(BaseCommand): | ||||||
|                 default=getattr(settings, 'GRAPHENE_SCHEMA_OUTPUT', 'schema.json'), |                 default=getattr(settings, 'GRAPHENE_SCHEMA_OUTPUT', 'schema.json'), | ||||||
|                 help='Output file (default: schema.json)') |                 help='Output file (default: schema.json)') | ||||||
| 
 | 
 | ||||||
|     def handle(self, *args, **options): |  | ||||||
|         schema_module = options['schema'] |  | ||||||
|         if schema_module == '': |  | ||||||
|             raise CommandError('Specify schema on GRAPHENE_SCHEMA setting or by using --schema') |  | ||||||
|         i = importlib.import_module(schema_module) |  | ||||||
| 
 | 
 | ||||||
|         schema_dict = {'data': i.schema.introspect()} | class Command(CommandArguments): | ||||||
|  |     help = 'Dump Graphene schema JSON to file' | ||||||
|  |     can_import_settings = True | ||||||
| 
 | 
 | ||||||
|         with open(options['out'], 'w') as outfile: |     def save_file(self, out, schema_dict): | ||||||
|  |         with open(out, 'w') as outfile: | ||||||
|             json.dump(schema_dict, outfile) |             json.dump(schema_dict, outfile) | ||||||
| 
 | 
 | ||||||
|         self.stdout.write(self.style.SUCCESS('Successfully dumped GraphQL schema to %s' % options['out'])) |     def handle(self, *args, **options): | ||||||
|  |         from django.conf import settings | ||||||
|  |         schema = options.get('schema') or getattr(settings, 'GRAPHENE_SCHEMA', '') | ||||||
|  |         out = options.get('out') or getattr(settings, 'GRAPHENE_SCHEMA_OUTPUT', 'schema.json') | ||||||
|  | 
 | ||||||
|  |         if schema == '': | ||||||
|  |             raise CommandError('Specify schema on GRAPHENE_SCHEMA setting or by using --schema') | ||||||
|  |         i = importlib.import_module(schema) | ||||||
|  | 
 | ||||||
|  |         schema_dict = {'data': i.schema.introspect()} | ||||||
|  |         self.save_file(out, schema_dict) | ||||||
|  | 
 | ||||||
|  |         style = getattr(self, 'style', None) | ||||||
|  |         SUCCESS = getattr(style, 'SUCCESS', lambda x: x) | ||||||
|  | 
 | ||||||
|  |         self.stdout.write(SUCCESS('Successfully dumped GraphQL schema to %s' % out)) | ||||||
|  |  | ||||||
|  | @ -7,6 +7,11 @@ class Pet(models.Model): | ||||||
|     name = models.CharField(max_length=30) |     name = models.CharField(max_length=30) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class Film(models.Model): | ||||||
|  |     reporters = models.ManyToManyField('Reporter', | ||||||
|  |                                        related_name='films') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class Reporter(models.Model): | class Reporter(models.Model): | ||||||
|     first_name = models.CharField(max_length=30) |     first_name = models.CharField(max_length=30) | ||||||
|     last_name = models.CharField(max_length=30) |     last_name = models.CharField(max_length=30) | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								graphene/contrib/django/tests/test_command.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								graphene/contrib/django/tests/test_command.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | ||||||
|  | from django.core import management | ||||||
|  | from mock import patch | ||||||
|  | from six import StringIO | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @patch('graphene.contrib.django.management.commands.graphql_schema.Command.save_file') | ||||||
|  | def test_generate_file_on_call_graphql_schema(savefile_mock, settings): | ||||||
|  |     settings.GRAPHENE_SCHEMA = 'graphene.contrib.django.tests.test_urls' | ||||||
|  |     out = StringIO() | ||||||
|  |     management.call_command('graphql_schema', schema='', stdout=out) | ||||||
|  |     assert "Successfully dumped GraphQL schema to schema.json" in out.getvalue() | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| from py.test import raises | from py.test import raises | ||||||
| from tests.utils import assert_equal_lists |  | ||||||
| 
 | 
 | ||||||
| from graphene.contrib.django import DjangoObjectType | from graphene.contrib.django import DjangoObjectType | ||||||
|  | from tests.utils import assert_equal_lists | ||||||
| 
 | 
 | ||||||
| from .models import Reporter | from .models import Reporter | ||||||
| 
 | 
 | ||||||
|  | @ -29,7 +29,7 @@ def test_should_map_fields_correctly(): | ||||||
|             model = Reporter |             model = Reporter | ||||||
|     assert_equal_lists( |     assert_equal_lists( | ||||||
|         ReporterType2._meta.fields_map.keys(), |         ReporterType2._meta.fields_map.keys(), | ||||||
|         ['articles', 'first_name', 'last_name', 'email', 'pets', 'id'] |         ['articles', 'first_name', 'last_name', 'email', 'pets', 'id', 'films'] | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,12 +1,12 @@ | ||||||
| from graphql.core.type import GraphQLObjectType | from graphql.core.type import GraphQLObjectType | ||||||
| from mock import patch | from mock import patch | ||||||
| from tests.utils import assert_equal_lists |  | ||||||
| 
 | 
 | ||||||
| from graphene import Schema | from graphene import Schema | ||||||
| from graphene.contrib.django.types import DjangoNode, DjangoObjectType | from graphene.contrib.django.types import DjangoNode, DjangoObjectType | ||||||
| from graphene.core.fields import Field | from graphene.core.fields import Field | ||||||
| from graphene.core.types.scalars import Int | from graphene.core.types.scalars import Int | ||||||
| from graphene.relay.fields import GlobalIDField | from graphene.relay.fields import GlobalIDField | ||||||
|  | from tests.utils import assert_equal_lists | ||||||
| 
 | 
 | ||||||
| from .models import Article, Reporter | from .models import Article, Reporter | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,7 +9,8 @@ from .compat import RelatedObject | ||||||
| try: | try: | ||||||
|     import django_filters  # noqa |     import django_filters  # noqa | ||||||
|     DJANGO_FILTER_INSTALLED = True |     DJANGO_FILTER_INSTALLED = True | ||||||
| except ImportError: | except (ImportError, AttributeError): | ||||||
|  |     # AtributeError raised if DjangoFilters installed with a incompatible Django Version | ||||||
|     DJANGO_FILTER_INSTALLED = False |     DJANGO_FILTER_INSTALLED = False | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -35,6 +36,8 @@ def get_reverse_fields(model): | ||||||
|             yield new_related |             yield new_related | ||||||
|         elif isinstance(related, models.ManyToOneRel): |         elif isinstance(related, models.ManyToOneRel): | ||||||
|             yield related |             yield related | ||||||
|  |         elif isinstance(related, models.ManyToManyRel) and not related.symmetrical: | ||||||
|  |             yield related | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class WrappedQueryset(LazyList): | class WrappedQueryset(LazyList): | ||||||
|  |  | ||||||
|  | @ -1,10 +1,10 @@ | ||||||
| from graphql.core import graphql | from graphql.core import graphql | ||||||
| from py.test import raises | from py.test import raises | ||||||
| from tests.utils import assert_equal_lists |  | ||||||
| 
 | 
 | ||||||
| from graphene import Interface, List, ObjectType, Schema, String | from graphene import Interface, List, ObjectType, Schema, String | ||||||
| from graphene.core.fields import Field | from graphene.core.fields import Field | ||||||
| from graphene.core.types.base import LazyType | from graphene.core.types.base import LazyType | ||||||
|  | from tests.utils import assert_equal_lists | ||||||
| 
 | 
 | ||||||
| schema = Schema(name='My own schema') | schema = Schema(name='My own schema') | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -48,6 +48,7 @@ def test_to_arguments_wrong_type(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_snake_case_args(): | def test_snake_case_args(): | ||||||
|     resolver = lambda instance, args, info: args['my_arg']['inner_arg'] |     def resolver(instance, args, info): | ||||||
|  |         return args['my_arg']['inner_arg'] | ||||||
|     r = snake_case_args(resolver) |     r = snake_case_args(resolver) | ||||||
|     assert r(None, {'myArg': {'innerArg': 3}}, None) == 3 |     assert r(None, {'myArg': {'innerArg': 3}}, None) == 3 | ||||||
|  |  | ||||||
|  | @ -25,7 +25,9 @@ def test_orderedtype_different(): | ||||||
| 
 | 
 | ||||||
| @patch('graphene.core.types.field.Field') | @patch('graphene.core.types.field.Field') | ||||||
| def test_type_as_field_called(Field): | def test_type_as_field_called(Field): | ||||||
|     resolver = lambda x: x |     def resolver(x): | ||||||
|  |         return x | ||||||
|  | 
 | ||||||
|     a = MountedType(2, description='A', resolver=resolver) |     a = MountedType(2, description='A', resolver=resolver) | ||||||
|     a.as_field() |     a.as_field() | ||||||
|     Field.assert_called_with( |     Field.assert_called_with( | ||||||
|  | @ -45,7 +47,8 @@ def test_type_as_argument_called(Argument): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_type_as_field(): | def test_type_as_field(): | ||||||
|     resolver = lambda x: x |     def resolver(x): | ||||||
|  |         return x | ||||||
| 
 | 
 | ||||||
|     class MyObjectType(ObjectType): |     class MyObjectType(ObjectType): | ||||||
|         t = MountedType(description='A', resolver=resolver) |         t = MountedType(description='A', resolver=resolver) | ||||||
|  |  | ||||||
|  | @ -11,7 +11,8 @@ from ..scalars import String | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_field_internal_type(): | def test_field_internal_type(): | ||||||
|     resolver = lambda *args: 'RESOLVED' |     def resolver(*args): | ||||||
|  |         return 'RESOLVED' | ||||||
| 
 | 
 | ||||||
|     field = Field(String(), description='My argument', resolver=resolver) |     field = Field(String(), description='My argument', resolver=resolver) | ||||||
| 
 | 
 | ||||||
|  | @ -132,7 +133,8 @@ def test_inputfield_internal_type(): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_field_resolve_argument(): | def test_field_resolve_argument(): | ||||||
|     resolver = lambda instance, args, info: args.get('first_name') |     def resolver(instance, args, info): | ||||||
|  |         return args.get('first_name') | ||||||
| 
 | 
 | ||||||
|     field = Field(String(), first_name=String(), description='My argument', resolver=resolver) |     field = Field(String(), first_name=String(), description='My argument', resolver=resolver) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ def to_camel_case(snake_str): | ||||||
|     components = snake_str.split('_') |     components = snake_str.split('_') | ||||||
|     # We capitalize the first letter of each component except the first one |     # We capitalize the first letter of each component except the first one | ||||||
|     # with the 'title' method and join them together. |     # with the 'title' method and join them together. | ||||||
|     return components[0] + "".join(x.title() for x in components[1:]) |     return components[0] + "".join(x.title() if x else '_' for x in components[1:]) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # From this response in Stackoverflow | # From this response in Stackoverflow | ||||||
|  |  | ||||||
|  | @ -2,7 +2,11 @@ from ..resolve_only_args import resolve_only_args | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_resolve_only_args(): | def test_resolve_only_args(): | ||||||
|  | 
 | ||||||
|  |     def resolver(*args, **kwargs): | ||||||
|  |         return kwargs | ||||||
|  | 
 | ||||||
|     my_data = {'one': 1, 'two': 2} |     my_data = {'one': 1, 'two': 2} | ||||||
|     resolver = lambda *args, **kwargs: kwargs | 
 | ||||||
|     wrapped = resolve_only_args(resolver) |     wrapped = resolve_only_args(resolver) | ||||||
|     assert wrapped(None, my_data, None) == my_data |     assert wrapped(None, my_data, None) == my_data | ||||||
|  |  | ||||||
|  | @ -4,11 +4,14 @@ from ..str_converters import to_camel_case, to_snake_case | ||||||
| def test_snake_case(): | def test_snake_case(): | ||||||
|     assert to_snake_case('snakesOnAPlane') == 'snakes_on_a_plane' |     assert to_snake_case('snakesOnAPlane') == 'snakes_on_a_plane' | ||||||
|     assert to_snake_case('SnakesOnAPlane') == 'snakes_on_a_plane' |     assert to_snake_case('SnakesOnAPlane') == 'snakes_on_a_plane' | ||||||
|  |     assert to_snake_case('SnakesOnA_Plane') == 'snakes_on_a__plane' | ||||||
|     assert to_snake_case('snakes_on_a_plane') == 'snakes_on_a_plane' |     assert to_snake_case('snakes_on_a_plane') == 'snakes_on_a_plane' | ||||||
|  |     assert to_snake_case('snakes_on_a__plane') == 'snakes_on_a__plane' | ||||||
|     assert to_snake_case('IPhoneHysteria') == 'i_phone_hysteria' |     assert to_snake_case('IPhoneHysteria') == 'i_phone_hysteria' | ||||||
|     assert to_snake_case('iPhoneHysteria') == 'i_phone_hysteria' |     assert to_snake_case('iPhoneHysteria') == 'i_phone_hysteria' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_camel_case(): | def test_camel_case(): | ||||||
|     assert to_camel_case('snakes_on_a_plane') == 'snakesOnAPlane' |     assert to_camel_case('snakes_on_a_plane') == 'snakesOnAPlane' | ||||||
|  |     assert to_camel_case('snakes_on_a__plane') == 'snakesOnA_Plane' | ||||||
|     assert to_camel_case('i_phone_hysteria') == 'iPhoneHysteria' |     assert to_camel_case('i_phone_hysteria') == 'iPhoneHysteria' | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| SECRET_KEY = 1 | SECRET_KEY = 1 | ||||||
| 
 | 
 | ||||||
| INSTALLED_APPS = [ | INSTALLED_APPS = [ | ||||||
|  |     'graphene.contrib.django', | ||||||
|     'graphene.contrib.django.tests', |     'graphene.contrib.django.tests', | ||||||
|     'examples.starwars_django', |     'examples.starwars_django', | ||||||
| ] | ] | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user