From dc0c2900d159fd096b187902c5aea8bbb5716d0f Mon Sep 17 00:00:00 2001 From: Jay Hale Date: Sun, 5 Aug 2018 10:26:14 -0400 Subject: [PATCH 1/5] Making GrapheneFilterSetMixin compatible with django_filter 2 --- docs/filtering.rst | 14 +++++------- examples/cookbook/requirements.txt | 2 +- graphene_django/filter/filterset.py | 35 +++++++++-------------------- setup.py | 4 ++-- 4 files changed, 20 insertions(+), 35 deletions(-) diff --git a/docs/filtering.rst b/docs/filtering.rst index cfecf2c..feafd40 100644 --- a/docs/filtering.rst +++ b/docs/filtering.rst @@ -2,11 +2,9 @@ Filtering ========= Graphene integrates with -`django-filter `__ (< 2.0.0) to provide -filtering of results (this also means filtering is only compatible with Django < 2.0). - -See the `usage -documentation `__ +`django-filter `__ (2.x for +Python 3 or 1.x for Python 2) to provide filtering of results. See the `usage +documentation `__ for details on the format for ``filter_fields``. This filtering is automatically available when implementing a ``relay.Node``. @@ -17,7 +15,7 @@ You will need to install it manually, which can be done as follows: .. code:: bash # You'll need to django-filter - pip install django-filter==1.1.0 + pip install django-filter>=2 Note: The techniques below are demoed in the `cookbook example app `__. @@ -28,7 +26,7 @@ Filterable fields The ``filter_fields`` parameter is used to specify the fields which can be filtered upon. The value specified here is passed directly to ``django-filter``, so see the `filtering -documentation `__ +documentation `__ for full details on the range of options available. For example: @@ -129,7 +127,7 @@ create your own ``Filterset`` as follows: all_animals = DjangoFilterConnectionField(AnimalNode, filterset_class=AnimalFilter) -The context argument is passed on as the `request argument `__ +The context argument is passed on as the `request argument `__ in a ``django_filters.FilterSet`` instance. You can use this to customize your filters to be context-dependent. We could modify the ``AnimalFilter`` above to pre-filter animals owned by the authenticated user (set in ``context.user``). diff --git a/examples/cookbook/requirements.txt b/examples/cookbook/requirements.txt index 1900a39..b2ace1f 100644 --- a/examples/cookbook/requirements.txt +++ b/examples/cookbook/requirements.txt @@ -2,4 +2,4 @@ graphene graphene-django graphql-core>=2.1rc1 django==1.9 -django-filter<2 +django-filter>=2 diff --git a/graphene_django/filter/filterset.py b/graphene_django/filter/filterset.py index 29a275d..8789147 100644 --- a/graphene_django/filter/filterset.py +++ b/graphene_django/filter/filterset.py @@ -1,7 +1,6 @@ import itertools from django.db import models -from django.utils.text import capfirst from django_filters import Filter, MultipleChoiceFilter from django_filters.filterset import BaseFilterSet, FilterSet from django_filters.filterset import FILTER_FOR_DBFIELD_DEFAULTS @@ -15,7 +14,10 @@ class GlobalIDFilter(Filter): field_class = GlobalIDFormField def filter(self, qs, value): - _type, _id = from_global_id(value) + """ Convert the filter value to a primary key before filtering """ + _id = None + if value is not None: + _, _id = from_global_id(value) return super(GlobalIDFilter, self).filter(qs, _id) @@ -32,37 +34,22 @@ GRAPHENE_FILTER_SET_OVERRIDES = { models.OneToOneField: {"filter_class": GlobalIDFilter}, models.ForeignKey: {"filter_class": GlobalIDFilter}, models.ManyToManyField: {"filter_class": GlobalIDMultipleChoiceFilter}, + models.ManyToOneRel: {"filter_class": GlobalIDMultipleChoiceFilter}, + models.ManyToManyRel: {"filter_class": GlobalIDMultipleChoiceFilter}, } class GrapheneFilterSetMixin(BaseFilterSet): + """ A django_filters.filterset.BaseFilterSet with default filter overrides + to handle global IDs """ + FILTER_DEFAULTS = dict( itertools.chain( - FILTER_FOR_DBFIELD_DEFAULTS.items(), GRAPHENE_FILTER_SET_OVERRIDES.items() + FILTER_FOR_DBFIELD_DEFAULTS.items(), + GRAPHENE_FILTER_SET_OVERRIDES.items() ) ) - @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) - """ - try: - rel = f.field.remote_field - except AttributeError: - 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) - def setup_filterset(filterset_class): """ Wrap a provided filterset in Graphene-specific functionality diff --git a/setup.py b/setup.py index 2e1f463..933b6b5 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ tests_require = [ "coveralls", "mock", "pytz", - "django-filter<2", + "django-filter>=2", "pytest-django>=3.3.2", ] + rest_framework_require @@ -50,7 +50,7 @@ setup( "six>=1.10.0", "graphene>=2.1,<3", "graphql-core>=2.1rc1", - "Django>=1.8.0", + "Django>=1.11", "iso8601", "singledispatch>=3.4.0.3", "promise>=2.1", From 9152075ed85bb85e53601da6344d43bde9afdc8f Mon Sep 17 00:00:00 2001 From: Jay Hale Date: Tue, 7 Aug 2018 13:01:53 -0400 Subject: [PATCH 2/5] Remove unsupported python & Django test cases --- .travis.yml | 15 +++++---------- setup.py | 5 +++-- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 348260b..4bbbba0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,6 @@ install: pip install -e .[test] pip install psycopg2 # Required for Django postgres fields testing pip install django==$DJANGO_VERSION - if (($(echo "$DJANGO_VERSION <= 1.9" | bc -l))); then # DRF dropped 1.8 and 1.9 support at 3.7.0 - pip install djangorestframework==3.6.4 - fi python setup.py develop elif [ "$TEST_TYPE" = lint ]; then pip install flake8 @@ -44,13 +41,11 @@ matrix: env: TEST_TYPE=build DJANGO_VERSION=2.0 - python: '3.6' env: TEST_TYPE=build DJANGO_VERSION=2.0 - - python: '2.7' - env: TEST_TYPE=build DJANGO_VERSION=1.8 - - python: '2.7' - env: TEST_TYPE=build DJANGO_VERSION=1.9 - - python: '2.7' - env: TEST_TYPE=build DJANGO_VERSION=1.10 - - python: '2.7' + - python: '3.5' + env: TEST_TYPE=build DJANGO_VERSION=2.1 + - python: '3.6' + env: TEST_TYPE=build DJANGO_VERSION=2.1 + - python: '3.6' env: TEST_TYPE=lint deploy: provider: pypi diff --git a/setup.py b/setup.py index 933b6b5..5529afa 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,8 @@ tests_require = [ "coveralls", "mock", "pytz", - "django-filter>=2", + "django-filter<2;python_version<'3'", + "django-filter>=2;python_version>='3'", "pytest-django>=3.3.2", ] + rest_framework_require @@ -39,9 +40,9 @@ setup( "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 :: 3.6", "Programming Language :: Python :: Implementation :: PyPy", ], keywords="api graphql protocol rest relay graphene", From d8bdda94dfcf60cf5cf560c32e18305f56d4ddf8 Mon Sep 17 00:00:00 2001 From: Jay Hale Date: Thu, 16 Aug 2018 14:05:34 -0400 Subject: [PATCH 3/5] Add back support for django-filter < 2 --- .travis.yml | 2 ++ graphene_django/filter/filterset.py | 33 ++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4bbbba0..a8375ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,6 +45,8 @@ matrix: env: TEST_TYPE=build DJANGO_VERSION=2.1 - python: '3.6' env: TEST_TYPE=build DJANGO_VERSION=2.1 + - python: '2.7' + env: TEST_TYPE=lint - python: '3.6' env: TEST_TYPE=lint deploy: diff --git a/graphene_django/filter/filterset.py b/graphene_django/filter/filterset.py index 8789147..4059083 100644 --- a/graphene_django/filter/filterset.py +++ b/graphene_django/filter/filterset.py @@ -1,7 +1,7 @@ import itertools from django.db import models -from django_filters import Filter, MultipleChoiceFilter +from django_filters import Filter, MultipleChoiceFilter, VERSION from django_filters.filterset import BaseFilterSet, FilterSet from django_filters.filterset import FILTER_FOR_DBFIELD_DEFAULTS @@ -51,6 +51,37 @@ class GrapheneFilterSetMixin(BaseFilterSet): ) +# To support a Django 1.11 + Python 2.7 combination django-filter must be +# < 2.x.x. To support the earlier version of django-filter, the +# filter_for_reverse_field method must be present on GrapheneFilterSetMixin and +# must not be present for later versions of django-filter. +if VERSION[0] < 2: + from django.utils.text import capfirst + + class GrapheneFilterSetMixinPython2(GrapheneFilterSetMixin): + + @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) + """ + try: + rel = f.field.remote_field + except AttributeError: + 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) + + GrapheneFilterSetMixin = GrapheneFilterSetMixinPython2 + + def setup_filterset(filterset_class): """ Wrap a provided filterset in Graphene-specific functionality """ From f8dff38e29edc644ca27eadabb5afe10757be623 Mon Sep 17 00:00:00 2001 From: Jay Hale Date: Thu, 16 Aug 2018 14:27:30 -0400 Subject: [PATCH 4/5] Remove unnecessary compat utility for Django < 1.11 --- graphene_django/compat.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/graphene_django/compat.py b/graphene_django/compat.py index f43db04..4a51de8 100644 --- a/graphene_django/compat.py +++ b/graphene_django/compat.py @@ -5,13 +5,7 @@ class MissingType(object): try: # Postgres fields are only available in Django with psycopg2 installed # and we cannot have psycopg2 on PyPy - from django.contrib.postgres.fields import ArrayField, HStoreField, RangeField + from django.contrib.postgres.fields import (ArrayField, HStoreField, + JSONField, RangeField) except ImportError: 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 From 0314931f120c01a74f46573689c0c413412bdbbb Mon Sep 17 00:00:00 2001 From: Jay Hale Date: Thu, 16 Aug 2018 14:32:10 -0400 Subject: [PATCH 5/5] Removed Django < 1.11 compatibility checks from tests --- graphene_django/tests/test_converter.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/graphene_django/tests/test_converter.py b/graphene_django/tests/test_converter.py index 196f008..5dc0184 100644 --- a/graphene_django/tests/test_converter.py +++ b/graphene_django/tests/test_converter.py @@ -237,16 +237,12 @@ def test_should_manytomany_convert_connectionorlist_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" - ) - class A(DjangoObjectType): class Meta: model = Article - graphene_field = convert_django_field(related, A._meta.registry) + graphene_field = convert_django_field(Reporter.articles.rel, + A._meta.registry) assert isinstance(graphene_field, graphene.Dynamic) dynamic_field = graphene_field.get_type() assert isinstance(dynamic_field, graphene.Field) @@ -255,14 +251,12 @@ def test_should_manytoone_convert_connectionorlist(): 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 - graphene_field = convert_django_field(related, A._meta.registry) + graphene_field = convert_django_field(Film.details.related, + A._meta.registry) assert isinstance(graphene_field, graphene.Dynamic) dynamic_field = graphene_field.get_type() assert isinstance(dynamic_field, graphene.Field)