mirror of
				https://github.com/graphql-python/graphene.git
				synced 2025-10-30 23:47:55 +03:00 
			
		
		
		
	First 1.0 with a separated Django version
This commit is contained in:
		
							parent
							
								
									80f98c5fd3
								
							
						
					
					
						commit
						feaa09616d
					
				
							
								
								
									
										14
									
								
								graphene-django/django_test_settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								graphene-django/django_test_settings.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| SECRET_KEY = 1 | ||||
| 
 | ||||
| INSTALLED_APPS = [ | ||||
|     'graphene_django', | ||||
|     'graphene_django.tests', | ||||
|     'examples.starwars', | ||||
| ] | ||||
| 
 | ||||
| DATABASES = { | ||||
|     'default': { | ||||
|         'ENGINE': 'django.db.backends.sqlite3', | ||||
|         'NAME': 'django_test.sqlite', | ||||
|     } | ||||
| } | ||||
							
								
								
									
										10
									
								
								graphene-django/graphene_django/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								graphene-django/graphene_django/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| from .types import ( | ||||
|     DjangoObjectType, | ||||
|     DjangoNode | ||||
| ) | ||||
| from .fields import ( | ||||
|     DjangoConnectionField, | ||||
| ) | ||||
| 
 | ||||
| __all__ = ['DjangoObjectType', 'DjangoNode', | ||||
|            'DjangoConnectionField'] | ||||
							
								
								
									
										24
									
								
								graphene-django/graphene_django/compat.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								graphene-django/graphene_django/compat.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | |||
| from django.db import models | ||||
| 
 | ||||
| 
 | ||||
| class MissingType(object): | ||||
|     pass | ||||
| 
 | ||||
| try: | ||||
|     UUIDField = models.UUIDField | ||||
| except AttributeError: | ||||
|     # Improved compatibility for Django 1.6 | ||||
|     UUIDField = MissingType | ||||
| 
 | ||||
| try: | ||||
|     from django.db.models.related import RelatedObject | ||||
| except: | ||||
|     # Improved compatibility for Django 1.6 | ||||
|     RelatedObject = MissingType | ||||
| 
 | ||||
| 
 | ||||
| try: | ||||
|     # Postgres fields are only available in Django 1.8+ | ||||
|     from django.contrib.postgres.fields import ArrayField, HStoreField, JSONField, RangeField | ||||
| except ImportError: | ||||
|     ArrayField, HStoreField, JSONField, RangeField = (MissingType, ) * 4 | ||||
							
								
								
									
										158
									
								
								graphene-django/graphene_django/converter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								graphene-django/graphene_django/converter.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,158 @@ | |||
| from django.db import models | ||||
| from django.utils.encoding import force_text | ||||
| 
 | ||||
| from graphene import Enum, List, ID, Boolean, Float, Int, String, Field | ||||
| from graphene.utils.str_converters import to_const | ||||
| from graphene.relay import Node, ConnectionField | ||||
| # from ...core.types.custom_scalars import DateTime, JSONString | ||||
| from .compat import (ArrayField, HStoreField, JSONField, RangeField, | ||||
|                      RelatedObject, UUIDField) | ||||
| from .utils import get_related_model, import_single_dispatch | ||||
| from .fields import DjangoConnectionField | ||||
| 
 | ||||
| singledispatch = import_single_dispatch() | ||||
| 
 | ||||
| 
 | ||||
| class Registry(object): | ||||
|     def __init__(self): | ||||
|         self._registry = {} | ||||
|         self._registry_models = {} | ||||
| 
 | ||||
|     def register(self, cls): | ||||
|         from .types import DjangoObjectType | ||||
|         print(cls.get_registry(), self) | ||||
|         assert issubclass(cls, DjangoObjectType), 'Only DjangoObjectTypes can be registered, received "{}"'.format(cls.__name__) | ||||
|         assert cls.get_registry() == self, 'Registry for a Model have to match.' | ||||
|         self._registry[cls._meta.model] = cls | ||||
| 
 | ||||
|     def get_type_for_model(self, model): | ||||
|         return self._registry.get(model) | ||||
| 
 | ||||
| 
 | ||||
| def convert_choices(choices): | ||||
|     for value, name in choices: | ||||
|         if isinstance(name, (tuple, list)): | ||||
|             for choice in convert_choices(name): | ||||
|                 yield choice | ||||
|         else: | ||||
|             yield to_const(force_text(name)), value | ||||
| 
 | ||||
| 
 | ||||
| def convert_django_field_with_choices(field, registry=None): | ||||
|     choices = getattr(field, 'choices', None) | ||||
|     if choices: | ||||
|         meta = field.model._meta | ||||
|         name = '{}_{}_{}'.format(meta.app_label, meta.object_name, field.name) | ||||
|         graphql_choices = list(convert_choices(choices)) | ||||
|         return Enum(name.upper(), graphql_choices, description=field.help_text) | ||||
|     return convert_django_field(field, registry) | ||||
| 
 | ||||
| 
 | ||||
| @singledispatch | ||||
| def convert_django_field(field, registry=None): | ||||
|     raise Exception( | ||||
|         "Don't know how to convert the Django field %s (%s)" % | ||||
|         (field, field.__class__)) | ||||
| 
 | ||||
| 
 | ||||
| @convert_django_field.register(models.CharField) | ||||
| @convert_django_field.register(models.TextField) | ||||
| @convert_django_field.register(models.EmailField) | ||||
| @convert_django_field.register(models.SlugField) | ||||
| @convert_django_field.register(models.URLField) | ||||
| @convert_django_field.register(models.GenericIPAddressField) | ||||
| @convert_django_field.register(models.FileField) | ||||
| @convert_django_field.register(UUIDField) | ||||
| def convert_field_to_string(field, registry=None): | ||||
|     return String(description=field.help_text) | ||||
| 
 | ||||
| 
 | ||||
| @convert_django_field.register(models.AutoField) | ||||
| def convert_field_to_id(field, registry=None): | ||||
|     return ID(description=field.help_text) | ||||
| 
 | ||||
| 
 | ||||
| @convert_django_field.register(models.PositiveIntegerField) | ||||
| @convert_django_field.register(models.PositiveSmallIntegerField) | ||||
| @convert_django_field.register(models.SmallIntegerField) | ||||
| @convert_django_field.register(models.BigIntegerField) | ||||
| @convert_django_field.register(models.IntegerField) | ||||
| def convert_field_to_int(field, registry=None): | ||||
|     return Int(description=field.help_text) | ||||
| 
 | ||||
| 
 | ||||
| @convert_django_field.register(models.BooleanField) | ||||
| def convert_field_to_boolean(field, registry=None): | ||||
|     return Boolean(description=field.help_text, required=True) | ||||
| 
 | ||||
| 
 | ||||
| @convert_django_field.register(models.NullBooleanField) | ||||
| def convert_field_to_nullboolean(field, registry=None): | ||||
|     return Boolean(description=field.help_text) | ||||
| 
 | ||||
| 
 | ||||
| @convert_django_field.register(models.DecimalField) | ||||
| @convert_django_field.register(models.FloatField) | ||||
| def convert_field_to_float(field, registry=None): | ||||
|     return Float(description=field.help_text) | ||||
| 
 | ||||
| 
 | ||||
| @convert_django_field.register(models.DateField) | ||||
| def convert_date_to_string(field, registry=None): | ||||
|     return DateTime(description=field.help_text) | ||||
| 
 | ||||
| 
 | ||||
| @convert_django_field.register(models.OneToOneRel) | ||||
| def convert_onetoone_field_to_djangomodel(field, registry=None): | ||||
|     model = get_related_model(field) | ||||
|     return Field(registry.get_type_for_model(model)) | ||||
| 
 | ||||
| 
 | ||||
| @convert_django_field.register(models.ManyToManyField) | ||||
| @convert_django_field.register(models.ManyToManyRel) | ||||
| @convert_django_field.register(models.ManyToOneRel) | ||||
| def convert_field_to_list_or_connection(field, registry=None): | ||||
|     model = get_related_model(field) | ||||
|     _type = registry.get_type_for_model(model) | ||||
|     if not _type: | ||||
|         return | ||||
| 
 | ||||
|     if issubclass(_type, Node): | ||||
|         return DjangoConnectionField(_type) | ||||
|     return Field(List(_type)) | ||||
| 
 | ||||
| 
 | ||||
| # For Django 1.6 | ||||
| @convert_django_field.register(RelatedObject) | ||||
| def convert_relatedfield_to_djangomodel(field, registry=None): | ||||
|     model = field.model | ||||
|     _type = registry.get_type_for_model(model) | ||||
|     if issubclass(_type, Node): | ||||
|         return DjangoConnectionField(_type) | ||||
|     return Field(List(_type)) | ||||
| 
 | ||||
| 
 | ||||
| @convert_django_field.register(models.OneToOneField) | ||||
| @convert_django_field.register(models.ForeignKey) | ||||
| def convert_field_to_djangomodel(field, registry=None): | ||||
|     model = get_related_model(field) | ||||
|     _type = registry.get_type_for_model(model) | ||||
|     return Field(_type, description=field.help_text) | ||||
| 
 | ||||
| 
 | ||||
| @convert_django_field.register(ArrayField) | ||||
| def convert_postgres_array_to_list(field, registry=None): | ||||
|     base_type = convert_django_field(field.base_field) | ||||
|     return List(base_type, description=field.help_text) | ||||
| 
 | ||||
| 
 | ||||
| @convert_django_field.register(HStoreField) | ||||
| @convert_django_field.register(JSONField) | ||||
| def convert_posgres_field_to_string(field, registry=None): | ||||
|     return JSONString(description=field.help_text) | ||||
| 
 | ||||
| 
 | ||||
| @convert_django_field.register(RangeField) | ||||
| def convert_posgres_range_to_string(field, registry=None): | ||||
|     inner_type = convert_django_field(field.base_field) | ||||
|     return List(inner_type, description=field.help_text) | ||||
							
								
								
									
										4
									
								
								graphene-django/graphene_django/debug/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								graphene-django/graphene_django/debug/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| from .middleware import DjangoDebugMiddleware | ||||
| from .types import DjangoDebug | ||||
| 
 | ||||
| __all__ = ['DjangoDebugMiddleware', 'DjangoDebug'] | ||||
							
								
								
									
										56
									
								
								graphene-django/graphene_django/debug/middleware.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								graphene-django/graphene_django/debug/middleware.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| from promise import Promise | ||||
| from django.db import connections | ||||
| 
 | ||||
| from .sql.tracking import unwrap_cursor, wrap_cursor | ||||
| from .types import DjangoDebug | ||||
| 
 | ||||
| 
 | ||||
| class DjangoDebugContext(object): | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.debug_promise = None | ||||
|         self.promises = [] | ||||
|         self.enable_instrumentation() | ||||
|         self.object = DjangoDebug(sql=[]) | ||||
| 
 | ||||
|     def get_debug_promise(self): | ||||
|         if not self.debug_promise: | ||||
|             self.debug_promise = Promise.all(self.promises) | ||||
|         return self.debug_promise.then(self.on_resolve_all_promises) | ||||
| 
 | ||||
|     def on_resolve_all_promises(self, values): | ||||
|         self.disable_instrumentation() | ||||
|         return self.object | ||||
| 
 | ||||
|     def add_promise(self, promise): | ||||
|         if self.debug_promise and not self.debug_promise.is_fulfilled: | ||||
|             self.promises.append(promise) | ||||
| 
 | ||||
|     def enable_instrumentation(self): | ||||
|         # This is thread-safe because database connections are thread-local. | ||||
|         for connection in connections.all(): | ||||
|             wrap_cursor(connection, self) | ||||
| 
 | ||||
|     def disable_instrumentation(self): | ||||
|         for connection in connections.all(): | ||||
|             unwrap_cursor(connection) | ||||
| 
 | ||||
| 
 | ||||
| class DjangoDebugMiddleware(object): | ||||
| 
 | ||||
|     def resolve(self, next, root, args, context, info): | ||||
|         django_debug = getattr(context, 'django_debug', None) | ||||
|         if not django_debug: | ||||
|             if context is None: | ||||
|                 raise Exception('DjangoDebug cannot be executed in None contexts') | ||||
|             try: | ||||
|                 context.django_debug = DjangoDebugContext() | ||||
|             except Exception: | ||||
|                 raise Exception('DjangoDebug need the context to be writable, context received: {}.'.format( | ||||
|                     context.__class__.__name__ | ||||
|                 )) | ||||
|         if info.schema.graphene_schema.T(DjangoDebug) == info.return_type: | ||||
|             return context.django_debug.get_debug_promise() | ||||
|         promise = next(root, args, context, info) | ||||
|         context.django_debug.add_promise(promise) | ||||
|         return promise | ||||
							
								
								
									
										170
									
								
								graphene-django/graphene_django/debug/sql/tracking.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								graphene-django/graphene_django/debug/sql/tracking.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,170 @@ | |||
