mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-02-05 06:00:37 +03:00
Merge branch 'master' into recursive-nodes
This commit is contained in:
commit
0200b3244c
35
.travis.yml
35
.travis.yml
|
@ -6,19 +6,19 @@ python:
|
||||||
- 3.5
|
- 3.5
|
||||||
- pypy
|
- pypy
|
||||||
before_install:
|
before_install:
|
||||||
- |
|
- |
|
||||||
if [ "$TRAVIS_PYTHON_VERSION" = "pypy" ]; then
|
if [ "$TRAVIS_PYTHON_VERSION" = "pypy" ]; then
|
||||||
export PYENV_ROOT="$HOME/.pyenv"
|
export PYENV_ROOT="$HOME/.pyenv"
|
||||||
if [ -f "$PYENV_ROOT/bin/pyenv" ]; then
|
if [ -f "$PYENV_ROOT/bin/pyenv" ]; then
|
||||||
cd "$PYENV_ROOT" && git pull
|
cd "$PYENV_ROOT" && git pull
|
||||||
else
|
else
|
||||||
rm -rf "$PYENV_ROOT" && git clone --depth 1 https://github.com/yyuu/pyenv.git "$PYENV_ROOT"
|
rm -rf "$PYENV_ROOT" && git clone --depth 1 https://github.com/yyuu/pyenv.git "$PYENV_ROOT"
|
||||||
fi
|
fi
|
||||||
export PYPY_VERSION="4.0.1"
|
export PYPY_VERSION="4.0.1"
|
||||||
"$PYENV_ROOT/bin/pyenv" install "pypy-$PYPY_VERSION"
|
"$PYENV_ROOT/bin/pyenv" install "pypy-$PYPY_VERSION"
|
||||||
virtualenv --python="$PYENV_ROOT/versions/pypy-$PYPY_VERSION/bin/python" "$HOME/virtualenvs/pypy-$PYPY_VERSION"
|
virtualenv --python="$PYENV_ROOT/versions/pypy-$PYPY_VERSION/bin/python" "$HOME/virtualenvs/pypy-$PYPY_VERSION"
|
||||||
source "$HOME/virtualenvs/pypy-$PYPY_VERSION/bin/activate"
|
source "$HOME/virtualenvs/pypy-$PYPY_VERSION/bin/activate"
|
||||||
fi
|
fi
|
||||||
install:
|
install:
|
||||||
- |
|
- |
|
||||||
if [ "$TEST_TYPE" = build ]; then
|
if [ "$TEST_TYPE" = build ]; then
|
||||||
|
@ -45,7 +45,7 @@ after_success:
|
||||||
fi
|
fi
|
||||||
env:
|
env:
|
||||||
matrix:
|
matrix:
|
||||||
- TEST_TYPE=build
|
- TEST_TYPE=build DJANGO_VERSION=1.10
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
|
@ -59,3 +59,10 @@ matrix:
|
||||||
env: TEST_TYPE=build DJANGO_VERSION=1.9
|
env: TEST_TYPE=build DJANGO_VERSION=1.9
|
||||||
- python: '2.7'
|
- python: '2.7'
|
||||||
env: TEST_TYPE=lint
|
env: TEST_TYPE=lint
|
||||||
|
deploy:
|
||||||
|
provider: pypi
|
||||||
|
user: syrusakbary
|
||||||
|
on:
|
||||||
|
tags: true
|
||||||
|
password:
|
||||||
|
secure: kymIFCEPUbkgRqe2NAXkWfxMmGRfWvWBOP6LIXdVdkOOkm91fU7bndPGrAjos+/7gN0Org609ZmHSlVXNMJUWcsL2or/x5LcADJ4cZDe+79qynuoRb9xs1Ri4O4SBAuVMZxuVJvs8oUzT2R11ql5vASSMtXgbX+ZDGpmPRVZStkCuXgOc4LBhbPKyl3OFy7UQFPgAEmy3Yjh4ZSKzlXheK+S6mmr60+DCIjpaA0BWPxYK9FUE0qm7JJbHLUbwsUP/QMp5MmGjwFisXCNsIe686B7QKRaiOw62eJc2R7He8AuEC8T9OM4kRwDlecSn8mMpkoSB7QWtlJ+6XdLrJFPNvtrOfgfzS9/96Qrw9WlOslk68hMlhJeRb0s2YUD8tiV3UUkvbL1mfFoS4SI9U+rojS55KhUEJWHg1w7DjoOPoZmaIL2ChRupmvrFYNAGae1cxwG3Urh+t3wYlN3gpKsRDe5GOT7Wm2tr0ad3McCpDGUwSChX59BAJXe/MoLxkKScTrMyR8yMxHOF0b4zpVn5l7xB/o2Ik4zavx5q/0rGBMK2D+5d+gpQogKShoquTPsZUwO7sB5hYeH2hqGqpeGzZtb76E2zZYd18pJ0FsBudm5+KWjYdZ+vbtGrLxdTXJ1EEtzVXm0lscykTpqUucbXSa51dhStJvW2xEEz6p3rHo=
|
||||||
|
|
|
@ -12,7 +12,7 @@ Let's use a simple example model.
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
class Post(models.Model):
|
class Post(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
title = models.CharField(max_length=100)
|
||||||
content = models.TextField()
|
content = models.TextField()
|
||||||
published = models.BooleanField(default=False)
|
published = models.BooleanField(default=False)
|
||||||
owner = models.ForeignKey('auth.User')
|
owner = models.ForeignKey('auth.User')
|
||||||
|
|
|
@ -91,50 +91,13 @@ Which you could query as follows:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Orderable fields
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Ordering can also be specified using ``filter_order_by``. Like
|
|
||||||
``filter_fields``, this value is also passed directly to
|
|
||||||
``django-filter`` as the ``order_by`` field. For full details see the
|
|
||||||
`order\_by
|
|
||||||
documentation <https://django-filter.readthedocs.org/en/latest/usage.html#ordering-using-order-by>`__.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
class AnimalNode(DjangoObjectType):
|
|
||||||
class Meta:
|
|
||||||
model = Animal
|
|
||||||
filter_fields = ['name', 'genus', 'is_domesticated']
|
|
||||||
# Either a tuple/list of fields upon which ordering is allowed, or
|
|
||||||
# True to allow filtering on all fields specified in filter_fields
|
|
||||||
filter_order_by = True
|
|
||||||
interfaces = (relay.Node, )
|
|
||||||
|
|
||||||
You can then control the ordering via the ``orderBy`` argument:
|
|
||||||
|
|
||||||
.. code::
|
|
||||||
|
|
||||||
query {
|
|
||||||
allAnimals(orderBy: "name") {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id,
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Custom Filtersets
|
Custom Filtersets
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
By default Graphene provides easy access to the most commonly used
|
By default Graphene provides easy access to the most commonly used
|
||||||
features of ``django-filter``. This is done by transparently creating a
|
features of ``django-filter``. This is done by transparently creating a
|
||||||
``django_filters.FilterSet`` class for you and passing in the values for
|
``django_filters.FilterSet`` class for you and passing in the values for
|
||||||
``filter_fields`` and ``filter_order_by``.
|
``filter_fields``.
|
||||||
|
|
||||||
However, you may find this to be insufficient. In these cases you can
|
However, you may find this to be insufficient. In these cases you can
|
||||||
create your own ``Filterset`` as follows:
|
create your own ``Filterset`` as follows:
|
||||||
|
|
|
@ -31,6 +31,7 @@ We will setup the project, create the following:
|
||||||
|
|
||||||
# Set up a new project with a single application
|
# Set up a new project with a single application
|
||||||
django-admin.py startproject cookbook . # Note the trailing '.' character
|
django-admin.py startproject cookbook . # Note the trailing '.' character
|
||||||
|
cd cookbook
|
||||||
django-admin.py startapp ingredients
|
django-admin.py startapp ingredients
|
||||||
|
|
||||||
Now sync your database for the first time:
|
Now sync your database for the first time:
|
||||||
|
@ -98,7 +99,6 @@ Create ``cookbook/ingredients/schema.py`` and type the following:
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Category
|
model = Category
|
||||||
filter_fields = ['name', 'ingredients']
|
filter_fields = ['name', 'ingredients']
|
||||||
filter_order_by = ['name']
|
|
||||||
interfaces = (relay.Node, )
|
interfaces = (relay.Node, )
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,7 +112,6 @@ Create ``cookbook/ingredients/schema.py`` and type the following:
|
||||||
'category': ['exact'],
|
'category': ['exact'],
|
||||||
'category__name': ['exact'],
|
'category__name': ['exact'],
|
||||||
}
|
}
|
||||||
filter_order_by = ['name', 'category__name']
|
|
||||||
interfaces = (relay.Node, )
|
interfaces = (relay.Node, )
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -60,5 +60,5 @@ Now you should be ready to start the server:
|
||||||
Now head on over to
|
Now head on over to
|
||||||
[http://127.0.0.1:8000/graphql](http://127.0.0.1:8000/graphql)
|
[http://127.0.0.1:8000/graphql](http://127.0.0.1:8000/graphql)
|
||||||
and run some queries!
|
and run some queries!
|
||||||
(See the [Graphene-Django Tutorial](http://docs.graphene-python.org/projects/django/en/latest/tutorial.html#testing-our-graphql-schema)
|
(See the [Graphene-Django Tutorial](http://docs.graphene-python.org/projects/django/en/latest/tutorial#testing-our-graphql-schema)
|
||||||
for some example queries)
|
for some example queries)
|
||||||
|
|
|
@ -12,7 +12,6 @@ class CategoryNode(DjangoObjectType):
|
||||||
model = Category
|
model = Category
|
||||||
interfaces = (Node, )
|
interfaces = (Node, )
|
||||||
filter_fields = ['name', 'ingredients']
|
filter_fields = ['name', 'ingredients']
|
||||||
filter_order_by = ['name']
|
|
||||||
|
|
||||||
|
|
||||||
class IngredientNode(DjangoObjectType):
|
class IngredientNode(DjangoObjectType):
|
||||||
|
@ -27,7 +26,6 @@ class IngredientNode(DjangoObjectType):
|
||||||
'category': ['exact'],
|
'category': ['exact'],
|
||||||
'category__name': ['exact'],
|
'category__name': ['exact'],
|
||||||
}
|
}
|
||||||
filter_order_by = ['name', 'category__name']
|
|
||||||
|
|
||||||
|
|
||||||
class Query(AbstractType):
|
class Query(AbstractType):
|
||||||
|
|
|
@ -9,7 +9,7 @@ class RecipeNode(DjangoObjectType):
|
||||||
model = Recipe
|
model = Recipe
|
||||||
interfaces = (Node, )
|
interfaces = (Node, )
|
||||||
filter_fields = ['title','amounts']
|
filter_fields = ['title','amounts']
|
||||||
filter_order_by = ['title']
|
|
||||||
|
|
||||||
class RecipeIngredientNode(DjangoObjectType):
|
class RecipeIngredientNode(DjangoObjectType):
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ class RecipeIngredientNode(DjangoObjectType):
|
||||||
'recipe': ['exact'],
|
'recipe': ['exact'],
|
||||||
'recipe__title': ['icontains'],
|
'recipe__title': ['icontains'],
|
||||||
}
|
}
|
||||||
filter_order_by = ['ingredient__name', 'recipe__title',]
|
|
||||||
|
|
||||||
class Query(AbstractType):
|
class Query(AbstractType):
|
||||||
recipe = Node.Field(RecipeNode)
|
recipe = Node.Field(RecipeNode)
|
||||||
|
|
|
@ -4,6 +4,7 @@ from django.db import models
|
||||||
class MissingType(object):
|
class MissingType(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
DurationField = models.DurationField
|
DurationField = models.DurationField
|
||||||
UUIDField = models.UUIDField
|
UUIDField = models.UUIDField
|
||||||
|
@ -21,6 +22,13 @@ except:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Postgres fields are only available in Django 1.8+
|
# Postgres fields are only available in Django 1.8+
|
||||||
from django.contrib.postgres.fields import ArrayField, HStoreField, JSONField, RangeField
|
from django.contrib.postgres.fields import ArrayField, HStoreField, RangeField
|
||||||
except ImportError:
|
except ImportError:
|
||||||
ArrayField, HStoreField, JSONField, RangeField = (MissingType, ) * 4
|
ArrayField, HStoreField, JSONField, RangeField = (MissingType, ) * 4
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Postgres fields are only available in Django 1.9+
|
||||||
|
from django.contrib.postgres.fields import JSONField
|
||||||
|
except ImportError:
|
||||||
|
JSONField = MissingType
|
||||||
|
|
|
@ -4,9 +4,9 @@ from django.utils.encoding import force_text
|
||||||
from graphene import (ID, Boolean, Dynamic, Enum, Field, Float, Int, List,
|
from graphene import (ID, Boolean, Dynamic, Enum, Field, Float, Int, List,
|
||||||
NonNull, String)
|
NonNull, String)
|
||||||
from graphene.relay import is_node
|
from graphene.relay import is_node
|
||||||
from graphene.types.datetime import DateTime
|
from graphene.types.datetime import DateTime, Time
|
||||||
from graphene.types.json import JSONString
|
from graphene.types.json import JSONString
|
||||||
from graphene.utils.str_converters import to_const
|
from graphene.utils.str_converters import to_camel_case, to_const
|
||||||
from graphql import assert_valid_name
|
from graphql import assert_valid_name
|
||||||
|
|
||||||
from .compat import (ArrayField, HStoreField, JSONField, RangeField,
|
from .compat import (ArrayField, HStoreField, JSONField, RangeField,
|
||||||
|
@ -41,7 +41,7 @@ def convert_django_field_with_choices(field, registry=None):
|
||||||
choices = getattr(field, 'choices', None)
|
choices = getattr(field, 'choices', None)
|
||||||
if choices:
|
if choices:
|
||||||
meta = field.model._meta
|
meta = field.model._meta
|
||||||
name = '{}{}'.format(meta.object_name, field.name.capitalize())
|
name = to_camel_case('{}_{}'.format(meta.object_name, field.name))
|
||||||
choices = list(get_choices(choices))
|
choices = list(get_choices(choices))
|
||||||
named_choices = [(c[0], c[1]) for c in choices]
|
named_choices = [(c[0], c[1]) for c in choices]
|
||||||
named_choices_descriptions = {c[0]: c[2] for c in choices}
|
named_choices_descriptions = {c[0]: c[2] for c in choices}
|
||||||
|
@ -112,6 +112,11 @@ def convert_date_to_string(field, registry=None):
|
||||||
return DateTime(description=field.help_text, required=not field.null)
|
return DateTime(description=field.help_text, required=not field.null)
|
||||||
|
|
||||||
|
|
||||||
|
@convert_django_field.register(models.TimeField)
|
||||||
|
def convert_time_to_string(field, registry=None):
|
||||||
|
return Time(description=field.help_text, required=not field.null)
|
||||||
|
|
||||||
|
|
||||||
@convert_django_field.register(models.OneToOneRel)
|
@convert_django_field.register(models.OneToOneRel)
|
||||||
def convert_onetoone_field_to_djangomodel(field, registry=None):
|
def convert_onetoone_field_to_djangomodel(field, registry=None):
|
||||||
model = get_related_model(field)
|
model = get_related_model(field)
|
||||||
|
|
|
@ -8,7 +8,7 @@ if not DJANGO_FILTER_INSTALLED:
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
from .fields import DjangoFilterConnectionField
|
from .fields import DjangoFilterConnectionField
|
||||||
from .filterset import GrapheneFilterSet, GlobalIDFilter, GlobalIDMultipleChoiceFilter
|
from .filterset import GlobalIDFilter, GlobalIDMultipleChoiceFilter
|
||||||
|
|
||||||
__all__ = ['DjangoFilterConnectionField', 'GrapheneFilterSet',
|
__all__ = ['DjangoFilterConnectionField',
|
||||||
'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter']
|
'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter']
|
||||||
|
|
|
@ -13,7 +13,6 @@ class DjangoFilterConnectionField(DjangoConnectionField):
|
||||||
def __init__(self, type, fields=None, order_by=None,
|
def __init__(self, type, fields=None, order_by=None,
|
||||||
extra_filter_meta=None, filterset_class=None,
|
extra_filter_meta=None, filterset_class=None,
|
||||||
*args, **kwargs):
|
*args, **kwargs):
|
||||||
self._order_by = order_by
|
|
||||||
self._fields = fields
|
self._fields = fields
|
||||||
self._type = type
|
self._type = type
|
||||||
self._filterset_class = filterset_class
|
self._filterset_class = filterset_class
|
||||||
|
@ -27,15 +26,10 @@ class DjangoFilterConnectionField(DjangoConnectionField):
|
||||||
return self._type()
|
return self._type()
|
||||||
return self._type
|
return self._type
|
||||||
|
|
||||||
@property
|
|
||||||
def order_by(self):
|
|
||||||
return self._order_by or self.node_type._meta.filter_order_by
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def meta(self):
|
def meta(self):
|
||||||
meta = dict(model=self.node_type._meta.model,
|
meta = dict(model=self.node_type._meta.model,
|
||||||
fields=self.fields,
|
fields=self.fields)
|
||||||
order_by=self.order_by)
|
|
||||||
if self._extra_filter_meta:
|
if self._extra_filter_meta:
|
||||||
meta.update(self._extra_filter_meta)
|
meta.update(self._extra_filter_meta)
|
||||||
return meta
|
return meta
|
||||||
|
@ -64,12 +58,8 @@ class DjangoFilterConnectionField(DjangoConnectionField):
|
||||||
def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args,
|
def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args,
|
||||||
root, args, context, info):
|
root, args, context, info):
|
||||||
filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
|
filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
|
||||||
order = args.get('order_by', None)
|
|
||||||
qs = default_manager.get_queryset()
|
qs = default_manager.get_queryset()
|
||||||
if order:
|
qs = filterset_class(data=filter_kwargs, queryset=qs).qs
|
||||||
qs = qs.order_by(order)
|
|
||||||
qs = filterset_class(data=filter_kwargs, queryset=qs)
|
|
||||||
|
|
||||||
return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info)
|
return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info)
|
||||||
|
|
||||||
def get_resolver(self, parent_resolver):
|
def get_resolver(self, parent_resolver):
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
import six
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django_filters import Filter, MultipleChoiceFilter
|
from django_filters import Filter, MultipleChoiceFilter
|
||||||
from django_filters.filterset import FilterSet, FilterSetMetaclass
|
from django_filters.filterset import BaseFilterSet, FilterSet
|
||||||
from django_filters.filterset import FILTER_FOR_DBFIELD_DEFAULTS
|
from django_filters.filterset import FILTER_FOR_DBFIELD_DEFAULTS
|
||||||
|
|
||||||
from graphql_relay.node.node import from_global_id
|
from graphql_relay.node.node import from_global_id
|
||||||
|
@ -29,9 +27,6 @@ class GlobalIDMultipleChoiceFilter(MultipleChoiceFilter):
|
||||||
return super(GlobalIDMultipleChoiceFilter, self).filter(qs, gids)
|
return super(GlobalIDMultipleChoiceFilter, self).filter(qs, gids)
|
||||||
|
|
||||||
|
|
||||||
ORDER_BY_FIELD = getattr(settings, 'GRAPHENE_ORDER_BY_FIELD', 'order_by')
|
|
||||||
|
|
||||||
|
|
||||||
GRAPHENE_FILTER_SET_OVERRIDES = {
|
GRAPHENE_FILTER_SET_OVERRIDES = {
|
||||||
models.AutoField: {
|
models.AutoField: {
|
||||||
'filter_class': GlobalIDFilter,
|
'filter_class': GlobalIDFilter,
|
||||||
|
@ -48,25 +43,7 @@ GRAPHENE_FILTER_SET_OVERRIDES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Only useful for Django-filter 0.14-, not necessary in latest version 0.15+
|
class GrapheneFilterSetMixin(BaseFilterSet):
|
||||||
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
|
|
||||||
if hasattr(new_class, '_meta') and hasattr(new_class._meta, 'filter_overrides'):
|
|
||||||
filter_overrides = new_class._meta.filter_overrides
|
|
||||||
else:
|
|
||||||
filter_overrides = new_class.filter_overrides
|
|
||||||
|
|
||||||
for k, v in GRAPHENE_FILTER_SET_OVERRIDES.items():
|
|
||||||
filter_overrides.setdefault(k, v)
|
|
||||||
|
|
||||||
return new_class
|
|
||||||
|
|
||||||
|
|
||||||
class GrapheneFilterSetMixin(object):
|
|
||||||
order_by_field = ORDER_BY_FIELD
|
|
||||||
FILTER_DEFAULTS = dict(itertools.chain(
|
FILTER_DEFAULTS = dict(itertools.chain(
|
||||||
FILTER_FOR_DBFIELD_DEFAULTS.items(),
|
FILTER_FOR_DBFIELD_DEFAULTS.items(),
|
||||||
GRAPHENE_FILTER_SET_OVERRIDES.items()
|
GRAPHENE_FILTER_SET_OVERRIDES.items()
|
||||||
|
@ -93,26 +70,17 @@ class GrapheneFilterSetMixin(object):
|
||||||
return GlobalIDFilter(**default)
|
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):
|
def setup_filterset(filterset_class):
|
||||||
""" Wrap a provided filterset in Graphene-specific functionality
|
""" Wrap a provided filterset in Graphene-specific functionality
|
||||||
"""
|
"""
|
||||||
return type(
|
return type(
|
||||||
'Graphene{}'.format(filterset_class.__name__),
|
'Graphene{}'.format(filterset_class.__name__),
|
||||||
(six.with_metaclass(GrapheneFilterSetMetaclass, GrapheneFilterSetMixin, filterset_class),),
|
(filterset_class, GrapheneFilterSetMixin),
|
||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def custom_filterset_factory(model, filterset_base_class=GrapheneFilterSet,
|
def custom_filterset_factory(model, filterset_base_class=FilterSet,
|
||||||
**meta):
|
**meta):
|
||||||
""" Create a filterset for the given model using the provided meta data
|
""" Create a filterset for the given model using the provided meta data
|
||||||
"""
|
"""
|
||||||
|
@ -122,7 +90,7 @@ def custom_filterset_factory(model, filterset_base_class=GrapheneFilterSet,
|
||||||
meta_class = type(str('Meta'), (object,), meta)
|
meta_class = type(str('Meta'), (object,), meta)
|
||||||
filterset = type(
|
filterset = type(
|
||||||
str('%sFilterSet' % model._meta.object_name),
|
str('%sFilterSet' % model._meta.object_name),
|
||||||
(filterset_base_class,),
|
(filterset_base_class, GrapheneFilterSetMixin),
|
||||||
{
|
{
|
||||||
'Meta': meta_class
|
'Meta': meta_class
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import django_filters
|
import django_filters
|
||||||
|
from django_filters import OrderingFilter
|
||||||
|
|
||||||
from graphene_django.tests.models import Article, Pet, Reporter
|
from graphene_django.tests.models import Article, Pet, Reporter
|
||||||
|
|
||||||
|
@ -12,7 +13,8 @@ class ArticleFilter(django_filters.FilterSet):
|
||||||
'pub_date': ['gt', 'lt', 'exact'],
|
'pub_date': ['gt', 'lt', 'exact'],
|
||||||
'reporter': ['exact'],
|
'reporter': ['exact'],
|
||||||
}
|
}
|
||||||
order_by = False
|
|
||||||
|
order_by = OrderingFilter(fields=('pub_date',))
|
||||||
|
|
||||||
|
|
||||||
class ReporterFilter(django_filters.FilterSet):
|
class ReporterFilter(django_filters.FilterSet):
|
||||||
|
@ -20,7 +22,8 @@ class ReporterFilter(django_filters.FilterSet):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Reporter
|
model = Reporter
|
||||||
fields = ['first_name', 'last_name', 'email', 'pets']
|
fields = ['first_name', 'last_name', 'email', 'pets']
|
||||||
order_by = True
|
|
||||||
|
order_by = OrderingFilter(fields=('pub_date',))
|
||||||
|
|
||||||
|
|
||||||
class PetFilter(django_filters.FilterSet):
|
class PetFilter(django_filters.FilterSet):
|
||||||
|
@ -28,4 +31,3 @@ class PetFilter(django_filters.FilterSet):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Pet
|
model = Pet
|
||||||
fields = ['name']
|
fields = ['name']
|
||||||
order_by = False
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ from graphene_django.tests.models import Article, Pet, Reporter
|
||||||
from graphene_django.utils import DJANGO_FILTER_INSTALLED
|
from graphene_django.utils import DJANGO_FILTER_INSTALLED
|
||||||
|
|
||||||
pytestmark = []
|
pytestmark = []
|
||||||
|
|
||||||
if DJANGO_FILTER_INSTALLED:
|
if DJANGO_FILTER_INSTALLED:
|
||||||
import django_filters
|
import django_filters
|
||||||
from graphene_django.filter import (GlobalIDFilter, DjangoFilterConnectionField,
|
from graphene_django.filter import (GlobalIDFilter, DjangoFilterConnectionField,
|
||||||
|
@ -22,27 +23,29 @@ else:
|
||||||
pytestmark.append(pytest.mark.django_db)
|
pytestmark.append(pytest.mark.django_db)
|
||||||
|
|
||||||
|
|
||||||
class ArticleNode(DjangoObjectType):
|
if DJANGO_FILTER_INSTALLED:
|
||||||
|
class ArticleNode(DjangoObjectType):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Article
|
model = Article
|
||||||
interfaces = (Node, )
|
interfaces = (Node, )
|
||||||
|
filter_fields = ('headline', )
|
||||||
|
|
||||||
|
|
||||||
class ReporterNode(DjangoObjectType):
|
class ReporterNode(DjangoObjectType):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Reporter
|
model = Reporter
|
||||||
interfaces = (Node, )
|
interfaces = (Node, )
|
||||||
|
|
||||||
|
|
||||||
class PetNode(DjangoObjectType):
|
class PetNode(DjangoObjectType):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Pet
|
model = Pet
|
||||||
interfaces = (Node, )
|
interfaces = (Node, )
|
||||||
|
|
||||||
# schema = Schema()
|
# schema = Schema()
|
||||||
|
|
||||||
|
|
||||||
def get_args(field):
|
def get_args(field):
|
||||||
|
@ -110,8 +113,8 @@ def test_filter_explicit_filterset_orderable():
|
||||||
|
|
||||||
|
|
||||||
def test_filter_shortcut_filterset_orderable_true():
|
def test_filter_shortcut_filterset_orderable_true():
|
||||||
field = DjangoFilterConnectionField(ReporterNode, order_by=True)
|
field = DjangoFilterConnectionField(ReporterNode)
|
||||||
assert_orderable(field)
|
assert_not_orderable(field)
|
||||||
|
|
||||||
|
|
||||||
# def test_filter_shortcut_filterset_orderable_headline():
|
# def test_filter_shortcut_filterset_orderable_headline():
|
||||||
|
@ -126,9 +129,9 @@ def test_filter_explicit_filterset_not_orderable():
|
||||||
|
|
||||||
def test_filter_shortcut_filterset_extra_meta():
|
def test_filter_shortcut_filterset_extra_meta():
|
||||||
field = DjangoFilterConnectionField(ArticleNode, extra_filter_meta={
|
field = DjangoFilterConnectionField(ArticleNode, extra_filter_meta={
|
||||||
'order_by': True
|
'exclude': ('headline', )
|
||||||
})
|
})
|
||||||
assert_orderable(field)
|
assert 'headline' not in field.filterset_class.get_fields()
|
||||||
|
|
||||||
|
|
||||||
def test_filter_filterset_information_on_meta():
|
def test_filter_filterset_information_on_meta():
|
||||||
|
@ -138,11 +141,10 @@ def test_filter_filterset_information_on_meta():
|
||||||
model = Reporter
|
model = Reporter
|
||||||
interfaces = (Node, )
|
interfaces = (Node, )
|
||||||
filter_fields = ['first_name', 'articles']
|
filter_fields = ['first_name', 'articles']
|
||||||
filter_order_by = True
|
|
||||||
|
|
||||||
field = DjangoFilterConnectionField(ReporterFilterNode)
|
field = DjangoFilterConnectionField(ReporterFilterNode)
|
||||||
assert_arguments(field, 'first_name', 'articles')
|
assert_arguments(field, 'first_name', 'articles')
|
||||||
assert_orderable(field)
|
assert_not_orderable(field)
|
||||||
|
|
||||||
|
|
||||||
def test_filter_filterset_information_on_meta_related():
|
def test_filter_filterset_information_on_meta_related():
|
||||||
|
@ -152,7 +154,6 @@ def test_filter_filterset_information_on_meta_related():
|
||||||
model = Reporter
|
model = Reporter
|
||||||
interfaces = (Node, )
|
interfaces = (Node, )
|
||||||
filter_fields = ['first_name', 'articles']
|
filter_fields = ['first_name', 'articles']
|
||||||
filter_order_by = True
|
|
||||||
|
|
||||||
class ArticleFilterNode(DjangoObjectType):
|
class ArticleFilterNode(DjangoObjectType):
|
||||||
|
|
||||||
|
@ -160,7 +161,6 @@ def test_filter_filterset_information_on_meta_related():
|
||||||
model = Article
|
model = Article
|
||||||
interfaces = (Node, )
|
interfaces = (Node, )
|
||||||
filter_fields = ['headline', 'reporter']
|
filter_fields = ['headline', 'reporter']
|
||||||
filter_order_by = True
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
|
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
|
||||||
|
@ -171,7 +171,7 @@ def test_filter_filterset_information_on_meta_related():
|
||||||
schema = Schema(query=Query)
|
schema = Schema(query=Query)
|
||||||
articles_field = ReporterFilterNode._meta.fields['articles'].get_type()
|
articles_field = ReporterFilterNode._meta.fields['articles'].get_type()
|
||||||
assert_arguments(articles_field, 'headline', 'reporter')
|
assert_arguments(articles_field, 'headline', 'reporter')
|
||||||
assert_orderable(articles_field)
|
assert_not_orderable(articles_field)
|
||||||
|
|
||||||
|
|
||||||
def test_filter_filterset_related_results():
|
def test_filter_filterset_related_results():
|
||||||
|
@ -181,7 +181,6 @@ def test_filter_filterset_related_results():
|
||||||
model = Reporter
|
model = Reporter
|
||||||
interfaces = (Node, )
|
interfaces = (Node, )
|
||||||
filter_fields = ['first_name', 'articles']
|
filter_fields = ['first_name', 'articles']
|
||||||
filter_order_by = True
|
|
||||||
|
|
||||||
class ArticleFilterNode(DjangoObjectType):
|
class ArticleFilterNode(DjangoObjectType):
|
||||||
|
|
||||||
|
@ -189,7 +188,6 @@ def test_filter_filterset_related_results():
|
||||||
interfaces = (Node, )
|
interfaces = (Node, )
|
||||||
model = Article
|
model = Article
|
||||||
filter_fields = ['headline', 'reporter']
|
filter_fields = ['headline', 'reporter']
|
||||||
filter_order_by = True
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
|
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from graphene import String
|
|
||||||
|
|
||||||
from .filterset import custom_filterset_factory, setup_filterset
|
from .filterset import custom_filterset_factory, setup_filterset
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,10 +16,6 @@ def get_filtering_args_from_filterset(filterset_class, type):
|
||||||
field_type.description = filter_field.label
|
field_type.description = filter_field.label
|
||||||
args[name] = field_type
|
args[name] = field_type
|
||||||
|
|
||||||
# Also add the 'order_by' field
|
|
||||||
if filterset_class._meta.order_by:
|
|
||||||
args[filterset_class.order_by_field] = String()
|
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ from py.test import raises
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
from graphene.relay import ConnectionField, Node
|
from graphene.relay import ConnectionField, Node
|
||||||
from graphene.types.datetime import DateTime
|
from graphene.types.datetime import DateTime, Time
|
||||||
from graphene.types.json import JSONString
|
from graphene.types.json import JSONString
|
||||||
|
|
||||||
from ..compat import (ArrayField, HStoreField, JSONField, MissingType,
|
from ..compat import (ArrayField, HStoreField, JSONField, MissingType,
|
||||||
|
@ -16,7 +16,7 @@ from ..types import DjangoObjectType
|
||||||
from .models import Article, Film, FilmDetails, Reporter
|
from .models import Article, Film, FilmDetails, Reporter
|
||||||
|
|
||||||
|
|
||||||
# from graphene.core.types.custom_scalars import DateTime, JSONString
|
# from graphene.core.types.custom_scalars import DateTime, Time, JSONString
|
||||||
|
|
||||||
|
|
||||||
def assert_conversion(django_field, graphene_field, *args, **kwargs):
|
def assert_conversion(django_field, graphene_field, *args, **kwargs):
|
||||||
|
@ -44,6 +44,10 @@ def test_should_date_convert_string():
|
||||||
assert_conversion(models.DateField, DateTime)
|
assert_conversion(models.DateField, DateTime)
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_time_convert_string():
|
||||||
|
assert_conversion(models.TimeField, Time)
|
||||||
|
|
||||||
|
|
||||||
def test_should_char_convert_string():
|
def test_should_char_convert_string():
|
||||||
assert_conversion(models.CharField, graphene.String)
|
assert_conversion(models.CharField, graphene.String)
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ type Article implements Node {
|
||||||
|
|
||||||
type ArticleConnection {
|
type ArticleConnection {
|
||||||
pageInfo: PageInfo!
|
pageInfo: PageInfo!
|
||||||
edges: [ArticleEdge]
|
edges: [ArticleEdge]!
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArticleEdge {
|
type ArticleEdge {
|
||||||
|
@ -110,11 +110,11 @@ type Reporter {
|
||||||
lastName: String!
|
lastName: String!
|
||||||
email: String!
|
email: String!
|
||||||
pets: [Reporter]
|
pets: [Reporter]
|
||||||
aChoice: ReporterA_choice!
|
aChoice: ReporterAChoice!
|
||||||
articles(before: String, after: String, first: Int, last: Int): ArticleConnection
|
articles(before: String, after: String, first: Int, last: Int): ArticleConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ReporterA_choice {
|
enum ReporterAChoice {
|
||||||
A_1
|
A_1
|
||||||
A_2
|
A_2
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,6 @@ class DjangoObjectTypeMeta(ObjectTypeMeta):
|
||||||
# we allow more attributes in Meta
|
# we allow more attributes in Meta
|
||||||
defaults.update(
|
defaults.update(
|
||||||
filter_fields=(),
|
filter_fields=(),
|
||||||
filter_order_by=(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
options = Options(
|
options = Options(
|
||||||
|
|
6
setup.py
6
setup.py
|
@ -2,7 +2,7 @@ from setuptools import find_packages, setup
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='graphene-django',
|
name='graphene-django',
|
||||||
version='1.0',
|
version='1.2.1',
|
||||||
|
|
||||||
description='Graphene Django integration',
|
description='Graphene Django integration',
|
||||||
long_description=open('README.rst').read(),
|
long_description=open('README.rst').read(),
|
||||||
|
@ -33,7 +33,7 @@ setup(
|
||||||
|
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'six>=1.10.0',
|
'six>=1.10.0',
|
||||||
'graphene>=1.0',
|
'graphene>=1.1.3',
|
||||||
'Django>=1.6.0',
|
'Django>=1.6.0',
|
||||||
'iso8601',
|
'iso8601',
|
||||||
'singledispatch>=3.4.0.3',
|
'singledispatch>=3.4.0.3',
|
||||||
|
@ -42,7 +42,7 @@ setup(
|
||||||
'pytest-runner',
|
'pytest-runner',
|
||||||
],
|
],
|
||||||
tests_require=[
|
tests_require=[
|
||||||
'django-filter>=0.10.0',
|
'django-filter>=1.0.0',
|
||||||
'pytest',
|
'pytest',
|
||||||
'pytest-django==2.9.1',
|
'pytest-django==2.9.1',
|
||||||
'mock',
|
'mock',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user