mirror of
				https://github.com/graphql-python/graphene.git
				synced 2025-10-31 07:57:26 +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', | ||||||
|  |     ], | ||||||
|  | ) | ||||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							|  | @ -24,7 +24,7 @@ class PyTest(TestCommand): | ||||||
| 
 | 
 | ||||||
| setup( | setup( | ||||||
|     name='graphene', |     name='graphene', | ||||||
|     version='0.10.2', |     version='1.0.0', | ||||||
| 
 | 
 | ||||||
|     description='GraphQL Framework for Python', |     description='GraphQL Framework for Python', | ||||||
|     long_description=open('README.rst').read(), |     long_description=open('README.rst').read(), | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user