| # Code obtained from django-debug-toolbar sql panel tracking | ||||
| from __future__ import absolute_import, unicode_literals | ||||
| 
 | ||||
| import json | ||||
| from threading import local | ||||
| from time import time | ||||
| 
 | ||||
| from django.utils import six | ||||
| from django.utils.encoding import force_text | ||||
| 
 | ||||
| from .types import DjangoDebugSQL, DjangoDebugPostgreSQL | ||||
| 
 | ||||
| 
 | ||||
| class SQLQueryTriggered(Exception): | ||||
|     """Thrown when template panel triggers a query""" | ||||
| 
 | ||||
| 
 | ||||
| class ThreadLocalState(local): | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.enabled = True | ||||
| 
 | ||||
|     @property | ||||
|     def Wrapper(self): | ||||
|         if self.enabled: | ||||
|             return NormalCursorWrapper | ||||
|         return ExceptionCursorWrapper | ||||
| 
 | ||||
|     def recording(self, v): | ||||
|         self.enabled = v | ||||
| 
 | ||||
| 
 | ||||
| state = ThreadLocalState() | ||||
| recording = state.recording  # export function | ||||
| 
 | ||||
| 
 | ||||
| def wrap_cursor(connection, panel): | ||||
|     if not hasattr(connection, '_graphene_cursor'): | ||||
|         connection._graphene_cursor = connection.cursor | ||||
| 
 | ||||
|         def cursor(): | ||||
|             return state.Wrapper(connection._graphene_cursor(), connection, panel) | ||||
| 
 | ||||
|         connection.cursor = cursor | ||||
|         return cursor | ||||
| 
 | ||||
| 
 | ||||
| def unwrap_cursor(connection): | ||||
|     if hasattr(connection, '_graphene_cursor'): | ||||
|         previous_cursor = connection._graphene_cursor | ||||
|         connection.cursor = previous_cursor | ||||
|         del connection._graphene_cursor | ||||
| 
 | ||||
| 
 | ||||
| class ExceptionCursorWrapper(object): | ||||
|     """ | ||||
|     Wraps a cursor and raises an exception on any operation. | ||||
|     Used in Templates panel. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, cursor, db, logger): | ||||
|         pass | ||||
| 
 | ||||
|     def __getattr__(self, attr): | ||||
|         raise SQLQueryTriggered() | ||||
| 
 | ||||
| 
 | ||||
| class NormalCursorWrapper(object): | ||||
|     """ | ||||
|     Wraps a cursor and logs queries. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, cursor, db, logger): | ||||
|         self.cursor = cursor | ||||
|         # Instance of a BaseDatabaseWrapper subclass | ||||
|         self.db = db | ||||
|         # logger must implement a ``record`` method | ||||
|         self.logger = logger | ||||
| 
 | ||||
|     def _quote_expr(self, element): | ||||
|         if isinstance(element, six.string_types): | ||||
|             return "'%s'" % force_text(element).replace("'", "''") | ||||
|         else: | ||||
|             return repr(element) | ||||
| 
 | ||||
|     def _quote_params(self, params): | ||||
|         if not params: | ||||
|             return params | ||||
|         if isinstance(params, dict): | ||||
|             return dict((key, self._quote_expr(value)) | ||||
|                         for key, value in params.items()) | ||||
|         return list(map(self._quote_expr, params)) | ||||
| 
 | ||||
|     def _decode(self, param): | ||||
|         try: | ||||
|             return force_text(param, strings_only=True) | ||||
|         except UnicodeDecodeError: | ||||
|             return '(encoded string)' | ||||
| 
 | ||||
|     def _record(self, method, sql, params): | ||||
|         start_time = time() | ||||
|         try: | ||||
|             return method(sql, params) | ||||
|         finally: | ||||
|             stop_time = time() | ||||
|             duration = (stop_time - start_time) | ||||
|             _params = '' | ||||
|             try: | ||||
|                 _params = json.dumps(list(map(self._decode, params))) | ||||
|             except Exception: | ||||
|                 pass  # object not JSON serializable | ||||
| 
 | ||||
|             alias = getattr(self.db, 'alias', 'default') | ||||
|             conn = self.db.connection | ||||
|             vendor = getattr(conn, 'vendor', 'unknown') | ||||
| 
 | ||||
|             params = { | ||||
|                 'vendor': vendor, | ||||
|                 'alias': alias, | ||||
|                 'sql': self.db.ops.last_executed_query( | ||||
|                     self.cursor, sql, self._quote_params(params)), | ||||
|                 'duration': duration, | ||||
|                 'raw_sql': sql, | ||||
|                 'params': _params, | ||||
|                 'start_time': start_time, | ||||
|                 'stop_time': stop_time, | ||||
|                 'is_slow': duration > 10, | ||||
|                 'is_select': sql.lower().strip().startswith('select'), | ||||
|             } | ||||
| 
 | ||||
|             if vendor == 'postgresql': | ||||
|                 # If an erroneous query was ran on the connection, it might | ||||
|                 # be in a state where checking isolation_level raises an | ||||
|                 # exception. | ||||
|                 try: | ||||
|                     iso_level = conn.isolation_level | ||||
|                 except conn.InternalError: | ||||
|                     iso_level = 'unknown' | ||||
|                 params.update({ | ||||
|                     'trans_id': self.logger.get_transaction_id(alias), | ||||
|                     'trans_status': conn.get_transaction_status(), | ||||
|                     'iso_level': iso_level, | ||||
|                     'encoding': conn.encoding, | ||||
|                 }) | ||||
|                 _sql = DjangoDebugPostgreSQL(**params) | ||||
|             else: | ||||
|                 _sql = DjangoDebugSQL(**params) | ||||
|             # We keep `sql` to maintain backwards compatibility | ||||
|             self.logger.object.sql.append(_sql) | ||||
| 
 | ||||
|     def callproc(self, procname, params=()): | ||||
|         return self._record(self.cursor.callproc, procname, params) | ||||
| 
 | ||||
|     def execute(self, sql, params=()): | ||||
|         return self._record(self.cursor.execute, sql, params) | ||||
| 
 | ||||
|     def executemany(self, sql, param_list): | ||||
|         return self._record(self.cursor.executemany, sql, param_list) | ||||
| 
 | ||||
|     def __getattr__(self, attr): | ||||
|         return getattr(self.cursor, attr) | ||||
| 
 | ||||
|     def __iter__(self): | ||||
|         return iter(self.cursor) | ||||
| 
 | ||||
|     def __enter__(self): | ||||
|         return self | ||||
| 
 | ||||
|     def __exit__(self, type, value, traceback): | ||||
|         self.close() | ||||
							
								
								
									
										25
									
								
								graphene-django/graphene_django/debug/sql/types.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								graphene-django/graphene_django/debug/sql/types.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| from .....core import Boolean, Float, ObjectType, String | ||||
| 
 | ||||
| 
 | ||||
| class DjangoDebugBaseSQL(ObjectType): | ||||
|     vendor = String() | ||||
|     alias = String() | ||||
|     sql = String() | ||||
|     duration = Float() | ||||
|     raw_sql = String() | ||||
|     params = String() | ||||
|     start_time = Float() | ||||
|     stop_time = Float() | ||||
|     is_slow = Boolean() | ||||
|     is_select = Boolean() | ||||
| 
 | ||||
| 
 | ||||
| class DjangoDebugSQL(DjangoDebugBaseSQL): | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class DjangoDebugPostgreSQL(DjangoDebugBaseSQL): | ||||
|     trans_id = String() | ||||
|     trans_status = String() | ||||
|     iso_level = String() | ||||
|     encoding = String() | ||||
							
								
								
									
										219
									
								
								graphene-django/graphene_django/debug/tests/test_query.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								graphene-django/graphene_django/debug/tests/test_query.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,219 @@ | |||
| import pytest | ||||
| 
 | ||||
| import graphene | ||||
| from graphene.contrib.django import DjangoConnectionField, DjangoNode | ||||
| from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED | ||||
| 
 | ||||
| from ...tests.models import Reporter | ||||
| from ..middleware import DjangoDebugMiddleware | ||||
| from ..types import DjangoDebug | ||||
| 
 | ||||
| 
 | ||||
| class context(object): | ||||
|     pass | ||||
| 
 | ||||
| # from examples.starwars_django.models import Character | ||||
| 
 | ||||
| pytestmark = pytest.mark.django_db | ||||
| 
 | ||||
| 
 | ||||
| def test_should_query_field(): | ||||
|     r1 = Reporter(last_name='ABA') | ||||
|     r1.save() | ||||
|     r2 = Reporter(last_name='Griffin') | ||||
|     r2.save() | ||||
| 
 | ||||
|     class ReporterType(DjangoNode): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
| 
 | ||||
|     class Query(graphene.ObjectType): | ||||
|         reporter = graphene.Field(ReporterType) | ||||
|         debug = graphene.Field(DjangoDebug, name='__debug') | ||||
| 
 | ||||
|         def resolve_reporter(self, *args, **kwargs): | ||||
|             return Reporter.objects.first() | ||||
| 
 | ||||
|     query = ''' | ||||
|         query ReporterQuery { | ||||
|           reporter { | ||||
|             lastName | ||||
|           } | ||||
|           __debug { | ||||
|             sql { | ||||
|               rawSql | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|     ''' | ||||
|     expected = { | ||||
|         'reporter': { | ||||
|             'lastName': 'ABA', | ||||
|         }, | ||||
|         '__debug': { | ||||
|             'sql': [{ | ||||
|                 'rawSql': str(Reporter.objects.order_by('pk')[:1].query) | ||||
|             }] | ||||
|         } | ||||
|     } | ||||
|     schema = graphene.Schema(query=Query, middlewares=[DjangoDebugMiddleware()]) | ||||
|     result = schema.execute(query, context_value=context()) | ||||
|     assert not result.errors | ||||
|     assert result.data == expected | ||||
| 
 | ||||
| 
 | ||||
| def test_should_query_list(): | ||||
|     r1 = Reporter(last_name='ABA') | ||||
|     r1.save() | ||||
|     r2 = Reporter(last_name='Griffin') | ||||
|     r2.save() | ||||
| 
 | ||||
|     class ReporterType(DjangoNode): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
| 
 | ||||
|     class Query(graphene.ObjectType): | ||||
|         all_reporters = ReporterType.List() | ||||
|         debug = graphene.Field(DjangoDebug, name='__debug') | ||||
| 
 | ||||
|         def resolve_all_reporters(self, *args, **kwargs): | ||||
|             return Reporter.objects.all() | ||||
| 
 | ||||
|     query = ''' | ||||
|         query ReporterQuery { | ||||
|           allReporters { | ||||
|             lastName | ||||
|           } | ||||
|           __debug { | ||||
|             sql { | ||||
|               rawSql | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|     ''' | ||||
|     expected = { | ||||
|         'allReporters': [{ | ||||
|             'lastName': 'ABA', | ||||
|         }, { | ||||
|             'lastName': 'Griffin', | ||||
|         }], | ||||
|         '__debug': { | ||||
|             'sql': [{ | ||||
|                 'rawSql': str(Reporter.objects.all().query) | ||||
|             }] | ||||
|         } | ||||
|     } | ||||
|     schema = graphene.Schema(query=Query, middlewares=[DjangoDebugMiddleware()]) | ||||
|     result = schema.execute(query, context_value=context()) | ||||
|     assert not result.errors | ||||
|     assert result.data == expected | ||||
| 
 | ||||
| 
 | ||||
| def test_should_query_connection(): | ||||
|     r1 = Reporter(last_name='ABA') | ||||
|     r1.save() | ||||
|     r2 = Reporter(last_name='Griffin') | ||||
|     r2.save() | ||||
| 
 | ||||
|     class ReporterType(DjangoNode): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
| 
 | ||||
|     class Query(graphene.ObjectType): | ||||
|         all_reporters = DjangoConnectionField(ReporterType) | ||||
|         debug = graphene.Field(DjangoDebug, name='__debug') | ||||
| 
 | ||||
|         def resolve_all_reporters(self, *args, **kwargs): | ||||
|             return Reporter.objects.all() | ||||
| 
 | ||||
|     query = ''' | ||||
|         query ReporterQuery { | ||||
|           allReporters(first:1) { | ||||
|             edges { | ||||
|               node { | ||||
|                 lastName | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|           __debug { | ||||
|             sql { | ||||
|               rawSql | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|     ''' | ||||
|     expected = { | ||||
|         'allReporters': { | ||||
|             'edges': [{ | ||||
|                 'node': { | ||||
|                     'lastName': 'ABA', | ||||
|                 } | ||||
|             }] | ||||
|         }, | ||||
|     } | ||||
|     schema = graphene.Schema(query=Query, middlewares=[DjangoDebugMiddleware()]) | ||||
|     result = schema.execute(query, context_value=context()) | ||||
|     assert not result.errors | ||||
|     assert result.data['allReporters'] == expected['allReporters'] | ||||
|     assert 'COUNT' in result.data['__debug']['sql'][0]['rawSql'] | ||||
|     query = str(Reporter.objects.all()[:1].query) | ||||
|     assert result.data['__debug']['sql'][1]['rawSql'] == query | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.skipif(not DJANGO_FILTER_INSTALLED, | ||||
|                     reason="requires django-filter") | ||||
| def test_should_query_connectionfilter(): | ||||
|     from graphene.contrib.django.filter import DjangoFilterConnectionField | ||||
| 
 | ||||
