From 73706fa6bbe692642db28a622d28db7894506072 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 5 Dec 2017 15:01:52 -0500 Subject: [PATCH 01/42] Add on_delete atrributes to test models foreignkeys --- examples/starwars/models.py | 6 +++--- graphene_django/tests/models.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/starwars/models.py b/examples/starwars/models.py index 2f80e27..45741da 100644 --- a/examples/starwars/models.py +++ b/examples/starwars/models.py @@ -5,7 +5,7 @@ from django.db import models class Character(models.Model): name = models.CharField(max_length=50) - ship = models.ForeignKey('Ship', blank=True, null=True, related_name='characters') + ship = models.ForeignKey('Ship', on_delete=models.CASCADE, blank=True, null=True, related_name='characters') def __str__(self): return self.name @@ -13,7 +13,7 @@ class Character(models.Model): class Faction(models.Model): name = models.CharField(max_length=50) - hero = models.ForeignKey(Character) + hero = models.ForeignKey(Character, on_delete=models.CASCADE) def __str__(self): return self.name @@ -21,7 +21,7 @@ class Faction(models.Model): class Ship(models.Model): name = models.CharField(max_length=50) - faction = models.ForeignKey(Faction, related_name='ships') + faction = models.ForeignKey(Faction, on_delete=models.CASCADE, related_name='ships') def __str__(self): return self.name diff --git a/graphene_django/tests/models.py b/graphene_django/tests/models.py index 0c62f28..db1fca4 100644 --- a/graphene_django/tests/models.py +++ b/graphene_django/tests/models.py @@ -15,7 +15,7 @@ class Pet(models.Model): class FilmDetails(models.Model): location = models.CharField(max_length=30) - film = models.OneToOneField('Film', related_name='details') + film = models.OneToOneField('Film', on_delete=models.CASCADE, related_name='details') class Film(models.Model): @@ -37,8 +37,8 @@ class Reporter(models.Model): class Article(models.Model): headline = models.CharField(max_length=100) pub_date = models.DateField() - reporter = models.ForeignKey(Reporter, related_name='articles') - editor = models.ForeignKey(Reporter, related_name='edited_articles_+') + reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE, related_name='articles') + editor = models.ForeignKey(Reporter, on_delete=models.CASCADE, related_name='edited_articles_+') lang = models.CharField(max_length=2, help_text='Language', choices=[ ('es', 'Spanish'), ('en', 'English') From 62c0694901cb0e4f711750bebb2901d509088cc0 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 5 Dec 2017 15:04:29 -0500 Subject: [PATCH 02/42] models.DateField corresponds to graphene Date Scalar --- graphene_django/converter.py | 9 +++++++-- graphene_django/filter/tests/test_fields.py | 10 ++++++---- graphene_django/rest_framework/serializer_converter.py | 6 +++++- .../rest_framework/tests/test_field_converter.py | 4 ++-- graphene_django/tests/models.py | 1 + graphene_django/tests/test_converter.py | 7 +++++-- graphene_django/tests/test_form_converter.py | 2 ++ graphene_django/tests/test_query.py | 7 +++++++ graphene_django/tests/test_types.py | 7 +++++-- 9 files changed, 40 insertions(+), 13 deletions(-) diff --git a/graphene_django/converter.py b/graphene_django/converter.py index dff77a8..fa771e2 100644 --- a/graphene_django/converter.py +++ b/graphene_django/converter.py @@ -3,7 +3,7 @@ from django.utils.encoding import force_text from graphene import (ID, Boolean, Dynamic, Enum, Field, Float, Int, List, NonNull, String, UUID) -from graphene.types.datetime import DateTime, Time +from graphene.types.datetime import DateTime, Date, Time from graphene.types.json import JSONString from graphene.utils.str_converters import to_camel_case, to_const from graphql import assert_valid_name @@ -121,11 +121,16 @@ def convert_field_to_float(field, registry=None): return Float(description=field.help_text, required=not field.null) -@convert_django_field.register(models.DateField) +@convert_django_field.register(models.DateTimeField) def convert_date_to_string(field, registry=None): return DateTime(description=field.help_text, required=not field.null) +@convert_django_field.register(models.DateField) +def convert_date_to_string(field, registry=None): + return Date(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) diff --git a/graphene_django/filter/tests/test_fields.py b/graphene_django/filter/tests/test_fields.py index 258da3e..c730ef3 100644 --- a/graphene_django/filter/tests/test_fields.py +++ b/graphene_django/filter/tests/test_fields.py @@ -157,8 +157,8 @@ def test_filter_shortcut_filterset_context(): 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, editor=r1) - Article.objects.create(headline='a2', pub_date=datetime.now(), reporter=r2, editor=r2) + Article.objects.create(headline='a1', pub_date=datetime.now(), pub_date_time=datetime.now(), reporter=r1, editor=r1) + Article.objects.create(headline='a2', pub_date=datetime.now(), pub_date_time=datetime.now(), reporter=r2, editor=r2) class context(object): reporter = r2 @@ -245,8 +245,8 @@ def test_filter_filterset_related_results(): 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) + Article.objects.create(headline='a1', pub_date=datetime.now(), pub_date_time=datetime.now(), reporter=r1) + Article.objects.create(headline='a2', pub_date=datetime.now(), pub_date_time=datetime.now(), reporter=r2) query = ''' query { @@ -464,6 +464,7 @@ def test_should_query_filter_node_limit(): Article.objects.create( headline='Article Node 1', pub_date=datetime.now(), + pub_date_time=datetime.now(), reporter=r, editor=r, lang='es' @@ -471,6 +472,7 @@ def test_should_query_filter_node_limit(): Article.objects.create( headline='Article Node 2', pub_date=datetime.now(), + pub_date_time=datetime.now(), reporter=r, editor=r, lang='en' diff --git a/graphene_django/rest_framework/serializer_converter.py b/graphene_django/rest_framework/serializer_converter.py index 6a57f5f..0c10a65 100644 --- a/graphene_django/rest_framework/serializer_converter.py +++ b/graphene_django/rest_framework/serializer_converter.py @@ -92,11 +92,15 @@ def convert_serializer_field_to_float(field): @get_graphene_type_from_serializer_field.register(serializers.DateTimeField) -@get_graphene_type_from_serializer_field.register(serializers.DateField) def convert_serializer_field_to_date_time(field): return graphene.types.datetime.DateTime +@get_graphene_type_from_serializer_field.register(serializers.DateField) +def convert_serializer_field_to_date_time(field): + return graphene.types.datetime.Date + + @get_graphene_type_from_serializer_field.register(serializers.TimeField) def convert_serializer_field_to_time(field): return graphene.types.datetime.Time diff --git a/graphene_django/rest_framework/tests/test_field_converter.py b/graphene_django/rest_framework/tests/test_field_converter.py index 623cf58..ec851c2 100644 --- a/graphene_django/rest_framework/tests/test_field_converter.py +++ b/graphene_django/rest_framework/tests/test_field_converter.py @@ -87,8 +87,8 @@ def test_should_date_time_convert_datetime(): assert_conversion(serializers.DateTimeField, graphene.types.datetime.DateTime) -def test_should_date_convert_datetime(): - assert_conversion(serializers.DateField, graphene.types.datetime.DateTime) +def test_should_date_convert_date(): + assert_conversion(serializers.DateField, graphene.types.datetime.Date) def test_should_time_convert_time(): diff --git a/graphene_django/tests/models.py b/graphene_django/tests/models.py index db1fca4..56fcecf 100644 --- a/graphene_django/tests/models.py +++ b/graphene_django/tests/models.py @@ -37,6 +37,7 @@ class Reporter(models.Model): class Article(models.Model): headline = models.CharField(max_length=100) pub_date = models.DateField() + pub_date_time = models.DateTimeField() reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE, related_name='articles') editor = models.ForeignKey(Reporter, on_delete=models.CASCADE, related_name='edited_articles_+') lang = models.CharField(max_length=2, help_text='Language', choices=[ diff --git a/graphene_django/tests/test_converter.py b/graphene_django/tests/test_converter.py index d616106..3dd2963 100644 --- a/graphene_django/tests/test_converter.py +++ b/graphene_django/tests/test_converter.py @@ -5,7 +5,7 @@ from py.test import raises import graphene from graphene.relay import ConnectionField, Node -from graphene.types.datetime import DateTime, Time +from graphene.types.datetime import DateTime, Date, Time from graphene.types.json import JSONString from ..compat import JSONField, ArrayField, HStoreField, RangeField, MissingType @@ -38,9 +38,12 @@ def test_should_unknown_django_field_raise_exception(): convert_django_field(None) assert 'Don\'t know how to convert the Django field' in str(excinfo.value) +def test_should_date_time_convert_string(): + assert_conversion(models.DateTimeField, DateTime) + def test_should_date_convert_string(): - assert_conversion(models.DateField, DateTime) + assert_conversion(models.DateField, Date) def test_should_time_convert_string(): diff --git a/graphene_django/tests/test_form_converter.py b/graphene_django/tests/test_form_converter.py index 5a13554..97932d8 100644 --- a/graphene_django/tests/test_form_converter.py +++ b/graphene_django/tests/test_form_converter.py @@ -30,6 +30,8 @@ def test_should_date_convert_string(): def test_should_time_convert_string(): assert_conversion(forms.TimeField, graphene.String) +def test_should_date_convert_string(): + assert_conversion(forms.DateField, graphene.String) def test_should_date_time_convert_string(): assert_conversion(forms.DateTimeField, graphene.String) diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py index 0dece3f..96e0f32 100644 --- a/graphene_django/tests/test_query.py +++ b/graphene_django/tests/test_query.py @@ -371,6 +371,7 @@ def test_should_query_node_filtering(): Article.objects.create( headline='Article Node 1', pub_date=datetime.date.today(), + pub_date_time=datetime.datetime.now(), reporter=r, editor=r, lang='es' @@ -378,6 +379,7 @@ def test_should_query_node_filtering(): Article.objects.create( headline='Article Node 2', pub_date=datetime.date.today(), + pub_date_time=datetime.datetime.now(), reporter=r, editor=r, lang='en' @@ -453,6 +455,7 @@ def test_should_query_node_multiple_filtering(): Article.objects.create( headline='Article Node 1', pub_date=datetime.date.today(), + pub_date_time=datetime.datetime.now(), reporter=r, editor=r, lang='es' @@ -460,6 +463,7 @@ def test_should_query_node_multiple_filtering(): Article.objects.create( headline='Article Node 2', pub_date=datetime.date.today(), + pub_date_time=datetime.datetime.now(), reporter=r, editor=r, lang='es' @@ -467,6 +471,7 @@ def test_should_query_node_multiple_filtering(): Article.objects.create( headline='Article Node 3', pub_date=datetime.date.today(), + pub_date_time=datetime.datetime.now(), reporter=r, editor=r, lang='en' @@ -692,6 +697,7 @@ def test_should_query_dataloader_fields(): Article.objects.create( headline='Article Node 1', pub_date=datetime.date.today(), + pub_date_time=datetime.datetime.now(), reporter=r, editor=r, lang='es' @@ -699,6 +705,7 @@ def test_should_query_dataloader_fields(): Article.objects.create( headline='Article Node 2', pub_date=datetime.date.today(), + pub_date_time=datetime.datetime.now(), reporter=r, editor=r, lang='en' diff --git a/graphene_django/tests/test_types.py b/graphene_django/tests/test_types.py index 83d9b40..0e0679b 100644 --- a/graphene_django/tests/test_types.py +++ b/graphene_django/tests/test_types.py @@ -64,7 +64,7 @@ def test_django_objecttype_map_correct_fields(): def test_django_objecttype_with_node_have_correct_fields(): fields = Article._meta.fields - assert list(fields.keys()) == ['id', 'headline', 'pub_date', 'reporter', 'editor', 'lang', 'importance'] + assert list(fields.keys()) == ['id', 'headline', 'pub_date', 'pub_date_time', 'reporter', 'editor', 'lang', 'importance'] def test_schema_representation(): @@ -76,7 +76,8 @@ schema { type Article implements Node { id: ID! headline: String! - pubDate: DateTime! + pubDate: Date! + pubDateTime: DateTime! reporter: Reporter! editor: Reporter! lang: ArticleLang! @@ -104,6 +105,8 @@ enum ArticleLang { EN } +scalar Date + scalar DateTime interface Node { From f8a5860f3474b9d5839f7220c56698b83abb3550 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 6 Dec 2017 15:10:50 -0500 Subject: [PATCH 03/42] django version depends on python version --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index beee83c..2e835d1 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ tests_require = [ 'pytest-django==2.9.1', ] + rest_framework_require +django_version = 'Django>=1.8.0,<2' if sys.version_info[0] < 3 else 'Django>=1.8.0' setup( name='graphene-django', version=version, @@ -58,7 +59,7 @@ setup( install_requires=[ 'six>=1.10.0', 'graphene>=2.0,<3', - 'Django>=1.8.0', + django_version, 'iso8601', 'singledispatch>=3.4.0.3', 'promise>=2.1', From d2db5f5584c74bbcdccd05b9099d5318a44adea3 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 10 Dec 2017 00:53:13 -0500 Subject: [PATCH 04/42] Remove Django1.7 shims --- graphene_django/form_converter.py | 8 +-- .../management/commands/graphql_schema.py | 71 ++++++------------- 2 files changed, 21 insertions(+), 58 deletions(-) diff --git a/graphene_django/form_converter.py b/graphene_django/form_converter.py index 46a38b3..195c8c4 100644 --- a/graphene_django/form_converter.py +++ b/graphene_django/form_converter.py @@ -8,12 +8,6 @@ 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): @@ -36,7 +30,7 @@ def convert_form_field_to_string(field): return String(description=field.help_text, required=field.required) -@convert_form_field.register(UUIDField) +@convert_form_field.register(forms.UUIDField) def convert_form_field_to_uuid(field): return UUID(description=field.help_text, required=field.required) diff --git a/graphene_django/management/commands/graphql_schema.py b/graphene_django/management/commands/graphql_schema.py index 7e2dbac..3a1690a 100644 --- a/graphene_django/management/commands/graphql_schema.py +++ b/graphene_django/management/commands/graphql_schema.py @@ -1,64 +1,33 @@ 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 from graphene_django.settings import graphene_settings -LT_DJANGO_1_8 = StrictVersion(get_django_version()) < StrictVersion('1.8') +class CommandArguments(BaseCommand): -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.schema', - ), - make_option( - '--out', - type=str, - dest='out', - default='', - help='Output file (default: schema.json)' - ), - make_option( - '--indent', - type=int, - dest='indent', - default=None, - help='Output file indent (default: None)' - ), - ) -else: - class CommandArguments(BaseCommand): + def add_arguments(self, parser): + parser.add_argument( + '--schema', + type=str, + dest='schema', + default=graphene_settings.SCHEMA, + help='Django app containing schema to dump, e.g. myproject.core.schema.schema') - def add_arguments(self, parser): - parser.add_argument( - '--schema', - type=str, - dest='schema', - default=graphene_settings.SCHEMA, - help='Django app containing schema to dump, e.g. myproject.core.schema.schema') + parser.add_argument( + '--out', + type=str, + dest='out', + default=graphene_settings.SCHEMA_OUTPUT, + help='Output file (default: schema.json)') - parser.add_argument( - '--out', - type=str, - dest='out', - default=graphene_settings.SCHEMA_OUTPUT, - help='Output file (default: schema.json)') - - parser.add_argument( - '--indent', - type=int, - dest='indent', - default=graphene_settings.SCHEMA_INDENT, - help='Output file indent (default: None)') + parser.add_argument( + '--indent', + type=int, + dest='indent', + default=graphene_settings.SCHEMA_INDENT, + help='Output file indent (default: None)') class Command(CommandArguments): From 6008cb6de44458f91a23359b60e8b5e78c7342a0 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 11 Dec 2017 21:08:42 -0500 Subject: [PATCH 05/42] Fix qfactor rankings for HTTP-ACCEPT --- graphene_django/tests/test_views.py | 14 ++++++++++++++ graphene_django/views.py | 11 +++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/graphene_django/tests/test_views.py b/graphene_django/tests/test_views.py index c31db8d..ae6cf6f 100644 --- a/graphene_django/tests/test_views.py +++ b/graphene_django/tests/test_views.py @@ -30,6 +30,20 @@ jl = lambda **kwargs: json.dumps([kwargs]) def test_graphiql_is_enabled(client): response = client.get(url_string(), HTTP_ACCEPT='text/html') assert response.status_code == 200 + assert response['Content-Type'].split(';')[0] == 'text/html' + +def test_qfactor_graphiql(client): + response = client.get(url_string(query='{test}'), HTTP_ACCEPT='application/json;q=0.8, text/html;q=0.9') + assert response.status_code == 200 + assert response['Content-Type'].split(';')[0] == 'text/html' + +def test_qfactor_json(client): + response = client.get(url_string(query='{test}'), HTTP_ACCEPT='text/html;q=0.8, application/json;q=0.9') + assert response.status_code == 200 + assert response['Content-Type'].split(';')[0] == 'application/json' + assert response_json(response) == { + 'data': {'test': "Hello World"} + } def test_allows_get_with_query_param(client): diff --git a/graphene_django/views.py b/graphene_django/views.py index cc9e8bb..8b413f2 100644 --- a/graphene_django/views.py +++ b/graphene_django/views.py @@ -35,8 +35,8 @@ def get_accepted_content_types(request): match = re.match(r'(^|;)q=(0(\.\d{,3})?|1(\.0{,3})?)(;|$)', parts[1]) if match: - return parts[0], float(match.group(2)) - return parts[0], 1 + return parts[0].strip(), float(match.group(2)) + return parts[0].strip(), 1 raw_content_types = request.META.get('HTTP_ACCEPT', '*/*').split(',') qualified_content_types = map(qualify, raw_content_types) @@ -280,8 +280,11 @@ class GraphQLView(View): @classmethod def request_wants_html(cls, request): accepted = get_accepted_content_types(request) - html_index = accepted.count('text/html') - json_index = accepted.count('application/json') + accepted_length = len(accepted) + #the list will be ordered in preferred first - so we have to make + #sure the most preferred gets the highest number + html_index = accepted_length - accepted.index('text/html') if 'text/html' in accepted else 0 + json_index = accepted_length - accepted.index('application/json') if 'application/json' in accepted else 0 return html_index > json_index From 65e63026a0d3f2da18a8df1055303ae2cd9f83ad Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 11 Dec 2017 21:23:00 -0500 Subject: [PATCH 06/42] Add tests --- .../rest_framework/tests/test_mutation.py | 13 +++++++++++++ graphene_django/tests/test_forms.py | 13 ++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/graphene_django/rest_framework/tests/test_mutation.py b/graphene_django/rest_framework/tests/test_mutation.py index c34a971..491192a 100644 --- a/graphene_django/rest_framework/tests/test_mutation.py +++ b/graphene_django/rest_framework/tests/test_mutation.py @@ -52,6 +52,19 @@ def test_has_input_fields(): assert 'model' in MyMutation.Input._meta.fields +def test_exclude_fields(): + class MyMutation(SerializerMutation): + class Meta: + serializer_class = MyModelSerializer + exclude_fields = ['created'] + + assert 'cool_name' in MyMutation._meta.fields + assert 'created' not in MyMutation._meta.fields + assert 'errors' in MyMutation._meta.fields + assert 'cool_name' in MyMutation.Input._meta.fields + assert 'created' not in MyMutation.Input._meta.fields + + def test_nested_model(): class MyFakeModelGrapheneType(DjangoObjectType): diff --git a/graphene_django/tests/test_forms.py b/graphene_django/tests/test_forms.py index ada9e8a..b15e866 100644 --- a/graphene_django/tests/test_forms.py +++ b/graphene_django/tests/test_forms.py @@ -1,7 +1,7 @@ from django.core.exceptions import ValidationError from py.test import raises -from ..forms import GlobalIDFormField +from ..forms import GlobalIDFormField,GlobalIDMultipleChoiceField # 'TXlUeXBlOmFiYw==' -> 'MyType', 'abc' @@ -18,6 +18,17 @@ def test_global_id_invalid(): field.clean('badvalue') +def test_global_id_multiple_valid(): + field = GlobalIDMultipleChoiceField() + field.clean(['TXlUeXBlOmFiYw==', 'TXlUeXBlOmFiYw==']) + + +def test_global_id_multiple_invalid(): + field = GlobalIDMultipleChoiceField() + with raises(ValidationError): + field.clean(['badvalue', 'another bad avue']) + + def test_global_id_none(): field = GlobalIDFormField() with raises(ValidationError): From de59d26968752b749141dc5122bec2e3fa9c7af8 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 12 Dec 2017 12:24:11 -0500 Subject: [PATCH 07/42] Test: erro if last is greater than max - plus fix wrong variable --- graphene_django/fields.py | 2 +- graphene_django/tests/test_query.py | 47 +++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/graphene_django/fields.py b/graphene_django/fields.py index aa7f124..e755b93 100644 --- a/graphene_django/fields.py +++ b/graphene_django/fields.py @@ -116,7 +116,7 @@ class DjangoConnectionField(ConnectionField): if last: assert last <= max_limit, ( 'Requesting {} records on the `{}` connection exceeds the `last` limit of {} records.' - ).format(first, info.field_name, max_limit) + ).format(last, info.field_name, max_limit) args['last'] = min(last, max_limit) iterable = resolver(root, info, **args) diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py index 0dece3f..7bb8fa8 100644 --- a/graphene_django/tests/test_query.py +++ b/graphene_django/tests/test_query.py @@ -606,6 +606,53 @@ def test_should_error_if_first_is_greater_than_max(): graphene_settings.RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST = False +def test_should_error_if_last_is_greater_than_max(): + graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 100 + + class ReporterType(DjangoObjectType): + + class Meta: + model = Reporter + interfaces = (Node, ) + + class Query(graphene.ObjectType): + all_reporters = DjangoConnectionField(ReporterType) + + r = Reporter.objects.create( + first_name='John', + last_name='Doe', + email='johndoe@example.com', + a_choice=1 + ) + + schema = graphene.Schema(query=Query) + query = ''' + query NodeFilteringQuery { + allReporters(last: 101) { + edges { + node { + id + } + } + } + } + ''' + + expected = { + 'allReporters': None + } + + result = schema.execute(query) + assert len(result.errors) == 1 + assert str(result.errors[0]) == ( + 'Requesting 101 records on the `allReporters` connection ' + 'exceeds the `last` limit of 100 records.' + ) + assert result.data == expected + + graphene_settings.RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST = False + + def test_should_query_promise_connectionfields(): from promise import Promise From 29935c2d333b0d79b872a0cb2f8abe0d79d2b713 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 12 Dec 2017 12:33:32 -0500 Subject: [PATCH 08/42] Test Last works --- graphene_django/tests/test_query.py | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py index 7bb8fa8..d400a37 100644 --- a/graphene_django/tests/test_query.py +++ b/graphene_django/tests/test_query.py @@ -695,6 +695,48 @@ def test_should_query_promise_connectionfields(): assert not result.errors assert result.data == expected +def test_should_query_promise_connectionfields_with_last(): + from promise import Promise + + class ReporterType(DjangoObjectType): + + class Meta: + model = Reporter + interfaces = (Node, ) + + class Query(graphene.ObjectType): + all_reporters = DjangoConnectionField(ReporterType) + + def resolve_all_reporters(self, info, **args): + return Promise.resolve([Reporter(id=1)]) + + schema = graphene.Schema(query=Query) + query = ''' + query ReporterPromiseConnectionQuery { + allReporters(last: 1) { + edges { + node { + id + } + } + } + } + ''' + + expected = { + 'allReporters': { + 'edges': [{ + 'node': { + 'id': 'UmVwb3J0ZXJUeXBlOjE=' + } + }] + } + } + + result = schema.execute(query) + assert not result.errors + assert result.data == expected + def test_should_query_dataloader_fields(): from promise import Promise From 616c549d2cd14dd19956fcd9e120a8907474f2ba Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 12 Dec 2017 12:49:02 -0500 Subject: [PATCH 09/42] Fix ConnectionField Last Test --- graphene_django/tests/test_query.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py index d400a37..a785a49 100644 --- a/graphene_django/tests/test_query.py +++ b/graphene_django/tests/test_query.py @@ -667,7 +667,7 @@ def test_should_query_promise_connectionfields(): def resolve_all_reporters(self, info, **args): return Promise.resolve([Reporter(id=1)]) - + schema = graphene.Schema(query=Query) query = ''' query ReporterPromiseConnectionQuery { @@ -695,8 +695,14 @@ def test_should_query_promise_connectionfields(): assert not result.errors assert result.data == expected -def test_should_query_promise_connectionfields_with_last(): - from promise import Promise +def test_should_query_connectionfields_with_last(): + + r = Reporter.objects.create( + first_name='John', + last_name='Doe', + email='johndoe@example.com', + a_choice=1 + ) class ReporterType(DjangoObjectType): @@ -708,11 +714,11 @@ def test_should_query_promise_connectionfields_with_last(): all_reporters = DjangoConnectionField(ReporterType) def resolve_all_reporters(self, info, **args): - return Promise.resolve([Reporter(id=1)]) - + return Reporter.objects.all() + schema = graphene.Schema(query=Query) query = ''' - query ReporterPromiseConnectionQuery { + query ReporterLastQuery { allReporters(last: 1) { edges { node { From 51f794edb6d4d4ed3a3aaef874a8fe911d8820ae Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 12 Dec 2017 12:52:32 -0500 Subject: [PATCH 10/42] Test Connectionfield with custom Manager --- graphene_django/tests/models.py | 5 +++ graphene_django/tests/test_query.py | 55 +++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/graphene_django/tests/models.py b/graphene_django/tests/models.py index 0c62f28..406d184 100644 --- a/graphene_django/tests/models.py +++ b/graphene_django/tests/models.py @@ -22,6 +22,9 @@ class Film(models.Model): reporters = models.ManyToManyField('Reporter', related_name='films') +class DoeReporterManager(models.Manager): + def get_queryset(self): + return super(DoeReporterManager, self).get_queryset().filter(last_name="Doe") class Reporter(models.Model): first_name = models.CharField(max_length=30) @@ -29,6 +32,8 @@ class Reporter(models.Model): email = models.EmailField() pets = models.ManyToManyField('self') a_choice = models.CharField(max_length=30, choices=CHOICES) + objects = models.Manager() + doe_objects = DoeReporterManager() def __str__(self): # __unicode__ on Python 2 return "%s %s" % (self.first_name, self.last_name) diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py index a785a49..c4c26f5 100644 --- a/graphene_django/tests/test_query.py +++ b/graphene_django/tests/test_query.py @@ -743,6 +743,61 @@ def test_should_query_connectionfields_with_last(): assert not result.errors assert result.data == expected +def test_should_query_connectionfields_with_manager(): + + r = Reporter.objects.create( + first_name='John', + last_name='Doe', + email='johndoe@example.com', + a_choice=1 + ) + + r = Reporter.objects.create( + first_name='John', + last_name='NotDoe', + email='johndoe@example.com', + a_choice=1 + ) + + class ReporterType(DjangoObjectType): + + class Meta: + model = Reporter + interfaces = (Node, ) + + class Query(graphene.ObjectType): + all_reporters = DjangoConnectionField(ReporterType, on='doe_objects') + + def resolve_all_reporters(self, info, **args): + return Reporter.objects.all() + + schema = graphene.Schema(query=Query) + query = ''' + query ReporterLastQuery { + allReporters(first: 2) { + edges { + node { + id + } + } + } + } + ''' + + expected = { + 'allReporters': { + 'edges': [{ + 'node': { + 'id': 'UmVwb3J0ZXJUeXBlOjE=' + } + }] + } + } + + result = schema.execute(query) + assert not result.errors + assert result.data == expected + def test_should_query_dataloader_fields(): from promise import Promise From 6da95d72eabb71d53824b0000563d7f7219c6e5e Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 16 Dec 2017 19:32:01 -0500 Subject: [PATCH 11/42] Rename [html/json]_index to _priority --- graphene_django/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graphene_django/views.py b/graphene_django/views.py index 8b413f2..2ce2146 100644 --- a/graphene_django/views.py +++ b/graphene_django/views.py @@ -283,10 +283,10 @@ class GraphQLView(View): accepted_length = len(accepted) #the list will be ordered in preferred first - so we have to make #sure the most preferred gets the highest number - html_index = accepted_length - accepted.index('text/html') if 'text/html' in accepted else 0 - json_index = accepted_length - accepted.index('application/json') if 'application/json' in accepted else 0 + html_priority = accepted_length - accepted.index('text/html') if 'text/html' in accepted else 0 + json_priority = accepted_length - accepted.index('application/json') if 'application/json' in accepted else 0 - return html_index > json_index + return html_priority > json_priority @staticmethod def get_graphql_params(request, data): From 28cccb49f7772f785e6b6424e9e579ce0933717a Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 18 Dec 2017 11:40:19 -0500 Subject: [PATCH 12/42] add saces for linter --- graphene_django/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphene_django/views.py b/graphene_django/views.py index 2ce2146..e27105d 100644 --- a/graphene_django/views.py +++ b/graphene_django/views.py @@ -281,8 +281,8 @@ class GraphQLView(View): def request_wants_html(cls, request): accepted = get_accepted_content_types(request) accepted_length = len(accepted) - #the list will be ordered in preferred first - so we have to make - #sure the most preferred gets the highest number + # the list will be ordered in preferred first - so we have to make + # sure the most preferred gets the highest number html_priority = accepted_length - accepted.index('text/html') if 'text/html' in accepted else 0 json_priority = accepted_length - accepted.index('application/json') if 'application/json' in accepted else 0 From f31db13cd00b4ba6bccdf2df0777d897ce389562 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 18 Dec 2017 12:02:04 -0500 Subject: [PATCH 13/42] Add blank line for linting --- graphene_django/management/commands/graphql_schema.py | 1 + 1 file changed, 1 insertion(+) diff --git a/graphene_django/management/commands/graphql_schema.py b/graphene_django/management/commands/graphql_schema.py index 3a1690a..14ecf0c 100644 --- a/graphene_django/management/commands/graphql_schema.py +++ b/graphene_django/management/commands/graphql_schema.py @@ -5,6 +5,7 @@ from django.core.management.base import BaseCommand, CommandError from graphene_django.settings import graphene_settings + class CommandArguments(BaseCommand): def add_arguments(self, parser): From c952ef1a880e271303bdcc7887b293fb5130eac6 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 18 Dec 2017 12:21:49 -0500 Subject: [PATCH 14/42] Date Scalar only added in graphene 2.0.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index beee83c..e31f87b 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ setup( install_requires=[ 'six>=1.10.0', - 'graphene>=2.0,<3', + 'graphene>=2.0.1,<3', 'Django>=1.8.0', 'iso8601', 'singledispatch>=3.4.0.3', From fe35baa627cce4c8bca671df6e677d93bfefb96e Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 6 Dec 2017 15:10:50 -0500 Subject: [PATCH 15/42] Fix merge conflict --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index e31f87b..b4a1633 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ tests_require = [ 'pytest-django==2.9.1', ] + rest_framework_require +django_version = 'Django>=1.8.0,<2' if sys.version_info[0] < 3 else 'Django>=1.8.0' setup( name='graphene-django', version=version, @@ -59,6 +60,7 @@ setup( 'six>=1.10.0', 'graphene>=2.0.1,<3', 'Django>=1.8.0', + django_version, 'iso8601', 'singledispatch>=3.4.0.3', 'promise>=2.1', From 1352d4e02b0fe21c65ddeadc454ded07fe9523c2 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 18 Dec 2017 12:33:42 -0500 Subject: [PATCH 16/42] Fix func names --- graphene_django/converter.py | 2 +- graphene_django/rest_framework/serializer_converter.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/graphene_django/converter.py b/graphene_django/converter.py index fa771e2..dbcbbd5 100644 --- a/graphene_django/converter.py +++ b/graphene_django/converter.py @@ -122,7 +122,7 @@ def convert_field_to_float(field, registry=None): @convert_django_field.register(models.DateTimeField) -def convert_date_to_string(field, registry=None): +def convert_datetime_to_string(field, registry=None): return DateTime(description=field.help_text, required=not field.null) diff --git a/graphene_django/rest_framework/serializer_converter.py b/graphene_django/rest_framework/serializer_converter.py index 0c10a65..44cb01d 100644 --- a/graphene_django/rest_framework/serializer_converter.py +++ b/graphene_django/rest_framework/serializer_converter.py @@ -92,7 +92,7 @@ def convert_serializer_field_to_float(field): @get_graphene_type_from_serializer_field.register(serializers.DateTimeField) -def convert_serializer_field_to_date_time(field): +def convert_serializer_field_to_datetime_time(field): return graphene.types.datetime.DateTime From 76a1d43e665c629f606c5e12ccfdc98d1548bd73 Mon Sep 17 00:00:00 2001 From: yothinix Date: Sat, 30 Dec 2017 18:29:23 +0700 Subject: [PATCH 17/42] Update example query document link --- examples/cookbook-plain/README.md | 2 +- examples/cookbook/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/cookbook-plain/README.md b/examples/cookbook-plain/README.md index 018c584..4075082 100644 --- a/examples/cookbook-plain/README.md +++ b/examples/cookbook-plain/README.md @@ -60,5 +60,5 @@ Now you should be ready to start the server: Now head on over to [http://127.0.0.1:8000/graphql](http://127.0.0.1:8000/graphql) and run some queries! -(See the [Graphene-Django Tutorial](http://docs.graphene-python.org/projects/django/en/latest/tutorial#testing-our-graphql-schema) +(See the [Graphene-Django Tutorial](http://docs.graphene-python.org/projects/django/en/latest/tutorial-plain/#testing-our-graphql-schema) for some example queries) diff --git a/examples/cookbook/README.md b/examples/cookbook/README.md index 1d3fc31..0ec906b 100644 --- a/examples/cookbook/README.md +++ b/examples/cookbook/README.md @@ -60,5 +60,5 @@ Now you should be ready to start the server: Now head on over to [http://127.0.0.1:8000/graphql](http://127.0.0.1:8000/graphql) and run some queries! -(See the [Graphene-Django Tutorial](http://docs.graphene-python.org/projects/django/en/latest/tutorial#testing-our-graphql-schema) +(See the [Graphene-Django Tutorial](http://docs.graphene-python.org/projects/django/en/latest/tutorial-plain/#testing-our-graphql-schema) for some example queries) From ea4ddc7f7908e6e5cff54a94ccae98450d1cc681 Mon Sep 17 00:00:00 2001 From: Martijn Faassen Date: Thu, 18 Jan 2018 16:46:08 +0100 Subject: [PATCH 18/42] Adjustments to make it work with Django 2.0.1 I tried the tutorial but ran into these two problems in Django 2.0.1. --- docs/tutorial-plain.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/tutorial-plain.rst b/docs/tutorial-plain.rst index eca7904..cf877eb 100644 --- a/docs/tutorial-plain.rst +++ b/docs/tutorial-plain.rst @@ -68,7 +68,8 @@ Let's get started with these models: class Ingredient(models.Model): name = models.CharField(max_length=100) notes = models.TextField() - category = models.ForeignKey(Category, related_name='ingredients') + category = models.ForeignKey(Category, related_name='ingredients', + on_delete=models.CASCADE) def __str__(self): return self.name @@ -80,7 +81,7 @@ Add ingredients as INSTALLED_APPS: INSTALLED_APPS = [ ... # Install the ingredients app - 'ingredients', + 'cookbook.ingredients', ] Don't forget to create & run migrations: From 167d0a396442dfb6f3cc636123d7e69d4c42d220 Mon Sep 17 00:00:00 2001 From: mongkok Date: Mon, 22 Jan 2018 01:03:52 +0530 Subject: [PATCH 19/42] Allow DjangoObjectType to have default _meta --- graphene_django/types.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/graphene_django/types.py b/graphene_django/types.py index 684863a..54ed87b 100644 --- a/graphene_django/types.py +++ b/graphene_django/types.py @@ -45,7 +45,7 @@ class DjangoObjectType(ObjectType): @classmethod def __init_subclass_with_meta__(cls, model=None, registry=None, skip_registry=False, only_fields=(), exclude_fields=(), filter_fields=None, connection=None, - connection_class=None, use_connection=None, interfaces=(), **options): + connection_class=None, use_connection=None, interfaces=(), _meta=None, **options): assert is_valid_django_model(model), ( 'You need to pass a valid Django Model in {}.Meta, received "{}".' ).format(cls.__name__, model) @@ -82,7 +82,9 @@ class DjangoObjectType(ObjectType): "The connection must be a Connection. Received {}" ).format(connection.__name__) - _meta = DjangoObjectTypeOptions(cls) + if not _meta: + _meta = DjangoObjectTypeOptions(cls) + _meta.model = model _meta.registry = registry _meta.filter_fields = filter_fields From 0dddea534f8ddda63921c881cbedaa93c019f174 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Wed, 31 Jan 2018 10:46:02 +0000 Subject: [PATCH 20/42] Fix django rest framework version when testing --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ef8a3d6..1999433 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ install: pip install -e .[test] pip install psycopg2 # Required for Django postgres fields testing pip install django==$DJANGO_VERSION - if [ $DJANGO_VERSION = 1.8 ]; then # DRF dropped 1.8 support at 3.7.0 + 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 From 6a152820caeeca220f2c32bd160f89acdf0dc25e Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Tue, 23 Jan 2018 16:17:33 +0000 Subject: [PATCH 21/42] Improve ErrorType Marks some fields as required and non null, it also prevents to do useless checks on the frontend if using a typed language. --- graphene_django/rest_framework/types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphene_django/rest_framework/types.py b/graphene_django/rest_framework/types.py index 956dc43..4c84c69 100644 --- a/graphene_django/rest_framework/types.py +++ b/graphene_django/rest_framework/types.py @@ -3,8 +3,8 @@ from graphene.types.unmountedtype import UnmountedType class ErrorType(graphene.ObjectType): - field = graphene.String() - messages = graphene.List(graphene.String) + field = graphene.String(required=True) + messages = graphene.List(graphene.NonNull(graphene.String), required=True) class DictType(UnmountedType): From bb2d24ec2783881c0fb068957ec23c789f009569 Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Mon, 8 Jan 2018 13:48:47 -0500 Subject: [PATCH 22/42] Convert Date/Time/DateTime form fields to appropriate Graphene types --- graphene_django/form_converter.py | 16 ++++++++++++++++ graphene_django/tests/test_form_converter.py | 12 ++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/graphene_django/form_converter.py b/graphene_django/form_converter.py index 195c8c4..fbda377 100644 --- a/graphene_django/form_converter.py +++ b/graphene_django/form_converter.py @@ -2,6 +2,7 @@ from django import forms from django.forms.fields import BaseTemporalField from graphene import ID, Boolean, Float, Int, List, String, UUID +from graphene.types.datetime import Date, DateTime, Time from .forms import GlobalIDFormField, GlobalIDMultipleChoiceField from .utils import import_single_dispatch @@ -63,6 +64,21 @@ def convert_form_field_to_list(field): return List(ID, required=field.required) +@convert_form_field.register(forms.DateField) +def convert_form_field_to_date(field): + return Date(description=field.help_text, required=field.required) + + +@convert_form_field.register(forms.DateTimeField) +def convert_form_field_to_datetime(field): + return DateTime(description=field.help_text, required=field.required) + + +@convert_form_field.register(forms.TimeField) +def convert_form_field_to_time(field): + return Time(description=field.help_text, required=field.required) + + @convert_form_field.register(forms.ModelChoiceField) @convert_form_field.register(GlobalIDFormField) def convert_form_field_to_id(field): diff --git a/graphene_django/tests/test_form_converter.py b/graphene_django/tests/test_form_converter.py index 5a13554..fbd0176 100644 --- a/graphene_django/tests/test_form_converter.py +++ b/graphene_django/tests/test_form_converter.py @@ -23,16 +23,16 @@ def test_should_unknown_django_field_raise_exception(): 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_date_convert_date(): + assert_conversion(forms.DateField, graphene.types.datetime.Date) -def test_should_time_convert_string(): - assert_conversion(forms.TimeField, graphene.String) +def test_should_time_convert_time(): + assert_conversion(forms.TimeField, graphene.types.datetime.Time) -def test_should_date_time_convert_string(): - assert_conversion(forms.DateTimeField, graphene.String) +def test_should_date_time_convert_date_time(): + assert_conversion(forms.DateTimeField, graphene.types.datetime.DateTime) def test_should_char_convert_string(): From cf35f7c76c582786d22ef94951671cd380a55da0 Mon Sep 17 00:00:00 2001 From: Jonathan Kim Date: Thu, 28 Dec 2017 08:55:03 +0000 Subject: [PATCH 23/42] Upgrade graphiql and react versions --- graphene_django/templates/graphene/graphiql.html | 10 +++++----- graphene_django/views.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/graphene_django/templates/graphene/graphiql.html b/graphene_django/templates/graphene/graphiql.html index 949b850..1ba0613 100644 --- a/graphene_django/templates/graphene/graphiql.html +++ b/graphene_django/templates/graphene/graphiql.html @@ -16,11 +16,11 @@ add "&raw" to the end of the URL within a browser. width: 100%; } - - - - - + + + + +