diff --git a/.travis.yml b/.travis.yml index 1ac8f98..ed78404 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,19 +6,19 @@ python: - 3.5 - pypy before_install: - - | - if [ "$TRAVIS_PYTHON_VERSION" = "pypy" ]; then - export PYENV_ROOT="$HOME/.pyenv" - if [ -f "$PYENV_ROOT/bin/pyenv" ]; then - cd "$PYENV_ROOT" && git pull - else - rm -rf "$PYENV_ROOT" && git clone --depth 1 https://github.com/yyuu/pyenv.git "$PYENV_ROOT" - fi - export PYPY_VERSION="4.0.1" - "$PYENV_ROOT/bin/pyenv" install "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" - fi +- | + if [ "$TRAVIS_PYTHON_VERSION" = "pypy" ]; then + export PYENV_ROOT="$HOME/.pyenv" + if [ -f "$PYENV_ROOT/bin/pyenv" ]; then + cd "$PYENV_ROOT" && git pull + else + rm -rf "$PYENV_ROOT" && git clone --depth 1 https://github.com/yyuu/pyenv.git "$PYENV_ROOT" + fi + export PYPY_VERSION="4.0.1" + "$PYENV_ROOT/bin/pyenv" install "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" + fi install: - | if [ "$TEST_TYPE" = build ]; then @@ -59,3 +59,10 @@ matrix: env: TEST_TYPE=build DJANGO_VERSION=1.9 - python: '2.7' 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= diff --git a/README.md b/README.md index 94278a3..ed8b22d 100644 --- a/README.md +++ b/README.md @@ -107,3 +107,21 @@ After developing, the full test suite can be evaluated by running: ```sh python setup.py test # Use --pytest-args="-v -s" for verbose mode ``` + + +### Documentation + +The documentation is generated using the excellent [Sphinx](http://www.sphinx-doc.org/) and a custom theme. + +The documentation dependencies are installed by running: + +```sh +cd docs +pip install -r requirements.txt +``` + +Then to produce a HTML version of the documentation: + +```sh +make html +``` diff --git a/README.rst b/README.rst index 976298c..1f594a9 100644 --- a/README.rst +++ b/README.rst @@ -117,6 +117,25 @@ After developing, the full test suite can be evaluated by running: python setup.py test # Use --pytest-args="-v -s" for verbose mode +Documentation +~~~~~~~~~~~~~ + +The documentation can be generated using the excellent +`Sphinx `__ and a custom theme. + +To install the documentation dependencies, run the following: + +.. code:: sh + + cd docs + pip install -r requirements.txt + +Then to produce a HTML version of the documentation: + +.. code:: sh + + make html + .. |Graphene Logo| image:: http://graphene-python.org/favicon.png .. |Build Status| image:: https://travis-ci.org/graphql-python/graphene-django.svg?branch=master :target: https://travis-ci.org/graphql-python/graphene-django diff --git a/docs/requirements.txt b/docs/requirements.txt index 5de8cc6..2548604 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ +sphinx # Docs template https://github.com/graphql-python/graphene-python.org/archive/docs.zip diff --git a/docs/tutorial.rst b/docs/tutorial.rst index e4a54a7..e1537a3 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -188,6 +188,8 @@ And then add the ``SCHEMA`` to the ``GRAPHENE`` config in ``cookbook/settings.py 'SCHEMA': 'cookbook.schema.schema' } +Alternatively, we can specify the schema to be used in the urls definition, +as explained below. Creating GraphQL and GraphiQL views ----------------------------------- @@ -199,6 +201,22 @@ view. This view will serve as GraphQL endpoint. As we want to have the aforementioned GraphiQL we specify that on the params with ``graphiql=True``. +.. code:: python + + from django.conf.urls import url, include + from django.contrib import admin + + from graphene_django.views import GraphQLView + + urlpatterns = [ + url(r'^admin/', admin.site.urls), + url(r'^graphql', GraphQLView.as_view(graphiql=True)), + ] + + +If we didn't specify the target schema in the Django settings file +as explained above, we can do so here using: + .. code:: python from django.conf.urls import url, include @@ -210,7 +228,7 @@ aforementioned GraphiQL we specify that on the params with ``graphiql=True``. urlpatterns = [ url(r'^admin/', admin.site.urls), - url(r'^graphql', GraphQLView.as_view(graphiql=True)), + url(r'^graphql', GraphQLView.as_view(graphiql=True, schema=schema)), ] Apply model changes to database diff --git a/graphene_django/compat.py b/graphene_django/compat.py index 461dfef..49ea68f 100644 --- a/graphene_django/compat.py +++ b/graphene_django/compat.py @@ -4,6 +4,7 @@ from django.db import models class MissingType(object): pass + try: DurationField = models.DurationField UUIDField = models.UUIDField @@ -21,6 +22,13 @@ except: try: # 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: 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 diff --git a/graphene_django/converter.py b/graphene_django/converter.py index 22df6ca..70b364c 100644 --- a/graphene_django/converter.py +++ b/graphene_django/converter.py @@ -6,7 +6,7 @@ from graphene import (ID, Boolean, Dynamic, Enum, Field, Float, Int, List, from graphene.relay import is_node from graphene.types.datetime import DateTime 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 .compat import (ArrayField, HStoreField, JSONField, RangeField, @@ -41,7 +41,7 @@ def convert_django_field_with_choices(field, registry=None): choices = getattr(field, 'choices', None) if choices: 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)) named_choices = [(c[0], c[1]) for c in choices] named_choices_descriptions = {c[0]: c[2] for c in choices} diff --git a/graphene_django/templates/graphene/graphiql.html b/graphene_django/templates/graphene/graphiql.html index 3285683..949b850 100644 --- a/graphene_django/templates/graphene/graphiql.html +++ b/graphene_django/templates/graphene/graphiql.html @@ -112,7 +112,7 @@ add "&raw" to the end of the URL within a browser. {% if variables %} variables: '{{ variables|escapejs }}', {% endif %} - {% if operationName %} + {% if operation_name %} operationName: '{{ operation_name|escapejs }}', {% endif %} }), diff --git a/graphene_django/tests/models.py b/graphene_django/tests/models.py index a055912..0c62f28 100644 --- a/graphene_django/tests/models.py +++ b/graphene_django/tests/models.py @@ -38,6 +38,7 @@ 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_+') lang = models.CharField(max_length=2, help_text='Language', choices=[ ('es', 'Spanish'), ('en', 'English') diff --git a/graphene_django/tests/test_types.py b/graphene_django/tests/test_types.py index 2510e1d..c617fe4 100644 --- a/graphene_django/tests/test_types.py +++ b/graphene_django/tests/test_types.py @@ -52,7 +52,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', 'lang', 'importance'] + assert list(fields.keys()) == ['id', 'headline', 'pub_date', 'reporter', 'editor', 'lang', 'importance'] def test_schema_representation(): @@ -66,13 +66,14 @@ type Article implements Node { headline: String! pubDate: DateTime! reporter: Reporter! + editor: Reporter! lang: ArticleLang! importance: ArticleImportance } type ArticleConnection { pageInfo: PageInfo! - edges: [ArticleEdge] + edges: [ArticleEdge]! } type ArticleEdge { @@ -109,11 +110,11 @@ type Reporter { lastName: String! email: String! pets: [Reporter] - aChoice: ReporterA_choice! + aChoice: ReporterAChoice! articles(before: String, after: String, first: Int, last: Int): ArticleConnection } -enum ReporterA_choice { +enum ReporterAChoice { A_1 A_2 } diff --git a/graphene_django/types.py b/graphene_django/types.py index ae2dc18..1973a85 100644 --- a/graphene_django/types.py +++ b/graphene_django/types.py @@ -26,9 +26,12 @@ def construct_fields(options): is_not_in_only = only_fields and name not in options.only_fields is_already_created = name in options.fields is_excluded = name in exclude_fields or is_already_created - if is_not_in_only or is_excluded: + # https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ForeignKey.related_query_name + is_no_backref = str(name).endswith('+') + if is_not_in_only or is_excluded or is_no_backref: # We skip this field if we specify only_fields and is not - # in there. Or when we exclude this field in exclude_fields + # in there. Or when we exclude this field in exclude_fields. + # Or when there is no back reference. continue converted = convert_django_field_with_choices(field, options.registry) if not converted: diff --git a/setup.cfg b/setup.cfg index d1d6da9..c50ce70 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,6 @@ +[aliases] +test=pytest + [tool:pytest] DJANGO_SETTINGS_MODULE = django_test_settings diff --git a/setup.py b/setup.py index bc18814..9c55634 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import find_packages, setup setup( name='graphene-django', - version='1.0', + version='1.1.0', description='Graphene Django integration', long_description=open('README.rst').read(), @@ -38,6 +38,9 @@ setup( 'iso8601', 'singledispatch>=3.4.0.3', ], + setup_requires=[ + 'pytest-runner', + ], tests_require=[ 'django-filter>=0.10.0', 'pytest',