|     r1 = Reporter(last_name='ABA') | ||||
|     r1.save() | ||||
|     r2 = Reporter(last_name='Griffin') | ||||
|     r2.save() | ||||
| 
 | ||||
|     class ReporterType(DjangoNode): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
| 
 | ||||
|     class Query(graphene.ObjectType): | ||||
|         all_reporters = DjangoFilterConnectionField(ReporterType) | ||||
|         debug = graphene.Field(DjangoDebug, name='__debug') | ||||
| 
 | ||||
|         def resolve_all_reporters(self, *args, **kwargs): | ||||
|             return Reporter.objects.all() | ||||
| 
 | ||||
|     query = ''' | ||||
|         query ReporterQuery { | ||||
|           allReporters(first:1) { | ||||
|             edges { | ||||
|               node { | ||||
|                 lastName | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|           __debug { | ||||
|             sql { | ||||
|               rawSql | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|     ''' | ||||
|     expected = { | ||||
|         'allReporters': { | ||||
|             'edges': [{ | ||||
|                 'node': { | ||||
|                     'lastName': 'ABA', | ||||
|                 } | ||||
|             }] | ||||
|         }, | ||||
|     } | ||||
|     schema = graphene.Schema(query=Query, middlewares=[DjangoDebugMiddleware()]) | ||||
|     result = schema.execute(query, context_value=context()) | ||||
|     assert not result.errors | ||||
|     assert result.data['allReporters'] == expected['allReporters'] | ||||
|     assert 'COUNT' in result.data['__debug']['sql'][0]['rawSql'] | ||||
|     query = str(Reporter.objects.all()[:1].query) | ||||
|     assert result.data['__debug']['sql'][1]['rawSql'] == query | ||||
							
								
								
									
										7
									
								
								graphene-django/graphene_django/debug/types.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								graphene-django/graphene_django/debug/types.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| from ....core.classtypes.objecttype import ObjectType | ||||
| from ....core.types import Field | ||||
| from .sql.types import DjangoDebugBaseSQL | ||||
| 
 | ||||
| 
 | ||||
| class DjangoDebug(ObjectType): | ||||
|     sql = Field(DjangoDebugBaseSQL.List()) | ||||
							
								
								
									
										64
									
								
								graphene-django/graphene_django/fields.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								graphene-django/graphene_django/fields.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,64 @@ | |||
| # from ...core.exceptions import SkipField | ||||
| from graphene import Field, List | ||||
| from graphene.relay import ConnectionField | ||||
| from .utils import DJANGO_FILTER_INSTALLED, get_type_for_model, maybe_queryset | ||||
| 
 | ||||
| 
 | ||||
| class DjangoConnectionField(ConnectionField): | ||||
| 
 | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         self.on = kwargs.pop('on', False) | ||||
|         # kwargs['default'] = kwargs.pop('default', self.get_manager) | ||||
|         return super(DjangoConnectionField, self).__init__(*args, **kwargs) | ||||
| 
 | ||||
|     @property | ||||
|     def model(self): | ||||
|         return self.type._meta.model | ||||
| 
 | ||||
|     def get_manager(self): | ||||
|         if self.on: | ||||
|             return getattr(self.model, self.on) | ||||
|         else: | ||||
|             return self.model._default_manager | ||||
| 
 | ||||
|     def get_queryset(self, resolved_qs, args, info): | ||||
|         return resolved_qs | ||||
| 
 | ||||
|     def from_list(self, connection_type, resolved, args, context, info): | ||||
|         resolved_qs = maybe_queryset(resolved) | ||||
|         qs = self.get_queryset(resolved_qs, args, info) | ||||
|         return super(DjangoConnectionField, self).from_list(connection_type, qs, args, context, info) | ||||
| 
 | ||||
| 
 | ||||
| def get_list_or_connection_type_for_model(model): | ||||
|     pass | ||||
|     # field_object_type = model_field.get_object_type(schema) | ||||
|     # if not field_object_type: | ||||
|     #     raise SkipField() | ||||
|     # if isinstance(: | ||||
|     #     if field_object_type._meta.filter_fields: | ||||
|     #         field = DjangoFilterConnectionField(field_object_type) | ||||
|     #     else: | ||||
|     #         field = DjangoConnectionField(field_object_type) | ||||
|     # else: | ||||
|     #     field = List(field_object_type) | ||||
|     # field.contribute_to_class(self.object_type, self.attname) | ||||
|     # return schema.T(field) | ||||
| 
 | ||||
| 
 | ||||
| def get_graphene_type_from_model(model): | ||||
|     pass | ||||
|     # _type = self.get_object_type(schema) | ||||
|     # if not _type and self.parent._meta.only_fields: | ||||
|     #     raise Exception( | ||||
|     #         "Model %r is not accessible by the schema. " | ||||
|     #         "You can either register the type manually " | ||||
|     #         "using @schema.register. " | ||||
|     #         "Or disable the field in %s" % ( | ||||
|     #             self.model, | ||||
|     #             self.parent, | ||||
|     #         ) | ||||
|     #     ) | ||||
|     # if not _type: | ||||
|     #     raise SkipField() | ||||
|     # return schema.T(_type) | ||||
							
								
								
									
										14
									
								
								graphene-django/graphene_django/filter/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								graphene-django/graphene_django/filter/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| import warnings | ||||
| from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED | ||||
| 
 | ||||
| if not DJANGO_FILTER_INSTALLED: | ||||
|     warnings.warn( | ||||
|         "Use of django filtering requires the django-filter package " | ||||
|         "be installed. You can do so using `pip install django-filter`", ImportWarning | ||||
|     ) | ||||
| else: | ||||
|     from .fields import DjangoFilterConnectionField | ||||
|     from .filterset import GrapheneFilterSet, GlobalIDFilter, GlobalIDMultipleChoiceFilter | ||||
| 
 | ||||
|     __all__ = ['DjangoFilterConnectionField', 'GrapheneFilterSet', | ||||
|                'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter'] | ||||
							
								
								
									
										36
									
								
								graphene-django/graphene_django/filter/fields.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								graphene-django/graphene_django/filter/fields.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| from ..fields import DjangoConnectionField | ||||
| from .utils import get_filtering_args_from_filterset, get_filterset_class | ||||
| 
 | ||||
| 
 | ||||
| class DjangoFilterConnectionField(DjangoConnectionField): | ||||
| 
 | ||||
|     def __init__(self, type, fields=None, order_by=None, | ||||
|                  extra_filter_meta=None, filterset_class=None, | ||||
|                  *args, **kwargs): | ||||
| 
 | ||||
|         self.order_by = order_by or type._meta.filter_order_by | ||||
|         self.fields = fields or type._meta.filter_fields | ||||
|         meta = dict(model=type._meta.model, | ||||
|                     fields=self.fields, | ||||
|                     order_by=self.order_by) | ||||
|         if extra_filter_meta: | ||||
|             meta.update(extra_filter_meta) | ||||
|         self.filterset_class = get_filterset_class(filterset_class, **meta) | ||||
|         self.filtering_args = get_filtering_args_from_filterset(self.filterset_class, type) | ||||
|         kwargs.setdefault('args', {}) | ||||
|         kwargs['args'].update(**self.filtering_args) | ||||
|         super(DjangoFilterConnectionField, self).__init__(type, *args, **kwargs) | ||||
| 
 | ||||
|     def get_queryset(self, qs, args, info): | ||||
|         filterset_class = self.filterset_class | ||||
|         filter_kwargs = self.get_filter_kwargs(args) | ||||
|         order = self.get_order(args) | ||||
|         if order: | ||||
|             qs = qs.order_by(order) | ||||
|         return filterset_class(data=filter_kwargs, queryset=qs) | ||||
| 
 | ||||
|     def get_filter_kwargs(self, args): | ||||
|         return {k: v for k, v in args.items() if k in self.filtering_args} | ||||
| 
 | ||||
|     def get_order(self, args): | ||||
|         return args.get('order_by', None) | ||||
							
								
								
									
										116
									
								
								graphene-django/graphene_django/filter/filterset.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								graphene-django/graphene_django/filter/filterset.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,116 @@ | |||
| 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 graphene.contrib.django.forms import (GlobalIDFormField, | ||||
|                                            GlobalIDMultipleChoiceField) | ||||
| from graphql_relay.node.node import from_global_id | ||||
| 
 | ||||
| 
 | ||||
| class GlobalIDFilter(Filter): | ||||
|     field_class = GlobalIDFormField | ||||
| 
 | ||||
|     def filter(self, qs, value): | ||||
|         _type, _id = from_global_id(value) | ||||
|         return super(GlobalIDFilter, self).filter(qs, _id) | ||||
| 
 | ||||
| 
 | ||||
| class GlobalIDMultipleChoiceFilter(MultipleChoiceFilter): | ||||
|     field_class = GlobalIDMultipleChoiceField | ||||
| 
 | ||||
|     def filter(self, qs, value): | ||||
|         gids = [from_global_id(v)[1] for v in value] | ||||
|         return super(GlobalIDMultipleChoiceFilter, self).filter(qs, gids) | ||||
| 
 | ||||
| 
 | ||||
| ORDER_BY_FIELD = getattr(settings, 'GRAPHENE_ORDER_BY_FIELD', 'order_by') | ||||
| 
 | ||||
| 
 | ||||
