mirror of
				https://github.com/graphql-python/graphene.git
				synced 2025-10-30 23:47:55 +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.ManyToOneRel) | ||||
| @convert_django_field.register(models.ManyToManyRel) | ||||
| def convert_field_to_list_or_connection(field): | ||||
|     from .fields import DjangoModelField, ConnectionOrListField | ||||
|     model_field = DjangoModelField(get_related_model(field)) | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ from ...core.types.base import FieldType | |||
| from ...core.types.definitions import List | ||||
| from ...relay import ConnectionField | ||||
| 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): | ||||
|  | @ -37,6 +37,7 @@ class DjangoConnectionField(ConnectionField): | |||
| class ConnectionOrListField(Field): | ||||
| 
 | ||||
|     def internal_type(self, schema): | ||||
|         if DJANGO_FILTER_INSTALLED: | ||||
|             from .filter.fields import DjangoFilterConnectionField | ||||
| 
 | ||||
|         model_field = self.type | ||||
|  |  | |||
|  | @ -1,13 +1,14 @@ | |||
| import warnings | ||||
| from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED | ||||
| 
 | ||||
| if not DJANGO_FILTER_INSTALLED: | ||||
|     raise Exception( | ||||
|     warnings.warn( | ||||
|         "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 .filterset import GrapheneFilterSet, GlobalIDFilter, GlobalIDMultipleChoiceFilter | ||||
| 
 | ||||
| from .fields import DjangoFilterConnectionField | ||||
| from .filterset import GrapheneFilterSet, GlobalIDFilter, GlobalIDMultipleChoiceFilter | ||||
| 
 | ||||
| __all__ = ['DjangoFilterConnectionField', 'GrapheneFilterSet', | ||||
|     __all__ = ['DjangoFilterConnectionField', 'GrapheneFilterSet', | ||||
|                'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter'] | ||||
|  |  | |||
|  | @ -2,10 +2,10 @@ import six | |||
| from django.conf import settings | ||||
| from django.db import models | ||||
| 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 django_filters import Filter, MultipleChoiceFilter | ||||
| from django_filters.filterset import FilterSet, FilterSetMetaclass | ||||
| from graphene.contrib.django.forms import (GlobalIDFormField, | ||||
|                                            GlobalIDMultipleChoiceField) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import django_filters | ||||
| 
 | ||||
| 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(): | ||||
|     field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleFilter) | ||||
|     assert_arguments(field, | ||||
|                      'headline', 'headlineIcontains', | ||||
|                      'pubDate', 'pubDateGt', 'pubDateLt', | ||||
|                      'headline', 'headline_Icontains', | ||||
|                      'pubDate', 'pubDate_Gt', 'pubDate_Lt', | ||||
|                      'reporter', | ||||
|                      ) | ||||
| 
 | ||||
|  | @ -89,7 +89,7 @@ def test_filter_shortcut_filterset_arguments_dict(): | |||
|         'reporter': ['exact'], | ||||
|     }) | ||||
|     assert_arguments(field, | ||||
|                      'headline', 'headlineIcontains', | ||||
|                      'headline', 'headline_Icontains', | ||||
|                      'reporter', | ||||
|                      ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,12 +1,33 @@ | |||
| import importlib | ||||
| 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 | ||||
| 
 | ||||
| LT_DJANGO_1_8 = StrictVersion(get_django_version()) < StrictVersion('1.8') | ||||
| 
 | ||||
| class Command(BaseCommand): | ||||
|     help = 'Dump Graphene schema JSON to file' | ||||
|     can_import_settings = True | ||||
| if LT_DJANGO_1_8: | ||||
|     class CommandArguments(BaseCommand): | ||||
|         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): | ||||
|             from django.conf import settings | ||||
|  | @ -24,15 +45,28 @@ class Command(BaseCommand): | |||
|                 default=getattr(settings, 'GRAPHENE_SCHEMA_OUTPUT', '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) | ||||
| 
 | ||||
|         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) | ||||
| 
 | ||||
| 
 | ||||
| class Film(models.Model): | ||||
|     reporters = models.ManyToManyField('Reporter', | ||||
|                                        related_name='films') | ||||
| 
 | ||||
| 
 | ||||
| class Reporter(models.Model): | ||||
|     first_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 tests.utils import assert_equal_lists | ||||
| 
 | ||||
| from graphene.contrib.django import DjangoObjectType | ||||
| from tests.utils import assert_equal_lists | ||||
| 
 | ||||
| from .models import Reporter | ||||
| 
 | ||||
