mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-26 03:23:55 +03:00
Work on Django integration as per #48
Discussion can be found here: https://github.com/graphql-python/graphene/issues/48 Original gist can be found here: https://gist.github.com/adamcharnock/ad051b419d4c613d40fe
This commit is contained in:
parent
699aebec33
commit
6e63e7b42d
|
@ -5,7 +5,8 @@ from graphene.contrib.django.types import (
|
|||
)
|
||||
from graphene.contrib.django.fields import (
|
||||
DjangoConnectionField,
|
||||
DjangoModelField
|
||||
DjangoModelField,
|
||||
DjangoFilterConnectionField
|
||||
)
|
||||
|
||||
__all__ = ['DjangoObjectType', 'DjangoNode', 'DjangoConnection',
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
from django import forms
|
||||
from django.db import models
|
||||
from singledispatch import singledispatch
|
||||
|
||||
from ...core.types.scalars import ID, Boolean, Float, Int, String
|
||||
from .fields import ConnectionOrListField, DjangoModelField
|
||||
|
||||
try:
|
||||
UUIDField = models.UUIDField
|
||||
UUIDModelField = models.UUIDField
|
||||
UUIDFormField = forms.UUIDField
|
||||
except AttributeError:
|
||||
# Improved compatibility for Django 1.6
|
||||
class UUIDField(object):
|
||||
class UUIDModelField(object):
|
||||
pass
|
||||
class UUIDFormField(object):
|
||||
pass
|
||||
|
||||
|
||||
|
@ -25,7 +28,7 @@ def convert_django_field(field):
|
|||
@convert_django_field.register(models.EmailField)
|
||||
@convert_django_field.register(models.SlugField)
|
||||
@convert_django_field.register(models.URLField)
|
||||
@convert_django_field.register(UUIDField)
|
||||
@convert_django_field.register(UUIDModelField)
|
||||
def convert_field_to_string(field):
|
||||
return String(description=field.help_text)
|
||||
|
||||
|
@ -63,6 +66,7 @@ def convert_field_to_float(field):
|
|||
@convert_django_field.register(models.ManyToManyField)
|
||||
@convert_django_field.register(models.ManyToOneRel)
|
||||
def convert_field_to_list_or_connection(field):
|
||||
from .fields import DjangoModelField, ConnectionOrListField
|
||||
model_field = DjangoModelField(field.related_model)
|
||||
return ConnectionOrListField(model_field)
|
||||
|
||||
|
@ -70,4 +74,7 @@ def convert_field_to_list_or_connection(field):
|
|||
@convert_django_field.register(models.OneToOneField)
|
||||
@convert_django_field.register(models.ForeignKey)
|
||||
def convert_field_to_djangomodel(field):
|
||||
from .fields import DjangoModelField
|
||||
return DjangoModelField(field.related_model, description=field.help_text)
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
import warnings
|
||||
|
||||
import six
|
||||
|
||||
|
||||
from ...core.exceptions import SkipField
|
||||
from ...core.fields import Field
|
||||
from ...core.types import Argument, String
|
||||
from ...core.types.base import FieldType
|
||||
from ...core.types.definitions import List
|
||||
from ...relay import ConnectionField
|
||||
from ...relay.utils import is_node
|
||||
from .form_converter import convert_form_field
|
||||
from .resolvers import FilterConnectionResolver
|
||||
from .utils import get_type_for_model
|
||||
|
||||
|
||||
|
@ -58,3 +64,27 @@ class DjangoModelField(FieldType):
|
|||
|
||||
def get_object_type(self, schema):
|
||||
return get_type_for_model(schema, self.model)
|
||||
|
||||
|
||||
class DjangoFilterConnectionField(DjangoConnectionField):
|
||||
|
||||
def __init__(self, type, filterset_class, resolver=None, on=None, *args, **kwargs):
|
||||
if not resolver:
|
||||
resolver = FilterConnectionResolver(type, on, filterset_class)
|
||||
|
||||
kwargs.setdefault('args', {})
|
||||
kwargs['args'].update(**self.get_filtering_args(type, filterset_class))
|
||||
super(DjangoFilterConnectionField, self).__init__(type, resolver, *args, **kwargs)
|
||||
|
||||
def get_filtering_args(self, type, filterset_class):
|
||||
args = {}
|
||||
for name, filter_field in six.iteritems(filterset_class.base_filters):
|
||||
field_type = Argument(convert_form_field(filter_field.field))
|
||||
# Is this correct? I don't quite grok the 'parent' system yet
|
||||
field_type.mount(type)
|
||||
args[name] = field_type
|
||||
|
||||
# Also add the 'order_by' field
|
||||
args[filterset_class.order_by_field] = Argument(String)
|
||||
return args
|
||||
|
||||
|
|
63
graphene/contrib/django/form_converter.py
Normal file
63
graphene/contrib/django/form_converter.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
from django import forms
|
||||
from django.forms.fields import BaseTemporalField
|
||||
from singledispatch import singledispatch
|
||||
|
||||
from graphene import String, Int, Boolean, Float, ID
|
||||
from .converter import UUIDFormField
|
||||
|
||||
|
||||
@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.Field)
|
||||
@convert_form_field.register(UUIDFormField)
|
||||
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)
|
||||
@convert_form_field.register(forms.NullBooleanField)
|
||||
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)
|
||||
def convert_form_field_to_list_or_connection(field):
|
||||
from .fields import DjangoModelField, ConnectionOrListField
|
||||
model_field = DjangoModelField(field.related_model)
|
||||
return ConnectionOrListField(model_field)
|
||||
|
||||
|
||||
@convert_form_field.register(forms.ModelChoiceField)
|
||||
def convert_form_field_to_djangomodel(field):
|
||||
return ID()
|
||||
# return DjangoModelField(field.queryset.model, description=field.help_text)
|
90
graphene/contrib/django/resolvers.py
Normal file
90
graphene/contrib/django/resolvers.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django_filters.filterset import filterset_factory
|
||||
|
||||
|
||||
class BaseQuerySetConnectionResolver(object):
|
||||
|
||||
def __init__(self, node, on=None):
|
||||
self.node = node
|
||||
self.model = node._meta.model
|
||||
# The name of the field on the model which contains the
|
||||
# manager upon which to perform the query. Optional.
|
||||
# If omitted the model's default manager will be used.
|
||||
self.on = on
|
||||
|
||||
def __call__(self, inst, args, info):
|
||||
self.inst = inst
|
||||
self.args = args
|
||||
self.info = info
|
||||
return self.make_query()
|
||||
|
||||
def get_manager(self):
|
||||
if self.on:
|
||||
return getattr(self.inst, self.on)
|
||||
else:
|
||||
return self.model._default_manager
|
||||
|
||||
def make_query(self):
|
||||
raise NotImplemented()
|
||||
|
||||
|
||||
class SimpleQuerySetConnectionResolver(BaseQuerySetConnectionResolver):
|
||||
# Simple querying without using django-filter (ported from previous gist)
|
||||
|
||||
def make_query(self):
|
||||
filter_kwargs = self.get_filter_kwargs()
|
||||
query = self.get_manager().filter(**filter_kwargs)
|
||||
order = self.get_order()
|
||||
if order:
|
||||
query = query.order_by(order)
|
||||
return query
|
||||
|
||||
def get_filter_kwargs(self):
|
||||
ignore = ['first', 'last', 'before', 'after', 'order']
|
||||
return {k: v for k, v in self.args.items() if k not in ignore}
|
||||
|
||||
def get_order(self):
|
||||
return self.args.get('order', None)
|
||||
|
||||
|
||||
class FilterConnectionResolver(BaseQuerySetConnectionResolver):
|
||||
# Querying using django-filter
|
||||
|
||||
def __init__(self, node, on=None, filterset_class=None):
|
||||
self.filterset_class = filterset_class
|
||||
super(FilterConnectionResolver, self).__init__(node, on)
|
||||
|
||||
def make_query(self):
|
||||
filterset_class = self.get_filterset_class()
|
||||
filterset = self.get_filterset(filterset_class)
|
||||
return filterset.qs
|
||||
|
||||
def get_filterset_class(self):
|
||||
if self.filterset_class:
|
||||
return self.filterset_class
|
||||
elif self.model:
|
||||
return filterset_factory(self.model)
|
||||
else:
|
||||
msg = "'%s' must define 'filterset_class' or 'model'"
|
||||
raise ImproperlyConfigured(msg % self.__class__.__name__)
|
||||
|
||||
def get_filterset(self, filterset_class):
|
||||
kwargs = self.get_filterset_kwargs(filterset_class)
|
||||
return filterset_class(**kwargs)
|
||||
|
||||
def get_filterset_kwargs(self, filterset_class):
|
||||
kwargs = {'data': self.args or None}
|
||||
try:
|
||||
kwargs.update({
|
||||
'queryset': self.get_manager(),
|
||||
})
|
||||
except ImproperlyConfigured:
|
||||
# ignore the error here if the filterset has a model defined
|
||||
# to acquire a queryset from
|
||||
if filterset_class._meta.model is None:
|
||||
msg = ("'%s' does not define a 'model' and the resolver '%s' "
|
||||
"does not return a valid queryset from 'get_queryset'. "
|
||||
"You must fix one of them.")
|
||||
args = (filterset_class.__name__, self.__class__.__name__)
|
||||
raise ImproperlyConfigured(msg % args)
|
||||
return kwargs
|
Loading…
Reference in New Issue
Block a user