| GRAPHENE_FILTER_SET_OVERRIDES = { | ||||
|     models.AutoField: { | ||||
|         'filter_class': GlobalIDFilter, | ||||
|     }, | ||||
|     models.OneToOneField: { | ||||
|         'filter_class': GlobalIDFilter, | ||||
|     }, | ||||
|     models.ForeignKey: { | ||||
|         'filter_class': GlobalIDFilter, | ||||
|     }, | ||||
|     models.ManyToManyField: { | ||||
|         'filter_class': GlobalIDMultipleChoiceFilter, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| class GrapheneFilterSetMetaclass(FilterSetMetaclass): | ||||
| 
 | ||||
|     def __new__(cls, name, bases, attrs): | ||||
|         new_class = super(GrapheneFilterSetMetaclass, cls).__new__(cls, name, bases, attrs) | ||||
|         # Customise the filter_overrides for Graphene | ||||
|         for k, v in GRAPHENE_FILTER_SET_OVERRIDES.items(): | ||||
|             new_class.filter_overrides.setdefault(k, v) | ||||
|         return new_class | ||||
| 
 | ||||
| 
 | ||||
| class GrapheneFilterSetMixin(object): | ||||
|     order_by_field = ORDER_BY_FIELD | ||||
| 
 | ||||
|     @classmethod | ||||
|     def filter_for_reverse_field(cls, f, name): | ||||
|         """Handles retrieving filters for reverse relationships | ||||
| 
 | ||||
|         We override the default implementation so that we can handle | ||||
|         Global IDs (the default implementation expects database | ||||
|         primary keys) | ||||
|         """ | ||||
|         rel = f.field.rel | ||||
|         default = { | ||||
|             'name': name, | ||||
|             'label': capfirst(rel.related_name) | ||||
|         } | ||||
|         if rel.multiple: | ||||
|             # For to-many relationships | ||||
|             return GlobalIDMultipleChoiceFilter(**default) | ||||
|         else: | ||||
|             # For to-one relationships | ||||
|             return GlobalIDFilter(**default) | ||||
| 
 | ||||
| 
 | ||||
| class GrapheneFilterSet(six.with_metaclass(GrapheneFilterSetMetaclass, GrapheneFilterSetMixin, FilterSet)): | ||||
|     """ Base class for FilterSets used by Graphene | ||||
| 
 | ||||
|     You shouldn't usually need to use this class. The | ||||
|     DjangoFilterConnectionField will wrap FilterSets with this class as | ||||
|     necessary | ||||
|     """ | ||||
| 
 | ||||
| 
 | ||||
| def setup_filterset(filterset_class): | ||||
|     """ Wrap a provided filterset in Graphene-specific functionality | ||||
|     """ | ||||
|     return type( | ||||
|         'Graphene{}'.format(filterset_class.__name__), | ||||
|         (six.with_metaclass(GrapheneFilterSetMetaclass, GrapheneFilterSetMixin, filterset_class),), | ||||
|         {}, | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def custom_filterset_factory(model, filterset_base_class=GrapheneFilterSet, | ||||
|                              **meta): | ||||
|     """ Create a filterset for the given model using the provided meta data | ||||
|     """ | ||||
|     meta.update({ | ||||
|         'model': model, | ||||
|     }) | ||||
|     meta_class = type(str('Meta'), (object,), meta) | ||||
|     filterset = type( | ||||
|         str('%sFilterSet' % model._meta.object_name), | ||||
|         (filterset_base_class,), | ||||
|         { | ||||
|             'Meta': meta_class | ||||
|         } | ||||
|     ) | ||||
|     return filterset | ||||
							
								
								
									
										31
									
								
								graphene-django/graphene_django/filter/tests/filters.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								graphene-django/graphene_django/filter/tests/filters.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| import django_filters | ||||
| 
 | ||||
| from graphene.contrib.django.tests.models import Article, Pet, Reporter | ||||
| 
 | ||||
| 
 | ||||
| class ArticleFilter(django_filters.FilterSet): | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = Article | ||||
|         fields = { | ||||
|             'headline': ['exact', 'icontains'], | ||||
|             'pub_date': ['gt', 'lt', 'exact'], | ||||
|             'reporter': ['exact'], | ||||
|         } | ||||
|         order_by = True | ||||
| 
 | ||||
| 
 | ||||
| class ReporterFilter(django_filters.FilterSet): | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = Reporter | ||||
|         fields = ['first_name', 'last_name', 'email', 'pets'] | ||||
|         order_by = False | ||||
| 
 | ||||
| 
 | ||||
| class PetFilter(django_filters.FilterSet): | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = Pet | ||||
|         fields = ['name'] | ||||
|         order_by = False | ||||
							
								
								
									
										287
									
								
								graphene-django/graphene_django/filter/tests/test_fields.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								graphene-django/graphene_django/filter/tests/test_fields.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,287 @@ | |||
| from datetime import datetime | ||||
| 
 | ||||
| import pytest | ||||
| 
 | ||||
| from graphene import ObjectType, Schema | ||||
| from graphene.contrib.django import DjangoNode | ||||
| from graphene.contrib.django.forms import (GlobalIDFormField, | ||||
|                                            GlobalIDMultipleChoiceField) | ||||
| from graphene.contrib.django.tests.models import Article, Pet, Reporter | ||||
| from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED | ||||
| from graphene.relay import NodeField | ||||
| 
 | ||||
| pytestmark = [] | ||||
| if DJANGO_FILTER_INSTALLED: | ||||
|     import django_filters | ||||
|     from graphene.contrib.django.filter import (GlobalIDFilter, DjangoFilterConnectionField, | ||||
|                                                 GlobalIDMultipleChoiceFilter) | ||||
|     from graphene.contrib.django.filter.tests.filters import ArticleFilter, PetFilter | ||||
| else: | ||||
|     pytestmark.append(pytest.mark.skipif(True, reason='django_filters not installed')) | ||||
| 
 | ||||
| pytestmark.append(pytest.mark.django_db) | ||||
| 
 | ||||
| 
 | ||||
| class ArticleNode(DjangoNode): | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = Article | ||||
| 
 | ||||
| 
 | ||||
| class ReporterNode(DjangoNode): | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = Reporter | ||||
| 
 | ||||
| 
 | ||||
| class PetNode(DjangoNode): | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = Pet | ||||
| 
 | ||||
| schema = Schema() | ||||
| 
 | ||||
| 
 | ||||
| def assert_arguments(field, *arguments): | ||||
|     ignore = ('after', 'before', 'first', 'last', 'orderBy') | ||||
|     actual = [ | ||||
|         name | ||||
|         for name in schema.T(field.arguments) | ||||
|         if name not in ignore and not name.startswith('_') | ||||
|     ] | ||||
|     assert set(arguments) == set(actual), \ | ||||
|         'Expected arguments ({}) did not match actual ({})'.format( | ||||
|             arguments, | ||||
|             actual | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def assert_orderable(field): | ||||
|     assert 'orderBy' in schema.T(field.arguments), \ | ||||
|         'Field cannot be ordered' | ||||
| 
 | ||||
| 
 | ||||
| def assert_not_orderable(field): | ||||
|     assert 'orderBy' not in schema.T(field.arguments), \ | ||||
|         'Field can be ordered' | ||||
| 
 | ||||
| 
 | ||||
| def test_filter_explicit_filterset_arguments(): | ||||
|     field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleFilter) | ||||
|     assert_arguments(field, | ||||
|                      'headline', 'headline_Icontains', | ||||
|                      'pubDate', 'pubDate_Gt', 'pubDate_Lt', | ||||
|                      'reporter', | ||||
|                      ) | ||||
| 
 | ||||
| 
 | ||||
| def test_filter_shortcut_filterset_arguments_list(): | ||||
|     field = DjangoFilterConnectionField(ArticleNode, fields=['pub_date', 'reporter']) | ||||
|     assert_arguments(field, | ||||
|                      'pubDate', | ||||
|                      'reporter', | ||||
|                      ) | ||||
| 
 | ||||
| 
 | ||||
| def test_filter_shortcut_filterset_arguments_dict(): | ||||
|     field = DjangoFilterConnectionField(ArticleNode, fields={ | ||||
|         'headline': ['exact', 'icontains'], | ||||
|         'reporter': ['exact'], | ||||
|     }) | ||||
|     assert_arguments(field, | ||||
|                      'headline', 'headline_Icontains', | ||||
|                      'reporter', | ||||
|                      ) | ||||
| 
 | ||||
| 
 | ||||
| def test_filter_explicit_filterset_orderable(): | ||||
|     field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleFilter) | ||||
|     assert_orderable(field) | ||||
| 
 | ||||
| 
 | ||||
| def test_filter_shortcut_filterset_orderable_true(): | ||||
|     field = DjangoFilterConnectionField(ArticleNode, order_by=True) | ||||
|     assert_orderable(field) | ||||
| 
 | ||||
| 
 | ||||
| def test_filter_shortcut_filterset_orderable_headline(): | ||||
|     field = DjangoFilterConnectionField(ArticleNode, order_by=['headline']) | ||||
|     assert_orderable(field) | ||||
| 
 | ||||
| 
 | ||||
| def test_filter_explicit_filterset_not_orderable(): | ||||
|     field = DjangoFilterConnectionField(PetNode, filterset_class=PetFilter) | ||||
|     assert_not_orderable(field) | ||||
| 
 | ||||
| 
 | ||||
| def test_filter_shortcut_filterset_extra_meta(): | ||||
|     field = DjangoFilterConnectionField(ArticleNode, extra_filter_meta={ | ||||
|         'order_by': True | ||||
|     }) | ||||
|     assert_orderable(field) | ||||
| 
 | ||||
| 
 | ||||
| def test_filter_filterset_information_on_meta(): | ||||
|     class ReporterFilterNode(DjangoNode): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
|             filter_fields = ['first_name', 'articles'] | ||||
|             filter_order_by = True | ||||
| 
 | ||||
|     field = DjangoFilterConnectionField(ReporterFilterNode) | ||||
|     assert_arguments(field, 'firstName', 'articles') | ||||
|     assert_orderable(field) | ||||
| 
 | ||||
| 
 | ||||
| def test_filter_filterset_information_on_meta_related(): | ||||
|     class ReporterFilterNode(DjangoNode): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
|             filter_fields = ['first_name', 'articles'] | ||||
|             filter_order_by = True | ||||
| 
 | ||||
|     class ArticleFilterNode(DjangoNode): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Article | ||||
|             filter_fields = ['headline', 'reporter'] | ||||
|             filter_order_by = True | ||||
| 
 | ||||
|     class Query(ObjectType): | ||||
|         all_reporters = DjangoFilterConnectionField(ReporterFilterNode) | ||||
|         all_articles = DjangoFilterConnectionField(ArticleFilterNode) | ||||
|         reporter = NodeField(ReporterFilterNode) | ||||
|         article = NodeField(ArticleFilterNode) | ||||
| 
 | ||||
|     schema = Schema(query=Query) | ||||
|     schema.schema  # Trigger the schema loading | ||||
|     articles_field = schema.get_type('ReporterFilterNode')._meta.fields_map['articles'] | ||||
|     assert_arguments(articles_field, 'headline', 'reporter') | ||||
|     assert_orderable(articles_field) | ||||
| 
 | ||||
| 
 | ||||
| def test_filter_filterset_related_results(): | ||||
|     class ReporterFilterNode(DjangoNode): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
|             filter_fields = ['first_name', 'articles'] | ||||
|             filter_order_by = True | ||||
| 
 | ||||
|     class ArticleFilterNode(DjangoNode): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Article | ||||
|             filter_fields = ['headline', 'reporter'] | ||||
|             filter_order_by = True | ||||
| 
 | ||||
|     class Query(ObjectType): | ||||
|         all_reporters = DjangoFilterConnectionField(ReporterFilterNode) | ||||
|         all_articles = DjangoFilterConnectionField(ArticleFilterNode) | ||||
|         reporter = NodeField(ReporterFilterNode) | ||||
|         article = NodeField(ArticleFilterNode) | ||||
| 
 | ||||
|     r1 = Reporter.objects.create(first_name='r1', last_name='r1', email='r1@test.com') | ||||
|     r2 = Reporter.objects.create(first_name='r2', last_name='r2', email='r2@test.com') | ||||
|     Article.objects.create(headline='a1', pub_date=datetime.now(), reporter=r1) | ||||
|     Article.objects.create(headline='a2', pub_date=datetime.now(), reporter=r2) | ||||
| 
 | ||||
|     query = ''' | ||||
|     query { | ||||
|         allReporters { | ||||
|             edges { | ||||
|                 node { | ||||
|                     articles { | ||||
|                         edges { | ||||
|                             node { | ||||
|                                 headline | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     ''' | ||||
|     schema = Schema(query=Query) | ||||
|     result = schema.execute(query) | ||||
|     assert not result.errors | ||||
|     # We should only get back a single article for each reporter | ||||
|     assert len(result.data['allReporters']['edges'][0]['node']['articles']['edges']) == 1 | ||||
|     assert len(result.data['allReporters']['edges'][1]['node']['articles']['edges']) == 1 | ||||
| 
 | ||||
| 
 | ||||
| def test_global_id_field_implicit(): | ||||
|     field = DjangoFilterConnectionField(ArticleNode, fields=['id']) | ||||
|     filterset_class = field.filterset_class | ||||
|     id_filter = filterset_class.base_filters['id'] | ||||
|     assert isinstance(id_filter, GlobalIDFilter) | ||||
|     assert id_filter.field_class == GlobalIDFormField | ||||
| 
 | ||||
| 
 | ||||
| def test_global_id_field_explicit(): | ||||
|     class ArticleIdFilter(django_filters.FilterSet): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Article | ||||
|             fields = ['id'] | ||||
| 
 | ||||
|     field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleIdFilter) | ||||
|     filterset_class = field.filterset_class | ||||
|     id_filter = filterset_class.base_filters['id'] | ||||
|     assert isinstance(id_filter, GlobalIDFilter) | ||||
|     assert id_filter.field_class == GlobalIDFormField | ||||
| 
 | ||||
| 
 | ||||
| def test_global_id_field_relation(): | ||||
|     field = DjangoFilterConnectionField(ArticleNode, fields=['reporter']) | ||||
|     filterset_class = field.filterset_class | ||||
|     id_filter = filterset_class.base_filters['reporter'] | ||||
|     assert isinstance(id_filter, GlobalIDFilter) | ||||
|     assert id_filter.field_class == GlobalIDFormField | ||||
| 
 | ||||
| 
 | ||||
| def test_global_id_multiple_field_implicit(): | ||||
|     field = DjangoFilterConnectionField(ReporterNode, fields=['pets']) | ||||
|     filterset_class = field.filterset_class | ||||
|     multiple_filter = filterset_class.base_filters['pets'] | ||||
|     assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) | ||||
|     assert multiple_filter.field_class == GlobalIDMultipleChoiceField | ||||
| 
 | ||||
| 
 | ||||
| def test_global_id_multiple_field_explicit(): | ||||
|     class ReporterPetsFilter(django_filters.FilterSet): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
|             fields = ['pets'] | ||||
| 
 | ||||
|     field = DjangoFilterConnectionField(ReporterNode, filterset_class=ReporterPetsFilter) | ||||
|     filterset_class = field.filterset_class | ||||
|     multiple_filter = filterset_class.base_filters['pets'] | ||||
|     assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) | ||||
|     assert multiple_filter.field_class == GlobalIDMultipleChoiceField | ||||
| 
 | ||||
| 
 | ||||
| def test_global_id_multiple_field_implicit_reverse(): | ||||
|     field = DjangoFilterConnectionField(ReporterNode, fields=['articles']) | ||||
|     filterset_class = field.filterset_class | ||||
|     multiple_filter = filterset_class.base_filters['articles'] | ||||
|     assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) | ||||
|     assert multiple_filter.field_class == GlobalIDMultipleChoiceField | ||||
| 
 | ||||
| 
 | ||||
| def test_global_id_multiple_field_explicit_reverse(): | ||||
|     class ReporterPetsFilter(django_filters.FilterSet): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
|             fields = ['articles'] | ||||
| 
 | ||||