|  | @ -29,7 +29,7 @@ def test_should_map_fields_correctly(): | |||
|             model = Reporter | ||||
|     assert_equal_lists( | ||||
|         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 mock import patch | ||||
| from tests.utils import assert_equal_lists | ||||
| 
 | ||||
| from graphene import Schema | ||||
| from graphene.contrib.django.types import DjangoNode, DjangoObjectType | ||||
| from graphene.core.fields import Field | ||||
| from graphene.core.types.scalars import Int | ||||
| from graphene.relay.fields import GlobalIDField | ||||
| from tests.utils import assert_equal_lists | ||||
| 
 | ||||
| from .models import Article, Reporter | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,7 +9,8 @@ from .compat import RelatedObject | |||
| try: | ||||
|     import django_filters  # noqa | ||||
|     DJANGO_FILTER_INSTALLED = True | ||||
| except ImportError: | ||||
| except (ImportError, AttributeError): | ||||
|     # AtributeError raised if DjangoFilters installed with a incompatible Django Version | ||||
|     DJANGO_FILTER_INSTALLED = False | ||||
| 
 | ||||
| 
 | ||||
|  | @ -35,6 +36,8 @@ def get_reverse_fields(model): | |||
|             yield new_related | ||||
|         elif isinstance(related, models.ManyToOneRel): | ||||
|             yield related | ||||
|         elif isinstance(related, models.ManyToManyRel) and not related.symmetrical: | ||||
|             yield related | ||||
| 
 | ||||
| 
 | ||||
| class WrappedQueryset(LazyList): | ||||
|  |  | |||
|  | @ -1,10 +1,10 @@ | |||
| from graphql.core import graphql | ||||
| from py.test import raises | ||||
| from tests.utils import assert_equal_lists | ||||
| 
 | ||||
| from graphene import Interface, List, ObjectType, Schema, String | ||||
| from graphene.core.fields import Field | ||||
| from graphene.core.types.base import LazyType | ||||
| from tests.utils import assert_equal_lists | ||||
| 
 | ||||
| schema = Schema(name='My own schema') | ||||
| 
 | ||||
|  |  | |||
|  | @ -48,6 +48,7 @@ def test_to_arguments_wrong_type(): | |||
| 
 | ||||
| 
 | ||||
| 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) | ||||
|     assert r(None, {'myArg': {'innerArg': 3}}, None) == 3 | ||||
|  |  | |||
|  | @ -25,7 +25,9 @@ def test_orderedtype_different(): | |||
| 
 | ||||
| @patch('graphene.core.types.field.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.as_field() | ||||
|     Field.assert_called_with( | ||||
|  | @ -45,7 +47,8 @@ def test_type_as_argument_called(Argument): | |||
| 
 | ||||
| 
 | ||||
| def test_type_as_field(): | ||||
|     resolver = lambda x: x | ||||
|     def resolver(x): | ||||
|         return x | ||||
| 
 | ||||
|     class MyObjectType(ObjectType): | ||||
|         t = MountedType(description='A', resolver=resolver) | ||||
|  |  | |||
|  | @ -11,7 +11,8 @@ from ..scalars import String | |||
| 
 | ||||
| 
 | ||||
| def test_field_internal_type(): | ||||
|     resolver = lambda *args: 'RESOLVED' | ||||
|     def resolver(*args): | ||||
|         return 'RESOLVED' | ||||
| 
 | ||||
|     field = Field(String(), description='My argument', resolver=resolver) | ||||
| 
 | ||||
|  | @ -132,7 +133,8 @@ def test_inputfield_internal_type(): | |||
| 
 | ||||
| 
 | ||||
| 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) | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ def to_camel_case(snake_str): | |||
|     components = snake_str.split('_') | ||||
|     # We capitalize the first letter of each component except the first one | ||||
|     # 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 | ||||
|  |  | |||
|  | @ -2,7 +2,11 @@ from ..resolve_only_args import resolve_only_args | |||
| 
 | ||||
| 
 | ||||
| def test_resolve_only_args(): | ||||
| 
 | ||||
|     def resolver(*args, **kwargs): | ||||
|         return kwargs | ||||
| 
 | ||||
|     my_data = {'one': 1, 'two': 2} | ||||
|     resolver = lambda *args, **kwargs: kwargs | ||||
| 
 | ||||
|     wrapped = resolve_only_args(resolver) | ||||
|     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(): | ||||
|     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('IPhoneHysteria') == 'i_phone_hysteria' | ||||
|     assert to_snake_case('iPhoneHysteria') == 'i_phone_hysteria' | ||||
| 
 | ||||
| 
 | ||||
| def test_camel_case(): | ||||
|     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' | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| SECRET_KEY = 1 | ||||
| 
 | ||||
| INSTALLED_APPS = [ | ||||
|     'graphene.contrib.django', | ||||
|     'graphene.contrib.django.tests', | ||||
|     'examples.starwars_django', | ||||
| ] | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user