|     field = DjangoFilterConnectionField(ReporterNode, filterset_class=ReporterPetsFilter) | ||||
|     filterset_class = field.filterset_class | ||||
|     multiple_filter = filterset_class.base_filters['articles'] | ||||
|     assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) | ||||
|     assert multiple_filter.field_class == GlobalIDMultipleChoiceField | ||||
							
								
								
									
										31
									
								
								graphene-django/graphene_django/filter/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								graphene-django/graphene_django/filter/utils.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| import six | ||||
| 
 | ||||
| from ....core.types import Argument, String | ||||
| from .filterset import custom_filterset_factory, setup_filterset | ||||
| 
 | ||||
| 
 | ||||
| def get_filtering_args_from_filterset(filterset_class, type): | ||||
|     """ Inspect a FilterSet and produce the arguments to pass to | ||||
|         a Graphene Field. These arguments will be available to | ||||
|         filter against in the GraphQL | ||||
|     """ | ||||
|     from graphene.contrib.django.form_converter import convert_form_field | ||||
| 
 | ||||
|     args = {} | ||||
|     for name, filter_field in six.iteritems(filterset_class.base_filters): | ||||
|         field_type = Argument(convert_form_field(filter_field.field)) | ||||
|         args[name] = field_type | ||||
| 
 | ||||
|     # Also add the 'order_by' field | ||||
|     if filterset_class._meta.order_by: | ||||
|         args[filterset_class.order_by_field] = Argument(String()) | ||||
|     return args | ||||
| 
 | ||||
| 
 | ||||
| def get_filterset_class(filterset_class, **meta): | ||||
|     """Get the class to be used as the FilterSet""" | ||||
|     if filterset_class: | ||||
|         # If were given a FilterSet class, then set it up and | ||||
|         # return it | ||||
|         return setup_filterset(filterset_class) | ||||
|     return custom_filterset_factory(**meta) | ||||
							
								
								
									
										70
									
								
								graphene-django/graphene_django/form_converter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								graphene-django/graphene_django/form_converter.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,70 @@ | |||
| from django import forms | ||||
| from django.forms.fields import BaseTemporalField | ||||
| 
 | ||||
| from graphene import ID, Boolean, Float, Int, String, List | ||||
| from .forms import GlobalIDFormField, GlobalIDMultipleChoiceField | ||||
| from .utils import import_single_dispatch | ||||
| 
 | ||||
| singledispatch = import_single_dispatch() | ||||
| 
 | ||||
| try: | ||||
|     UUIDField = forms.UUIDField | ||||
| except AttributeError: | ||||
|     class UUIDField(object): | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
| @singledispatch | ||||
| def convert_form_field(field): | ||||
|     raise Exception( | ||||
|         "Don't know how to convert the Django form field %s (%s) " | ||||
|         "to Graphene type" % | ||||
|         (field, field.__class__) | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| @convert_form_field.register(BaseTemporalField) | ||||
| @convert_form_field.register(forms.CharField) | ||||
| @convert_form_field.register(forms.EmailField) | ||||
| @convert_form_field.register(forms.SlugField) | ||||
| @convert_form_field.register(forms.URLField) | ||||
| @convert_form_field.register(forms.ChoiceField) | ||||
| @convert_form_field.register(forms.RegexField) | ||||
| @convert_form_field.register(forms.Field) | ||||
| @convert_form_field.register(UUIDField) | ||||
| def convert_form_field_to_string(field): | ||||
|     return String(description=field.help_text) | ||||
| 
 | ||||
| 
 | ||||
| @convert_form_field.register(forms.IntegerField) | ||||
| @convert_form_field.register(forms.NumberInput) | ||||
| def convert_form_field_to_int(field): | ||||
|     return Int(description=field.help_text) | ||||
| 
 | ||||
| 
 | ||||
| @convert_form_field.register(forms.BooleanField) | ||||
| def convert_form_field_to_boolean(field): | ||||
|     return Boolean(description=field.help_text, required=True) | ||||
| 
 | ||||
| 
 | ||||
| @convert_form_field.register(forms.NullBooleanField) | ||||
| def convert_form_field_to_nullboolean(field): | ||||
|     return Boolean(description=field.help_text) | ||||
| 
 | ||||
| 
 | ||||
| @convert_form_field.register(forms.DecimalField) | ||||
| @convert_form_field.register(forms.FloatField) | ||||
| def convert_form_field_to_float(field): | ||||
|     return Float(description=field.help_text) | ||||
| 
 | ||||
| 
 | ||||
| @convert_form_field.register(forms.ModelMultipleChoiceField) | ||||
| @convert_form_field.register(GlobalIDMultipleChoiceField) | ||||
| def convert_form_field_to_list(field): | ||||
|     return List(ID()) | ||||
| 
 | ||||
| 
 | ||||
| @convert_form_field.register(forms.ModelChoiceField) | ||||
| @convert_form_field.register(GlobalIDFormField) | ||||
| def convert_form_field_to_id(field): | ||||
|     return ID() | ||||
							
								
								
									
										42
									
								
								graphene-django/graphene_django/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								graphene-django/graphene_django/forms.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| import binascii | ||||
| 
 | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.forms import CharField, Field, IntegerField, MultipleChoiceField | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| 
 | ||||
| from graphql_relay import from_global_id | ||||
| 
 | ||||
| 
 | ||||
| class GlobalIDFormField(Field): | ||||
|     default_error_messages = { | ||||
|         'invalid': _('Invalid ID specified.'), | ||||
|     } | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|         if not value and not self.required: | ||||
|             return None | ||||
| 
 | ||||
|         try: | ||||
|             _type, _id = from_global_id(value) | ||||
|         except (TypeError, ValueError, UnicodeDecodeError, binascii.Error): | ||||
|             raise ValidationError(self.error_messages['invalid']) | ||||
| 
 | ||||
|         try: | ||||
|             IntegerField().clean(_id) | ||||
|             CharField().clean(_type) | ||||
|         except ValidationError: | ||||
|             raise ValidationError(self.error_messages['invalid']) | ||||
| 
 | ||||
|         return value | ||||
| 
 | ||||
| 
 | ||||
| class GlobalIDMultipleChoiceField(MultipleChoiceField): | ||||
|     default_error_messages = { | ||||
|         'invalid_choice': _('One of the specified IDs was invalid (%(value)s).'), | ||||
|         'invalid_list': _('Enter a list of values.'), | ||||
|     } | ||||
| 
 | ||||
|     def valid_value(self, value): | ||||
|         # Clean will raise a validation error if there is a problem | ||||
|         GlobalIDFormField().clean(value) | ||||
|         return True | ||||
|  | @ -0,0 +1,72 @@ | |||
| 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') | ||||
| 
 | ||||
| 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 | ||||
|             parser.add_argument( | ||||
|                 '--schema', | ||||
|                 type=str, | ||||
|                 dest='schema', | ||||
|                 default=getattr(settings, 'GRAPHENE_SCHEMA', ''), | ||||
|                 help='Django app containing schema to dump, e.g. myproject.core.schema') | ||||
| 
 | ||||
|             parser.add_argument( | ||||
|                 '--out', | ||||
|                 type=str, | ||||
|                 dest='out', | ||||
|                 default=getattr(settings, 'GRAPHENE_SCHEMA_OUTPUT', 'schema.json'), | ||||
|                 help='Output file (default: schema.json)') | ||||
| 
 | ||||
| 
 | ||||
| class Command(CommandArguments): | ||||
|     help = 'Dump Graphene schema JSON to file' | ||||
|     can_import_settings = True | ||||
| 
 | ||||
|     def save_file(self, out, schema_dict): | ||||
|         with open(out, 'w') as outfile: | ||||
|             json.dump(schema_dict, outfile) | ||||
| 
 | ||||
|     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)) | ||||
							
								
								
									
										0
									
								
								graphene-django/graphene_django/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								graphene-django/graphene_django/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										52
									
								
								graphene-django/graphene_django/tests/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								graphene-django/graphene_django/tests/models.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | |||
| from __future__ import absolute_import | ||||
| 
 | ||||
| from django.db import models | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| 
 | ||||
| CHOICES = ( | ||||
|     (1, 'this'), | ||||
|     (2, _('that')) | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| class Pet(models.Model): | ||||
|     name = models.CharField(max_length=30) | ||||
| 
 | ||||
| 
 | ||||
| class FilmDetails(models.Model): | ||||
|     location = models.CharField(max_length=30) | ||||
|     film = models.OneToOneField('Film', related_name='details') | ||||
| 
 | ||||
| 
 | ||||
| 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) | ||||
|     email = models.EmailField() | ||||
|     pets = models.ManyToManyField('self') | ||||
|     a_choice = models.CharField(max_length=30, choices=CHOICES) | ||||
| 
 | ||||
|     def __str__(self):              # __unicode__ on Python 2 | ||||
|         return "%s %s" % (self.first_name, self.last_name) | ||||
| 
 | ||||
| 
 | ||||
| class Article(models.Model): | ||||
|     headline = models.CharField(max_length=100) | ||||
|     pub_date = models.DateField() | ||||
|     reporter = models.ForeignKey(Reporter, related_name='articles') | ||||
|     lang = models.CharField(max_length=2, help_text='Language', choices=[ | ||||
|         ('es', 'Spanish'), | ||||
|         ('en', 'English') | ||||
|     ], default='es') | ||||
|     importance = models.IntegerField('Importance', null=True, blank=True, | ||||
|                                      choices=[(1, u'Very important'), (2, u'Not as important')]) | ||||
| 
 | ||||
|     def __str__(self):              # __unicode__ on Python 2 | ||||
|         return self.headline | ||||
| 
 | ||||
|     class Meta: | ||||
|         ordering = ('headline',) | ||||
							
								
								
									
										11
									
								
								graphene-django/graphene_django/tests/test_command.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								graphene-django/graphene_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_django.management.commands.graphql_schema.Command.save_file') | ||||
| def test_generate_file_on_call_graphql_schema(savefile_mock, settings): | ||||
|     settings.GRAPHENE_SCHEMA = 'graphene_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() | ||||
							
								
								
									
										258
									
								
								graphene-django/graphene_django/tests/test_converter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								graphene-django/graphene_django/tests/test_converter.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,258 @@ | |||
| import pytest | ||||
| from django.db import models | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| from py.test import raises | ||||
| 
 | ||||
| import graphene | ||||
| from graphene.relay import Node, ConnectionField | ||||
| from graphene.utils.get_graphql_type import get_graphql_type | ||||
| # from graphene.core.types.custom_scalars import DateTime, JSONString | ||||
| 
 | ||||
| from ..compat import (ArrayField, HStoreField, JSONField, MissingType, | ||||
|                       RangeField) | ||||
| from ..converter import convert_django_field, convert_django_field_with_choices, Registry | ||||
| from .models import Article, Reporter, Film, FilmDetails, Pet | ||||
| from ..types import DjangoObjectType, DjangoNode | ||||
| 
 | ||||
| 
 | ||||
| def assert_conversion(django_field, graphene_field, *args, **kwargs): | ||||
|     field = django_field(help_text='Custom Help Text', *args, **kwargs) | ||||
|     graphene_type = convert_django_field(field) | ||||
|     assert isinstance(graphene_type, graphene_field) | ||||
|     field = graphene_type.as_field() | ||||
|     assert field.description == 'Custom Help Text' | ||||
|     return field | ||||
| 
 | ||||
| 
 | ||||
| def test_should_unknown_django_field_raise_exception(): | ||||
|     with raises(Exception) as excinfo: | ||||
|         convert_django_field(None) | ||||
|     assert 'Don\'t know how to convert the Django field' in str(excinfo.value) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_date_convert_string(): | ||||
|     assert_conversion(models.DateField, DateTime) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_char_convert_string(): | ||||
|     assert_conversion(models.CharField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_text_convert_string(): | ||||
|     assert_conversion(models.TextField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_email_convert_string(): | ||||
|     assert_conversion(models.EmailField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_slug_convert_string(): | ||||
|     assert_conversion(models.SlugField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_url_convert_string(): | ||||
|     assert_conversion(models.URLField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_ipaddress_convert_string(): | ||||
|     assert_conversion(models.GenericIPAddressField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_file_convert_string(): | ||||
|     assert_conversion(models.FileField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_image_convert_string(): | ||||
|     assert_conversion(models.ImageField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_auto_convert_id(): | ||||
|     assert_conversion(models.AutoField, graphene.ID, primary_key=True) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_positive_integer_convert_int(): | ||||
|     assert_conversion(models.PositiveIntegerField, graphene.Int) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_positive_small_convert_int(): | ||||
|     assert_conversion(models.PositiveSmallIntegerField, graphene.Int) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_small_integer_convert_int(): | ||||
|     assert_conversion(models.SmallIntegerField, graphene.Int) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_big_integer_convert_int(): | ||||
|     assert_conversion(models.BigIntegerField, graphene.Int) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_integer_convert_int(): | ||||
|     assert_conversion(models.IntegerField, graphene.Int) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_boolean_convert_boolean(): | ||||
|     field = assert_conversion(models.BooleanField, graphene.Boolean) | ||||
|     assert field.required is True | ||||
| 
 | ||||
| 
 | ||||
| def test_should_nullboolean_convert_boolean(): | ||||
|     field = assert_conversion(models.NullBooleanField, graphene.Boolean) | ||||
|     assert field.required is False | ||||
| 
 | ||||
| 
 | ||||
| def test_field_with_choices_convert_enum(): | ||||
|     field = models.CharField(help_text='Language', choices=( | ||||
|         ('es', 'Spanish'), | ||||
|         ('en', 'English') | ||||
|     )) | ||||
| 
 | ||||
|     class TranslatedModel(models.Model): | ||||
|         language = field | ||||
| 
 | ||||
|         class Meta: | ||||
|             app_label = 'test' | ||||
| 
 | ||||
|     graphene_type = convert_django_field_with_choices(field) | ||||
|     assert issubclass(graphene_type, graphene.Enum) | ||||
|     assert graphene_type._meta.graphql_type.name == 'TEST_TRANSLATEDMODEL_LANGUAGE' | ||||
|     assert graphene_type._meta.graphql_type.description == 'Language' | ||||
|     assert graphene_type._meta.enum.__members__['SPANISH'].value == 'es' | ||||
|     assert graphene_type._meta.enum.__members__['ENGLISH'].value == 'en' | ||||
| 
 | ||||
| 
 | ||||
| def test_field_with_grouped_choices(): | ||||
|     field = models.CharField(help_text='Language', choices=( | ||||
|         ('Europe', ( | ||||
|             ('es', 'Spanish'), | ||||
|             ('en', 'English'), | ||||
|         )), | ||||
|     )) | ||||
| 
 | ||||
|     class GroupedChoicesModel(models.Model): | ||||
|         language = field | ||||
| 
 | ||||
|         class Meta: | ||||
|             app_label = 'test' | ||||
| 
 | ||||
|     convert_django_field_with_choices(field) | ||||
| 
 | ||||
| 
 | ||||
| def test_field_with_choices_gettext(): | ||||
|     field = models.CharField(help_text='Language', choices=( | ||||
|         ('es', _('Spanish')), | ||||
|         ('en', _('English')) | ||||
|     )) | ||||
| 
 | ||||
|     class TranslatedChoicesModel(models.Model): | ||||
|         language = field | ||||
| 
 | ||||
|         class Meta: | ||||
|             app_label = 'test' | ||||
| 
 | ||||
|     convert_django_field_with_choices(field) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_float_convert_float(): | ||||
|     assert_conversion(models.FloatField, graphene.Float) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_manytomany_convert_connectionorlist(): | ||||
|     registry = Registry() | ||||
|     graphene_field = convert_django_field(Reporter._meta.local_many_to_many[0], registry) | ||||
|     assert not graphene_field | ||||
| 
 | ||||
| 
 | ||||
| def test_should_manytomany_convert_connectionorlist_list(): | ||||
|     registry = Registry() | ||||
| 
 | ||||
|     class A(DjangoObjectType): | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
| 
 | ||||
|     registry.register(A) | ||||
|     graphene_field = convert_django_field(Reporter._meta.local_many_to_many[0], registry) | ||||
|     assert isinstance(graphene_field, graphene.Field) | ||||
|     assert isinstance(graphene_field.type, graphene.List) | ||||
|     assert graphene_field.type.of_type == get_graphql_type(A) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_manytomany_convert_connectionorlist_connection(): | ||||
|     registry = Registry() | ||||
|     class A(DjangoNode, DjangoObjectType): | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
| 
 | ||||
|     registry.register(A) | ||||
|     graphene_field = convert_django_field(Reporter._meta.local_many_to_many[0], registry) | ||||
|     assert isinstance(graphene_field, ConnectionField) | ||||
|     assert graphene_field.type == get_graphql_type(A.get_default_connection()) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_manytoone_convert_connectionorlist(): | ||||
|     # Django 1.9 uses 'rel', <1.9 uses 'related | ||||
|     related = getattr(Reporter.articles, 'rel', None) or \ | ||||
|         getattr(Reporter.articles, 'related') | ||||
|     registry = Registry() | ||||
| 
 | ||||
|     class A(DjangoObjectType): | ||||
|         class Meta: | ||||
|             model = Article | ||||
| 
 | ||||
|     registry.register(A) | ||||
|     graphene_field = convert_django_field(related, registry) | ||||
|     assert isinstance(graphene_field, graphene.Field) | ||||
|     assert isinstance(graphene_field.type, graphene.List) | ||||
|     assert graphene_field.type.of_type == get_graphql_type(A) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_onetoone_reverse_convert_model(): | ||||
|     # Django 1.9 uses 'rel', <1.9 uses 'related | ||||
|     related = getattr(Film.details, 'rel', None) or \ | ||||
|         getattr(Film.details, 'related') | ||||
| 
 | ||||
|     class A(DjangoObjectType): | ||||
|         class Meta: | ||||
|             model = FilmDetails | ||||
| 
 | ||||
|     registry = Registry() | ||||
|     registry.register(A) | ||||
|     graphene_field = convert_django_field(related, registry) | ||||
|     assert isinstance(graphene_field, graphene.Field) | ||||
|     assert graphene_field.type == get_graphql_type(A) | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.skipif(ArrayField is MissingType, | ||||
|                     reason="ArrayField should exist") | ||||
| def test_should_postgres_array_convert_list(): | ||||
|     field = assert_conversion(ArrayField, graphene.List, models.CharField(max_length=100)) | ||||
|     assert isinstance(field.type, graphene.List) | ||||
|     assert isinstance(field.type.of_type, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.skipif(ArrayField is MissingType, | ||||
|                     reason="ArrayField should exist") | ||||
| def test_should_postgres_array_multiple_convert_list(): | ||||
|     field = assert_conversion(ArrayField, graphene.List, ArrayField(models.CharField(max_length=100))) | ||||
|     assert isinstance(field.type, graphene.List) | ||||
|     assert isinstance(field.type.of_type, graphene.List) | ||||
|     assert isinstance(field.type.of_type.of_type, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.skipif(HStoreField is MissingType, | ||||
|                     reason="HStoreField should exist") | ||||
| def test_should_postgres_hstore_convert_string(): | ||||
|     assert_conversion(HStoreField, JSONString) | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.skipif(JSONField is MissingType, | ||||
|                     reason="JSONField should exist") | ||||
| def test_should_postgres_json_convert_string(): | ||||
|     assert_conversion(JSONField, JSONString) | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.skipif(RangeField is MissingType, | ||||
|                     reason="RangeField should exist") | ||||
| def test_should_postgres_range_convert_list(): | ||||
|     from django.contrib.postgres.fields import IntegerRangeField | ||||
|     field = assert_conversion(IntegerRangeField, graphene.List) | ||||
|     assert isinstance(field.type.of_type, graphene.Int) | ||||
							
								
								
									
										103
									
								
								graphene-django/graphene_django/tests/test_form_converter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								graphene-django/graphene_django/tests/test_form_converter.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,103 @@ | |||
| from django import forms | ||||
| from py.test import raises | ||||
| 
 | ||||
| import graphene | ||||
| from ..form_converter import convert_form_field | ||||
| from graphene import ID, List | ||||
| 
 | ||||
| from .models import Reporter | ||||
| 
 | ||||
| 
 | ||||
| def assert_conversion(django_field, graphene_field, *args): | ||||
|     field = django_field(*args, help_text='Custom Help Text') | ||||
|     graphene_type = convert_form_field(field) | ||||
|     assert isinstance(graphene_type, graphene_field) | ||||
|     field = graphene_type.as_field() | ||||
|     assert field.description == 'Custom Help Text' | ||||
|     return field | ||||
| 
 | ||||
| 
 | ||||
| def test_should_unknown_django_field_raise_exception(): | ||||
|     with raises(Exception) as excinfo: | ||||
|         convert_form_field(None) | ||||
|     assert 'Don\'t know how to convert the Django form field' in str(excinfo.value) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_date_convert_string(): | ||||
|     assert_conversion(forms.DateField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_time_convert_string(): | ||||
|     assert_conversion(forms.TimeField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_date_time_convert_string(): | ||||
|     assert_conversion(forms.DateTimeField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_char_convert_string(): | ||||
|     assert_conversion(forms.CharField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_email_convert_string(): | ||||
|     assert_conversion(forms.EmailField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_slug_convert_string(): | ||||
|     assert_conversion(forms.SlugField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_url_convert_string(): | ||||
|     assert_conversion(forms.URLField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_choice_convert_string(): | ||||
|     assert_conversion(forms.ChoiceField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_base_field_convert_string(): | ||||
|     assert_conversion(forms.Field, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_regex_convert_string(): | ||||
|     assert_conversion(forms.RegexField, graphene.String, '[0-9]+') | ||||
| 
 | ||||
| 
 | ||||
| def test_should_uuid_convert_string(): | ||||
|     if hasattr(forms, 'UUIDField'): | ||||
|         assert_conversion(forms.UUIDField, graphene.String) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_integer_convert_int(): | ||||
|     assert_conversion(forms.IntegerField, graphene.Int) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_boolean_convert_boolean(): | ||||
|     field = assert_conversion(forms.BooleanField, graphene.Boolean) | ||||
|     assert field.required is True | ||||
| 
 | ||||
| 
 | ||||
| def test_should_nullboolean_convert_boolean(): | ||||
|     field = assert_conversion(forms.NullBooleanField, graphene.Boolean) | ||||
|     assert field.required is False | ||||
| 
 | ||||
| 
 | ||||
| def test_should_float_convert_float(): | ||||
|     assert_conversion(forms.FloatField, graphene.Float) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_decimal_convert_float(): | ||||
|     assert_conversion(forms.DecimalField, graphene.Float) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_multiple_choice_convert_connectionorlist(): | ||||
|     field = forms.ModelMultipleChoiceField(Reporter.objects.all()) | ||||
|     graphene_type = convert_form_field(field) | ||||
|     assert isinstance(graphene_type, List) | ||||
|     assert isinstance(graphene_type.of_type, ID) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_manytoone_convert_connectionorlist(): | ||||
|     field = forms.ModelChoiceField(Reporter.objects.all()) | ||||
|     graphene_type = convert_form_field(field) | ||||
|     assert isinstance(graphene_type, graphene.ID) | ||||
							
								
								
									
										36
									
								
								graphene-django/graphene_django/tests/test_forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								graphene-django/graphene_django/tests/test_forms.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| from django.core.exceptions import ValidationError | ||||
| from py.test import raises | ||||
| 
 | ||||
| from ..forms import GlobalIDFormField | ||||
| 
 | ||||
| 
 | ||||
| # 'TXlUeXBlOjEwMA==' -> 'MyType', 100 | ||||
| # 'TXlUeXBlOmFiYw==' -> 'MyType', 'abc' | ||||
| 
 | ||||
| 
 | ||||
| def test_global_id_valid(): | ||||
|     field = GlobalIDFormField() | ||||
|     field.clean('TXlUeXBlOjEwMA==') | ||||
| 
 | ||||
| 
 | ||||
| def test_global_id_invalid(): | ||||
|     field = GlobalIDFormField() | ||||
|     with raises(ValidationError): | ||||
|         field.clean('badvalue') | ||||
| 
 | ||||
| 
 | ||||
| def test_global_id_none(): | ||||
|     field = GlobalIDFormField() | ||||
|     with raises(ValidationError): | ||||
|         field.clean(None) | ||||
| 
 | ||||
| 
 | ||||
| def test_global_id_none_optional(): | ||||
|     field = GlobalIDFormField(required=False) | ||||
|     field.clean(None) | ||||
| 
 | ||||
| 
 | ||||
| def test_global_id_bad_int(): | ||||
|     field = GlobalIDFormField() | ||||
|     with raises(ValidationError): | ||||
|         field.clean('TXlUeXBlOmFiYw==') | ||||
							
								
								
									
										201
									
								
								graphene-django/graphene_django/tests/test_query.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								graphene-django/graphene_django/tests/test_query.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,201 @@ | |||
| import datetime | ||||
| 
 | ||||
| import pytest | ||||
| from django.db import models | ||||
| from py.test import raises | ||||
| 
 | ||||
| import graphene | ||||
| from graphene import relay | ||||
| 
 | ||||
| from ..compat import MissingType, RangeField | ||||
| from ..types import DjangoNode, DjangoObjectType | ||||
| from .models import Article, Reporter | ||||
| 
 | ||||
| pytestmark = pytest.mark.django_db | ||||
| 
 | ||||
| 
 | ||||
| def test_should_query_only_fields(): | ||||
|     with raises(Exception): | ||||
|         class ReporterType(DjangoObjectType): | ||||
| 
 | ||||
|             class Meta: | ||||
|                 model = Reporter | ||||
|                 only_fields = ('articles', ) | ||||
| 
 | ||||
|         schema = graphene.Schema(query=ReporterType) | ||||
|         query = ''' | ||||
|             query ReporterQuery { | ||||
|               articles | ||||
|             } | ||||
|         ''' | ||||
|         result = schema.execute(query) | ||||
|         assert not result.errors | ||||
| 
 | ||||
| 
 | ||||
| def test_should_query_well(): | ||||
|     class ReporterType(DjangoObjectType): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
| 
 | ||||
|     class Query(graphene.ObjectType): | ||||
|         reporter = graphene.Field(ReporterType) | ||||
| 
 | ||||
|         def resolve_reporter(self, *args, **kwargs): | ||||
|             return ReporterType(Reporter(first_name='ABA', last_name='X')) | ||||
| 
 | ||||
|     query = ''' | ||||
|         query ReporterQuery { | ||||
|           reporter { | ||||
|             firstName, | ||||
|             lastName, | ||||
|             email | ||||
|           } | ||||
|         } | ||||
|     ''' | ||||
|     expected = { | ||||
|         'reporter': { | ||||
|             'firstName': 'ABA', | ||||
|             'lastName': 'X', | ||||
|             'email': '' | ||||
|         } | ||||
|     } | ||||
|     schema = graphene.Schema(query=Query) | ||||
|     result = schema.execute(query) | ||||
|     assert not result.errors | ||||
|     assert result.data == expected | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.skipif(RangeField is MissingType, | ||||
|                     reason="RangeField should exist") | ||||
| def test_should_query_postgres_fields(): | ||||
|     from django.contrib.postgres.fields import IntegerRangeField, ArrayField, JSONField, HStoreField | ||||
| 
 | ||||
|     class Event(models.Model): | ||||
|         ages = IntegerRangeField(help_text='The age ranges') | ||||
|         data = JSONField(help_text='Data') | ||||
|         store = HStoreField() | ||||
|         tags = ArrayField(models.CharField(max_length=50)) | ||||
| 
 | ||||
|     class EventType(DjangoObjectType): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Event | ||||
| 
 | ||||
|     class Query(graphene.ObjectType): | ||||
|         event = graphene.Field(EventType) | ||||
| 
 | ||||
|         def resolve_event(self, *args, **kwargs): | ||||
|             return Event( | ||||
|                 ages=(0, 10), | ||||
|                 data={'angry_babies': True}, | ||||
|                 store={'h': 'store'}, | ||||
|                 tags=['child', 'angry', 'babies'] | ||||
|             ) | ||||
| 
 | ||||
|     schema = graphene.Schema(query=Query) | ||||
|     query = ''' | ||||
|         query myQuery { | ||||
|           event { | ||||
|             ages | ||||
|             tags | ||||
|             data | ||||
|             store | ||||
|           } | ||||
|         } | ||||
|     ''' | ||||
|     expected = { | ||||
|         'event': { | ||||
|             'ages': [0, 10], | ||||
|             'tags': ['child', 'angry', 'babies'], | ||||
|             'data': '{"angry_babies": true}', | ||||
|             'store': '{"h": "store"}', | ||||
|         }, | ||||
|     } | ||||
|     result = schema.execute(query) | ||||
|     assert not result.errors | ||||
|     assert result.data == expected | ||||
| 
 | ||||
| 
 | ||||
| def test_should_node(): | ||||
|     class ReporterNode(DjangoNode): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
| 
 | ||||
|         @classmethod | ||||
|         def get_node(cls, id, info): | ||||
|             return ReporterNode(Reporter(id=2, first_name='Cookie Monster')) | ||||
| 
 | ||||
|         def resolve_articles(self, *args, **kwargs): | ||||
|             return [ArticleNode(Article(headline='Hi!'))] | ||||
| 
 | ||||
|     class ArticleNode(DjangoNode): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Article | ||||
| 
 | ||||
|         @classmethod | ||||
|         def get_node(cls, id, info): | ||||
|             return ArticleNode(Article(id=1, headline='Article node', pub_date=datetime.date(2002, 3, 11))) | ||||
| 
 | ||||
|     class Query(graphene.ObjectType): | ||||
|         node = relay.NodeField() | ||||
|         reporter = graphene.Field(ReporterNode) | ||||
|         article = graphene.Field(ArticleNode) | ||||
| 
 | ||||
|         def resolve_reporter(self, *args, **kwargs): | ||||
|             return ReporterNode( | ||||
|                 Reporter(id=1, first_name='ABA', last_name='X')) | ||||
| 
 | ||||
|     query = ''' | ||||
|         query ReporterQuery { | ||||
|           reporter { | ||||
|             id, | ||||
|             firstName, | ||||
|             articles { | ||||
|               edges { | ||||
|                 node { | ||||
|                   headline | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|             lastName, | ||||
|             email | ||||
|           } | ||||
|           myArticle: node(id:"QXJ0aWNsZU5vZGU6MQ==") { | ||||
|             id | ||||
|             ... on ReporterNode { | ||||
|                 firstName | ||||
|             } | ||||
|             ... on ArticleNode { | ||||
|                 headline | ||||
|                 pubDate | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|     ''' | ||||
|     expected = { | ||||
|         'reporter': { | ||||
|             'id': 'UmVwb3J0ZXJOb2RlOjE=', | ||||
|             'firstName': 'ABA', | ||||
|             'lastName': 'X', | ||||
|             'email': '', | ||||
|             'articles': { | ||||
|                 'edges': [{ | ||||
|                   'node': { | ||||
|                       'headline': 'Hi!' | ||||
|                   } | ||||
|                 }] | ||||
|             }, | ||||
|         }, | ||||
|         'myArticle': { | ||||
|             'id': 'QXJ0aWNsZU5vZGU6MQ==', | ||||
|             'headline': 'Article node', | ||||
|             'pubDate': '2002-03-11', | ||||
|         } | ||||
|     } | ||||
|     schema = graphene.Schema(query=Query) | ||||
|     result = schema.execute(query) | ||||
|     assert not result.errors | ||||
|     assert result.data == expected | ||||
							
								
								
									
										45
									
								
								graphene-django/graphene_django/tests/test_schema.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								graphene-django/graphene_django/tests/test_schema.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| from py.test import raises | ||||
| 
 | ||||
| from ..types import DjangoObjectType | ||||
| from tests.utils import assert_equal_lists | ||||
| 
 | ||||
| from .models import Reporter | ||||
| 
 | ||||
| 
 | ||||
| def test_should_raise_if_no_model(): | ||||
|     with raises(Exception) as excinfo: | ||||
|         class Character1(DjangoObjectType): | ||||
|             pass | ||||
|     assert 'model in the Meta' in str(excinfo.value) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_raise_if_model_is_invalid(): | ||||
|     with raises(Exception) as excinfo: | ||||
|         class Character2(DjangoObjectType): | ||||
| 
 | ||||
|             class Meta: | ||||
|                 model = 1 | ||||
|     assert 'not a Django model' in str(excinfo.value) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_map_fields_correctly(): | ||||
|     class ReporterType2(DjangoObjectType): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
|     assert_equal_lists( | ||||
|         ReporterType2._meta.fields_map.keys(), | ||||
|         ['articles', 'first_name', 'last_name', 'email', 'pets', 'id', 'films'] | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def test_should_map_only_few_fields(): | ||||
|     class Reporter2(DjangoObjectType): | ||||
| 
 | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
|             only_fields = ('id', 'email') | ||||
|     assert_equal_lists( | ||||
|         Reporter2._meta.fields_map.keys(), | ||||
|         ['id', 'email'] | ||||
|     ) | ||||
							
								
								
									
										102
									
								
								graphene-django/graphene_django/tests/test_types.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								graphene-django/graphene_django/tests/test_types.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,102 @@ | |||
| from graphql.type import GraphQLObjectType | ||||
| from mock import patch | ||||
| 
 | ||||
| from graphene import Schema, Interface | ||||
| from ..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 | ||||
| 
 | ||||
| schema = Schema() | ||||
| 
 | ||||
| 
 | ||||
| @schema.register | ||||
| class Character(DjangoObjectType): | ||||
|     '''Character description''' | ||||
|     class Meta: | ||||
|         model = Reporter | ||||
| 
 | ||||
| 
 | ||||
| @schema.register | ||||
| class Human(DjangoNode): | ||||
|     '''Human description''' | ||||
| 
 | ||||
|     pub_date = Int() | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = Article | ||||
| 
 | ||||
| 
 | ||||
| def test_django_interface(): | ||||
|     assert DjangoNode._meta.interface is True | ||||
| 
 | ||||
| 
 | ||||
| @patch('graphene_django.tests.models.Article.objects.get', return_value=Article(id=1)) | ||||
| def test_django_get_node(get): | ||||
|     human = Human.get_node(1, None) | ||||
|     get.assert_called_with(id=1) | ||||
|     assert human.id == 1 | ||||
| 
 | ||||
| 
 | ||||
| def test_djangonode_idfield(): | ||||
|     idfield = DjangoNode._meta.fields_map['id'] | ||||
|     assert isinstance(idfield, GlobalIDField) | ||||
| 
 | ||||
| 
 | ||||
| def test_node_idfield(): | ||||
|     idfield = Human._meta.fields_map['id'] | ||||
|     assert isinstance(idfield, GlobalIDField) | ||||
| 
 | ||||
| 
 | ||||
| def test_node_replacedfield(): | ||||
|     idfield = Human._meta.fields_map['pub_date'] | ||||
|     assert isinstance(idfield, Field) | ||||
|     assert schema.T(idfield).type == schema.T(Int()) | ||||
| 
 | ||||
| 
 | ||||
| def test_objecttype_init_none(): | ||||
|     h = Human() | ||||
|     assert h._root is None | ||||
| 
 | ||||
| 
 | ||||
| def test_objecttype_init_good(): | ||||
|     instance = Article() | ||||
|     h = Human(instance) | ||||
|     assert h._root == instance | ||||
| 
 | ||||
| 
 | ||||
| def test_object_type(): | ||||
|     object_type = schema.T(Human) | ||||
|     Human._meta.fields_map | ||||
|     assert Human._meta.interface is False | ||||
|     assert isinstance(object_type, GraphQLObjectType) | ||||
|     assert_equal_lists( | ||||
|         object_type.get_fields().keys(), | ||||
|         ['headline', 'id', 'reporter', 'pubDate'] | ||||
|     ) | ||||
|     assert schema.T(DjangoNode) in object_type.get_interfaces() | ||||
| 
 | ||||
| 
 | ||||
| def test_node_notinterface(): | ||||
|     assert Human._meta.interface is False | ||||
|     assert DjangoNode in Human._meta.interfaces | ||||
| 
 | ||||
| 
 | ||||
| def test_django_objecttype_could_extend_interface(): | ||||
|     schema = Schema() | ||||
| 
 | ||||
|     @schema.register | ||||
|     class Customer(Interface): | ||||
|         id = Int() | ||||
| 
 | ||||
|     @schema.register | ||||
|     class UserType(DjangoObjectType): | ||||
|         class Meta: | ||||
|             model = Reporter | ||||
|             interfaces = [Customer] | ||||
| 
 | ||||
|     object_type = schema.T(UserType) | ||||
|     assert schema.T(Customer) in object_type.get_interfaces() | ||||
							
								
								
									
										45
									
								
								graphene-django/graphene_django/tests/test_urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								graphene-django/graphene_django/tests/test_urls.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| from django.conf.urls import url | ||||
| 
 | ||||
| import graphene | ||||
| from graphene import Schema | ||||
| from ..types import DjangoNode | ||||
| from ..views import GraphQLView | ||||
| 
 | ||||
| from .models import Article, Reporter | ||||
| 
 | ||||
| 
 | ||||
| class Character(DjangoNode): | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = Reporter | ||||
| 
 | ||||
|     def get_node(self, id): | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
| class Human(DjangoNode): | ||||
|     raises = graphene.String() | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = Article | ||||
| 
 | ||||
|     def resolve_raises(self, *args): | ||||
|         raise Exception("This field should raise exception") | ||||
| 
 | ||||
|     def get_node(self, id): | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
| class Query(graphene.ObjectType): | ||||
|     human = graphene.Field(Human) | ||||
| 
 | ||||
|     def resolve_human(self, args, info): | ||||
|         return Human() | ||||
| 
 | ||||
| 
 | ||||
| schema = Schema(query=Query) | ||||
| 
 | ||||
| 
 | ||||
| urlpatterns = [ | ||||
|     url(r'^graphql', GraphQLView.as_view(schema=schema)), | ||||
| ] | ||||
							
								
								
									
										57
									
								
								graphene-django/graphene_django/tests/test_views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								graphene-django/graphene_django/tests/test_views.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,57 @@ | |||
| import json | ||||
| 
 | ||||
| 
 | ||||
| def format_response(response): | ||||
|     return json.loads(response.content.decode()) | ||||
| 
 | ||||
| 
 | ||||
| def test_client_get_good_query(settings, client): | ||||
|     settings.ROOT_URLCONF = 'graphene_django.tests.test_urls' | ||||
|     response = client.get('/graphql', {'query': '{ human { headline } }'}) | ||||
|     json_response = format_response(response) | ||||
|     expected_json = { | ||||
|         'data': { | ||||
|             'human': { | ||||
|                 'headline': None | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     assert json_response == expected_json | ||||
| 
 | ||||
| 
 | ||||
| def test_client_get_good_query_with_raise(settings, client): | ||||
|     settings.ROOT_URLCONF = 'graphene_django.tests.test_urls' | ||||
|     response = client.get('/graphql', {'query': '{ human { raises } }'}) | ||||
|     json_response = format_response(response) | ||||
|     assert json_response['errors'][0]['message'] == 'This field should raise exception' | ||||
|     assert json_response['data']['human']['raises'] is None | ||||
| 
 | ||||
| 
 | ||||
| def test_client_post_good_query_json(settings, client): | ||||
|     settings.ROOT_URLCONF = 'graphene_django.tests.test_urls' | ||||
|     response = client.post( | ||||
|         '/graphql', json.dumps({'query': '{ human { headline } }'}), 'application/json') | ||||
|     json_response = format_response(response) | ||||
|     expected_json = { | ||||
|         'data': { | ||||
|             'human': { | ||||
|                 'headline': None | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     assert json_response == expected_json | ||||
| 
 | ||||
| 
 | ||||
| def test_client_post_good_query_graphql(settings, client): | ||||
|     settings.ROOT_URLCONF = 'graphene_django.tests.test_urls' | ||||
|     response = client.post( | ||||
|         '/graphql', '{ human { headline } }', 'application/graphql') | ||||
|     json_response = format_response(response) | ||||
|     expected_json = { | ||||
|         'data': { | ||||
|             'human': { | ||||
|                 'headline': None | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     assert json_response == expected_json | ||||
							
								
								
									
										92
									
								
								graphene-django/graphene_django/types.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								graphene-django/graphene_django/types.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,92 @@ | |||
| import inspect | ||||
| 
 | ||||
| import six | ||||
| from django.db import models | ||||
| 
 | ||||
| from graphene import Field, Interface | ||||
| from graphene.types.objecttype import ObjectType, ObjectTypeMeta, attrs_without_fields, GrapheneObjectType, get_interfaces | ||||
| from graphene.types.interface import InterfaceTypeMeta | ||||
| from graphene.relay import Connection, Node | ||||
| from graphene.relay.node import NodeMeta | ||||
| from .converter import convert_django_field_with_choices, Registry | ||||
| from graphene.types.options import Options | ||||
| from graphene import String | ||||
| from .utils import get_model_fields | ||||
| from graphene.utils.is_base_type import is_base_type | ||||
| 
 | ||||
| from graphene.utils.copy_fields import copy_fields | ||||
| from graphene.utils.get_fields import get_fields | ||||
| from graphene.utils.is_base_type import is_base_type | ||||
| 
 | ||||
| 
 | ||||
| class DjangoObjectTypeMeta(ObjectTypeMeta): | ||||
|     def __new__(cls, name, bases, attrs): | ||||
|         # super_new = super(DjangoObjectTypeMeta, cls).__new__ | ||||
|         super_new = type.__new__ | ||||
| 
 | ||||
|         # Also ensure initialization is only performed for subclasses of Model | ||||
|         # (excluding Model class itself). | ||||
|         if not is_base_type(bases, DjangoObjectTypeMeta): | ||||
|             return super_new(cls, name, bases, attrs) | ||||
| 
 | ||||
|         options = Options( | ||||
|             attrs.pop('Meta', None), | ||||
|             name=None, | ||||
|             description=None, | ||||
|             model=None, | ||||
|             fields=(), | ||||
|             exclude=(), | ||||
|             interfaces=(), | ||||
|         ) | ||||
|         assert options.model, 'You need to pass a valid Django Model in {}.Meta'.format(name) | ||||
|         get_model_fields(options.model) | ||||
| 
 | ||||
|         interfaces = tuple(options.interfaces) | ||||
|         fields = get_fields(ObjectType, attrs, bases, interfaces) | ||||
|         attrs = attrs_without_fields(attrs, fields) | ||||
|         cls = super_new(cls, name, bases, dict(attrs, _meta=options)) | ||||
| 
 | ||||
|         fields = copy_fields(Field, fields, parent=cls) | ||||
|         base_interfaces = tuple(b for b in bases if issubclass(b, Interface)) | ||||
|         options.graphql_type = GrapheneObjectType( | ||||
|             graphene_type=cls, | ||||
|             name=options.name or cls.__name__, | ||||
|             description=options.description or cls.__doc__, | ||||
|             fields=fields, | ||||
|             interfaces=tuple(get_interfaces(interfaces + base_interfaces)) | ||||
|         ) | ||||
| 
 | ||||
|         # for field in all_fields: | ||||
|         #     is_not_in_only = only_fields and field.name not in only_fields | ||||
|         #     is_already_created = field.name in already_created_fields | ||||
|         #     is_excluded = field.name in cls._meta.exclude_fields or is_already_created | ||||
|         #     if is_not_in_only or is_excluded: | ||||
|         #         # We skip this field if we specify only_fields and is not | ||||
|         #         # in there. Or when we exclude this field in exclude_fields | ||||
|         #         continue | ||||
|         #         converted_field = convert_django_field_with_choices(field) | ||||
|         return cls | ||||
| 
 | ||||
| 
 | ||||
| class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, ObjectType)): | ||||
|     _registry = None | ||||
| 
 | ||||
|     @classmethod | ||||
|     def get_registry(cls): | ||||
|         if not DjangoObjectType._registry: | ||||
|             DjangoObjectType._registry = Registry() | ||||
|         return DjangoObjectType._registry | ||||
| 
 | ||||
| 
 | ||||
| class DjangoNodeMeta(DjangoObjectTypeMeta, NodeMeta): | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class DjangoNode(six.with_metaclass(DjangoNodeMeta, Node)): | ||||
|     @classmethod | ||||
|     def get_node(cls, id, context, info): | ||||
|         try: | ||||
|             instance = cls._meta.model.objects.get(id=id) | ||||
|             return cls(instance) | ||||
|         except cls._meta.model.DoesNotExist: | ||||
|             return None | ||||
							
								
								
									
										98
									
								
								graphene-django/graphene_django/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								graphene-django/graphene_django/utils.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,98 @@ | |||
| from django.db import models | ||||
| from django.db.models.manager import Manager | ||||
| from django.db.models.query import QuerySet | ||||
| 
 | ||||
| # from graphene.utils import LazyList | ||||
| class LazyList(object): | ||||
|     pass | ||||
| 
 | ||||
| from .compat import RelatedObject | ||||
| 
 | ||||
| try: | ||||
|     import django_filters  # noqa | ||||
|     DJANGO_FILTER_INSTALLED = True | ||||
| except (ImportError, AttributeError): | ||||
|     # AtributeError raised if DjangoFilters installed with a incompatible Django Version | ||||
|     DJANGO_FILTER_INSTALLED = False | ||||
| 
 | ||||
| 
 | ||||
| def get_type_for_model(schema, model): | ||||
|     schema = schema | ||||
|     types = schema.types.values() | ||||
|     for _type in types: | ||||
|         type_model = hasattr(_type, '_meta') and getattr( | ||||
|             _type._meta, 'model', None) | ||||
|         if model == type_model: | ||||
|             return _type | ||||
| 
 | ||||
| 
 | ||||
| def get_reverse_fields(model): | ||||
|     for name, attr in model.__dict__.items(): | ||||
|         # Django =>1.9 uses 'rel', django <1.9 uses 'related' | ||||
|         related = getattr(attr, 'rel', None) or \ | ||||
|             getattr(attr, 'related', None) | ||||
|         if isinstance(related, RelatedObject): | ||||
|             # Hack for making it compatible with Django 1.6 | ||||
|             new_related = RelatedObject(related.parent_model, related.model, related.field) | ||||
|             new_related.name = name | ||||
|             yield new_related | ||||
|         elif isinstance(related, models.ManyToOneRel): | ||||
|             yield related | ||||
|         elif isinstance(related, models.ManyToManyRel) and not related.symmetrical: | ||||
|             yield related | ||||
| 
 | ||||
| 
 | ||||
| class WrappedQueryset(LazyList): | ||||
| 
 | ||||
|     def __len__(self): | ||||
|         # Dont calculate the length using len(queryset), as this will | ||||
|         # evaluate the whole queryset and return it's length. | ||||
|         # Use .count() instead | ||||
|         return self._origin.count() | ||||
| 
 | ||||
| 
 | ||||
| def maybe_queryset(value): | ||||
|     if isinstance(value, Manager): | ||||
|         value = value.get_queryset() | ||||
|     if isinstance(value, QuerySet): | ||||
|         return WrappedQueryset(value) | ||||
|     return value | ||||
| 
 | ||||
| 
 | ||||
| def get_model_fields(model): | ||||
|     reverse_fields = get_reverse_fields(model) | ||||
|     all_fields = sorted(list(model._meta.fields) + | ||||
|                         list(model._meta.local_many_to_many)) | ||||
|     all_fields += list(reverse_fields) | ||||
| 
 | ||||
|     return all_fields | ||||
| 
 | ||||
| 
 | ||||
| def get_related_model(field): | ||||
|     if hasattr(field, 'rel'): | ||||
|         # Django 1.6, 1.7 | ||||
|         return field.rel.to | ||||
|     return field.related_model | ||||
| 
 | ||||
| 
 | ||||
| def import_single_dispatch(): | ||||
|     try: | ||||
|         from functools import singledispatch | ||||
|     except ImportError: | ||||
|         singledispatch = None | ||||
| 
 | ||||
|     if not singledispatch: | ||||
|         try: | ||||
|             from singledispatch import singledispatch | ||||
|         except ImportError: | ||||
|             pass | ||||
| 
 | ||||
|     if not singledispatch: | ||||
|         raise Exception( | ||||
|             "It seems your python version does not include " | ||||
|             "functools.singledispatch. Please install the 'singledispatch' " | ||||
|             "package. More information here: " | ||||
|             "https://pypi.python.org/pypi/singledispatch" | ||||
|         ) | ||||
| 
 | ||||
|     return singledispatch | ||||
							
								
								
									
										12
									
								
								graphene-django/graphene_django/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								graphene-django/graphene_django/views.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| from graphql_django_view import GraphQLView as BaseGraphQLView | ||||
| 
 | ||||
| 
 | ||||
| class GraphQLView(BaseGraphQLView): | ||||
|     graphene_schema = None | ||||
| 
 | ||||
|     def __init__(self, schema, **kwargs): | ||||
|         super(GraphQLView, self).__init__( | ||||
|             graphene_schema=schema, | ||||
|             schema=schema, | ||||
|             **kwargs | ||||
|         ) | ||||
							
								
								
									
										2
									
								
								graphene-django/setup.cfg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								graphene-django/setup.cfg
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| [pytest] | ||||
| DJANGO_SETTINGS_MODULE = django_test_settings | ||||
							
								
								
									
										49
									
								
								graphene-django/setup.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								graphene-django/setup.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | |||
| from setuptools import find_packages, setup | ||||
| 
 | ||||
| setup( | ||||
|     name='graphene-django', | ||||
|     version='1.0', | ||||
| 
 | ||||
|     description='Graphene Django integration', | ||||
|     # long_description=open('README.rst').read(), | ||||
| 
 | ||||
|     url='https://github.com/graphql-python/graphene-django', | ||||
| 
 | ||||
|     author='Syrus Akbary', | ||||
|     author_email='me@syrusakbary.com', | ||||
| 
 | ||||
|     license='MIT', | ||||
| 
 | ||||
|     classifiers=[ | ||||
|         'Development Status :: 3 - Alpha', | ||||
|         'Intended Audience :: Developers', | ||||
|         'Topic :: Software Development :: Libraries', | ||||
|         'Programming Language :: Python :: 2', | ||||
|         'Programming Language :: Python :: 2.7', | ||||
|         'Programming Language :: Python :: 3', | ||||
|         'Programming Language :: Python :: 3.3', | ||||
|         'Programming Language :: Python :: 3.4', | ||||
|         'Programming Language :: Python :: 3.5', | ||||
|         'Programming Language :: Python :: Implementation :: PyPy', | ||||
|     ], | ||||
| 
 | ||||
|     keywords='api graphql protocol rest relay graphene', | ||||
| 
 | ||||
|     packages=find_packages(exclude=['tests']), | ||||
| 
 | ||||
|     install_requires=[ | ||||
|         'six>=1.10.0', | ||||
|         'graphene>=1.0', | ||||
|         'Django>=1.6.0', | ||||
|         'singledispatch>=3.4.0.3', | ||||
|         'graphql-django-view>=1.3', | ||||
|     ], | ||||
|     tests_require=[ | ||||
|         'django-filter>=0.10.0', | ||||
|         'pytest>=2.7.2', | ||||
|         'pytest-django', | ||||
|         'mock', | ||||
|         # Required for Django postgres fields testing | ||||
|         'psycopg2', | ||||
|     ], | ||||
| ) | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user