Browse Source

Isolated Graphene Django in a new package

pull/4/head
Syrus Akbary 6 years ago
commit
0434899b4e
  1. 81
      .gitignore
  2. 49
      .travis.yml
  3. 72
      README.md
  4. 7
      bin/autolinter
  5. 3
      bin/convert_documentation
  6. 18
      django_test_settings.py
  7. 64
      examples/cookbook/README.md
  8. 0
      examples/cookbook/cookbook/__init__.py
  9. 0
      examples/cookbook/cookbook/ingredients/__init__.py
  10. 6
      examples/cookbook/cookbook/ingredients/admin.py
  11. 7
      examples/cookbook/cookbook/ingredients/apps.py
  12. 1
      examples/cookbook/cookbook/ingredients/fixtures/ingredients.json
  13. 33
      examples/cookbook/cookbook/ingredients/migrations/0001_initial.py
  14. 0
      examples/cookbook/cookbook/ingredients/migrations/__init__.py
  15. 17
      examples/cookbook/cookbook/ingredients/models.py
  16. 38
      examples/cookbook/cookbook/ingredients/schema.py
  17. 2
      examples/cookbook/cookbook/ingredients/tests.py
  18. 2
      examples/cookbook/cookbook/ingredients/views.py
  19. 0
      examples/cookbook/cookbook/recipes/__init__.py
  20. 6
      examples/cookbook/cookbook/recipes/admin.py
  21. 7
      examples/cookbook/cookbook/recipes/apps.py
  22. 36
      examples/cookbook/cookbook/recipes/migrations/0001_initial.py
  23. 0
      examples/cookbook/cookbook/recipes/migrations/__init__.py
  24. 19
      examples/cookbook/cookbook/recipes/models.py
  25. 2
      examples/cookbook/cookbook/recipes/tests.py
  26. 2
      examples/cookbook/cookbook/recipes/views.py
  27. 9
      examples/cookbook/cookbook/schema.py
  28. 125
      examples/cookbook/cookbook/settings.py
  29. 12
      examples/cookbook/cookbook/urls.py
  30. 16
      examples/cookbook/cookbook/wsgi.py
  31. 10
      examples/cookbook/manage.py
  32. 5
      examples/cookbook/requirements.txt
  33. 0
      examples/starwars/__init__.py
  34. 114
      examples/starwars/data.py
  35. 26
      examples/starwars/models.py
  36. 87
      examples/starwars/schema.py
  37. 0
      examples/starwars/tests/__init__.py
  38. 47
      examples/starwars/tests/test_connections.py
  39. 79
      examples/starwars/tests/test_mutation.py
  40. 117
      examples/starwars/tests/test_objectidentification.py
  41. 9
      graphene_django/__init__.py
  42. 24
      graphene_django/compat.py
  43. 189
      graphene_django/converter.py
  44. 4
      graphene_django/debug/__init__.py
  45. 56
      graphene_django/debug/middleware.py
  46. 0
      graphene_django/debug/sql/__init__.py
  47. 169
      graphene_django/debug/sql/tracking.py
  48. 20
      graphene_django/debug/sql/types.py
  49. 0
      graphene_django/debug/tests/__init__.py
  50. 225
      graphene_django/debug/tests/test_query.py
  51. 6
      graphene_django/debug/types.py
  52. 57
      graphene_django/fields.py
  53. 14
      graphene_django/filter/__init__.py
  54. 39
      graphene_django/filter/fields.py
  55. 115
      graphene_django/filter/filterset.py
  56. 0
      graphene_django/filter/tests/__init__.py
  57. 31
      graphene_django/filter/tests/filters.py
  58. 339
      graphene_django/filter/tests/test_fields.py
  59. 31
      graphene_django/filter/utils.py
  60. 70
      graphene_django/form_converter.py
  61. 42
      graphene_django/forms.py
  62. 0
      graphene_django/management/__init__.py
  63. 0
      graphene_django/management/commands/__init__.py
  64. 72
      graphene_django/management/commands/graphql_schema.py
  65. 29
      graphene_django/registry.py
  66. 0
      graphene_django/tests/__init__.py
  67. 52
      graphene_django/tests/models.py
  68. 39
      graphene_django/tests/schema.py
  69. 11
      graphene_django/tests/test_command.py
  70. 261
      graphene_django/tests/test_converter.py
  71. 103
      graphene_django/tests/test_form_converter.py
  72. 29
      graphene_django/tests/test_forms.py
  73. 252
      graphene_django/tests/test_query.py
  74. 40
      graphene_django/tests/test_schema.py
  75. 123
      graphene_django/tests/test_types.py
  76. 57
      graphene_django/tests/test_views.py
  77. 8
      graphene_django/tests/urls.py
  78. 114
      graphene_django/types.py
  79. 81
      graphene_django/utils.py
  80. 9
      graphene_django/views.py
  81. 2
      setup.cfg
  82. 50
      setup.py

81
.gitignore vendored

@ -0,0 +1,81 @@
# Created by https://www.gitignore.io
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
/tests/django.sqlite
/graphene/index.json
/graphene/meta.json
/meta.json
/index.json
/docs/playground/graphene-js/pypyjs-release-nojit/
/docs/static/playground/lib
/docs/static/playground
# PyCharm
.idea
# Databases
*.sqlite3
.vscode

49
.travis.yml

@ -0,0 +1,49 @@
language: python
sudo: false
python:
- 2.7
- 3.4
- 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
install:
- |
if [ "$TEST_TYPE" = build ]; then
pip install pytest pytest-cov pytest-benchmark coveralls six pytest-django mock django-filter
pip install -e .
python setup.py develop
elif [ "$TEST_TYPE" = lint ]; then
pip install flake8
fi
script:
- |
if [ "$TEST_TYPE" = lint ]; then
echo "Checking Python code lint."
flake8 graphene_django
exit
elif [ "$TEST_TYPE" = build ]; then
py.test --cov=graphene_django graphene_django examples
fi
after_success:
- |
if [ "$TEST_TYPE" = build ]; then
coveralls
fi
matrix:
fast_finish: true
include:
- python: '2.7'
env: TEST_TYPE=lint

72
README.md

@ -0,0 +1,72 @@
You are in the `next` unreleased version of Graphene-Django (`1.0.dev`).
Please read [UPGRADE-v1.0.md](https://github.com/graphql-python/graphene/blob/master/UPGRADE-v1.0.md) to learn how to upgrade.
---
# ![Graphene Logo](http://graphene-python.org/favicon.png) [Graphene-Django](http://graphene-python.org) [![Build Status](https://travis-ci.org/graphql-python/graphene-django.svg?branch=master)](https://travis-ci.org/graphql-python/graphene-django) [![PyPI version](https://badge.fury.io/py/graphene-django.svg)](https://badge.fury.io/py/graphene-django) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphene-django/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphene-django?branch=master)
[Graphene](http://graphene-python.org) is a Python library for building GraphQL schemas/types fast and easily.
- **Easy to use:** Graphene helps you use GraphQL in Python without effort.
- **Relay:** Graphene has builtin support for Relay
- **Django:** Automatic *Django model* mapping to Graphene Types. Check a fully working [Django](http://github.com/graphql-python/swapi-graphene) implementation
Graphene also supports *SQLAlchemy*!
*What is supported in this Python version?* **Everything**: Interfaces, ObjectTypes, Scalars, Unions and Relay (Nodes, Connections), in addition to queries, mutations and subscriptions.
**NEW**!: [Try graphene online](http://graphene-python.org/playground/)
## Installation
For instaling graphene, just run this command in your shell
```bash
pip install "graphene-django>=1.0.dev"
```
## Examples
Here is one example for get you started:
```python
from django.db import models
from graphene_django import DjangoObjectType
class UserModel(models.Model):
name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
class User(DjangoObjectType):
class Meta:
# This type will transform all the UserModel fields
# into Graphene fields automatically
model = UserModel
# An extra field in the User Type
full_name = graphene.String()
def resolve_full_name(self, args, context, info):
return "{} {}".format(self.name, self.last_name)
```
If you want to learn even more, you can also check the following [examples](examples/):
* **Schema with Filtering**: [Cookbook example](examples/cookbook)
* **Relay Schema**: [Starwars Relay example](examples/starwars)
## Contributing
After cloning this repo, ensure dependencies are installed by running:
```sh
python setup.py install
```
After developing, the full test suite can be evaluated by running:
```sh
python setup.py test # Use --pytest-args="-v -s" for verbose mode
```

7
bin/autolinter

@ -0,0 +1,7 @@
#!/bin/bash
# Install the required scripts with
# pip install autoflake autopep8 isort
autoflake ./examples/ ./graphene_django/ -r --remove-unused-variables --remove-all-unused-imports --in-place
autopep8 ./examples/ ./graphene_django/ -r --in-place --experimental --aggressive --max-line-length 120
isort -rc ./examples/ ./graphene_django/

3
bin/convert_documentation

@ -0,0 +1,3 @@
#!/bin/bash
pandoc README.md --from markdown --to rst -s -o README.rst

18
django_test_settings.py

@ -0,0 +1,18 @@
import sys, os
ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, ROOT_PATH + '/examples/')
SECRET_KEY = 1
INSTALLED_APPS = [
'graphene_django',
'graphene_django.tests',
'starwars',
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'django_test.sqlite',
}
}

64
examples/cookbook/README.md

@ -0,0 +1,64 @@
Cookbook Example Django Project
===============================
This example project demos integration between Graphene and Django.
The project contains two apps, one named `ingredients` and another
named `recepies`.
Getting started
---------------
First you'll need to get the source of the project. Do this by cloning the
whole Graphene repository:
```bash
# Get the example project code
git clone https://github.com/graphql-python/graphene.git
cd graphene/examples/cookbook
```
It is good idea (but not required) to create a virtual environment
for this project. We'll do this using
[virtualenv](http://docs.python-guide.org/en/latest/dev/virtualenvs/)
to keep things simple,
but you may also find something like
[virtualenvwrapper](https://virtualenvwrapper.readthedocs.org/en/latest/)
to be useful:
```bash
# Create a virtualenv in which we can install the dependencies
virtualenv env
source env/bin/activate
```
Now we can install our dependencies:
```bash
pip install -r requirements.txt
```
Now setup our database:
```bash
# Setup the database
./manage.py migrate
# Load some example data
./manage.py loaddata ingredients
# Create an admin user (useful for logging into the admin UI
# at http://127.0.0.1:8000/admin)
./manage.py createsuperuser
```
Now you should be ready to start the server:
```bash
./manage.py runserver
```
Now head on over to
[http://127.0.0.1:8000/graphiql](http://127.0.0.1:8000/graphiql)
and run some queries!
(See the [Django quickstart guide](http://graphene-python.org/docs/quickstart-django/)
for some example queries)

0
examples/cookbook/cookbook/__init__.py

0
examples/cookbook/cookbook/ingredients/__init__.py

6
examples/cookbook/cookbook/ingredients/admin.py

@ -0,0 +1,6 @@
from django.contrib import admin
from cookbook.ingredients.models import Category, Ingredient
admin.site.register(Ingredient)
admin.site.register(Category)

7
examples/cookbook/cookbook/ingredients/apps.py

@ -0,0 +1,7 @@
from django.apps import AppConfig
class IngredientsConfig(AppConfig):
name = 'cookbook.ingredients'
label = 'ingredients'
verbose_name = 'Ingredients'

1
examples/cookbook/cookbook/ingredients/fixtures/ingredients.json

@ -0,0 +1 @@
[{"model": "ingredients.category", "pk": 1, "fields": {"name": "Dairy"}}, {"model": "ingredients.category", "pk": 2, "fields": {"name": "Meat"}}, {"model": "ingredients.ingredient", "pk": 1, "fields": {"name": "Eggs", "notes": "Good old eggs", "category": 1}}, {"model": "ingredients.ingredient", "pk": 2, "fields": {"name": "Milk", "notes": "Comes from a cow", "category": 1}}, {"model": "ingredients.ingredient", "pk": 3, "fields": {"name": "Beef", "notes": "Much like milk, this comes from a cow", "category": 2}}, {"model": "ingredients.ingredient", "pk": 4, "fields": {"name": "Chicken", "notes": "Definitely doesn't come from a cow", "category": 2}}]

33
examples/cookbook/cookbook/ingredients/migrations/0001_initial.py

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2015-12-04 18:15
from __future__ import unicode_literals
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
],
),
migrations.CreateModel(
name='Ingredient',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('notes', models.TextField()),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ingredients', to='ingredients.Category')),
],
),
]

0
examples/cookbook/cookbook/ingredients/migrations/__init__.py

17
examples/cookbook/cookbook/ingredients/models.py

@ -0,0 +1,17 @@
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Ingredient(models.Model):
name = models.CharField(max_length=100)
notes = models.TextField()
category = models.ForeignKey(Category, related_name='ingredients')
def __str__(self):
return self.name

38
examples/cookbook/cookbook/ingredients/schema.py

@ -0,0 +1,38 @@
from cookbook.ingredients.models import Category, Ingredient
from graphene import ObjectType, Field, AbstractType, Node
from graphene_django.filter import DjangoFilterConnectionField
from graphene_django.types import DjangoObjectType
# Graphene will automatically map the Category model's fields onto the CategoryNode.
# This is configured in the CategoryNode's Meta class (as you can see below)
class CategoryNode(DjangoObjectType):
class Meta:
model = Category
interfaces = (Node, )
filter_fields = ['name', 'ingredients']
filter_order_by = ['name']
class IngredientNode(DjangoObjectType):
class Meta:
model = Ingredient
# Allow for some more advanced filtering here
interfaces = (Node, )
filter_fields = {
'name': ['exact', 'icontains', 'istartswith'],
'notes': ['exact', 'icontains'],
'category': ['exact'],
'category__name': ['exact'],
}
filter_order_by = ['name', 'category__name']
class Query(AbstractType):
category = Field(CategoryNode)
all_categories = DjangoFilterConnectionField(CategoryNode)
ingredient = Field(IngredientNode)
all_ingredients = DjangoFilterConnectionField(IngredientNode)

2
examples/cookbook/cookbook/ingredients/tests.py

@ -0,0 +1,2 @@
# Create your tests here.

2
examples/cookbook/cookbook/ingredients/views.py

@ -0,0 +1,2 @@
# Create your views here.

0
examples/cookbook/cookbook/recipes/__init__.py

6
examples/cookbook/cookbook/recipes/admin.py

@ -0,0 +1,6 @@
from django.contrib import admin
from cookbook.recipes.models import Recipe, RecipeIngredient
admin.site.register(Recipe)
admin.site.register(RecipeIngredient)

7
examples/cookbook/cookbook/recipes/apps.py

@ -0,0 +1,7 @@
from django.apps import AppConfig
class RecipesConfig(AppConfig):
name = 'cookbook.recipes'
label = 'recipes'
verbose_name = 'Recipes'

36
examples/cookbook/cookbook/recipes/migrations/0001_initial.py

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2015-12-04 18:20
from __future__ import unicode_literals
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('ingredients', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Recipe',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('instructions', models.TextField()),
],
),
migrations.CreateModel(
name='RecipeIngredient',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.FloatField()),
('unit', models.CharField(choices=[('kg', 'Kilograms'), ('l', 'Litres'), ('', 'Units')], max_length=20)),
('ingredient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='used_by', to='ingredients.Ingredient')),
('recipes', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='amounts', to='recipes.Recipe')),
],
),
]

0
examples/cookbook/cookbook/recipes/migrations/__init__.py

19
examples/cookbook/cookbook/recipes/models.py

@ -0,0 +1,19 @@
from django.db import models
from cookbook.ingredients.models import Ingredient
class Recipe(models.Model):
title = models.CharField(max_length=100)
instructions = models.TextField()
class RecipeIngredient(models.Model):
recipes = models.ForeignKey(Recipe, related_name='amounts')
ingredient = models.ForeignKey(Ingredient, related_name='used_by')
amount = models.FloatField()
unit = models.CharField(max_length=20, choices=(
('kg', 'Kilograms'),
('l', 'Litres'),
('', 'Units'),
))

2
examples/cookbook/cookbook/recipes/tests.py

@ -0,0 +1,2 @@
# Create your tests here.

2
examples/cookbook/cookbook/recipes/views.py

@ -0,0 +1,2 @@
# Create your views here.

9
examples/cookbook/cookbook/schema.py

@ -0,0 +1,9 @@
import graphene
import cookbook.ingredients.schema
# print cookbook.ingredients.schema.Query._meta.graphql_type.get_fields()['allIngredients'].args
class Query(cookbook.ingredients.schema.Query, graphene.ObjectType):
pass
schema = graphene.Schema(query=Query)

125
examples/cookbook/cookbook/settings.py

@ -0,0 +1,125 @@
"""
Django settings for cookbook project.
Generated by 'django-admin startproject' using Django 1.9.
For more information on this file, see
https://docs.djangoproject.com/en/1.9/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.9/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '_$=$%eqxk$8ss4n7mtgarw^5$8^d5+c83!vwatr@i_81myb=e4'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_graphiql',
'cookbook.ingredients.apps.IngredientsConfig',
'cookbook.recipes.apps.RecipesConfig',
]
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'cookbook.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'cookbook.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/
STATIC_URL = '/static/'

12
examples/cookbook/cookbook/urls.py

@ -0,0 +1,12 @@
from django.conf.urls import include, url
from django.contrib import admin
from django.views.decorators.csrf import csrf_exempt
from cookbook.schema import schema
from graphene_django.views import GraphQLView
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^graphql', csrf_exempt(GraphQLView.as_view(schema=schema))),
url(r'^graphiql', include('django_graphiql.urls')),
]

16
examples/cookbook/cookbook/wsgi.py

@ -0,0 +1,16 @@
"""
WSGI config for cookbook project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cookbook.settings")
application = get_wsgi_application()

10
examples/cookbook/manage.py

@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cookbook.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

5
examples/cookbook/requirements.txt

@ -0,0 +1,5 @@
graphene[django]
django_graphiql
graphql-core
django==1.9
django-filter==0.11.0

0
examples/starwars/__init__.py

114
examples/starwars/data.py

@ -0,0 +1,114 @@
from .models import Character, Faction, Ship
def initialize():
human = Character(
name='Human'
)
human.save()
droid = Character(
name='Droid'
)
droid.save()
rebels = Faction(
id='1',
name='Alliance to Restore the Republic',
hero=human
)
rebels.save()
empire = Faction(
id='2',
name='Galactic Empire',
hero=droid
)
empire.save()
xwing = Ship(
id='1',
name='X-Wing',
faction=rebels,
)
xwing.save()
ywing = Ship(
id='2',
name='Y-Wing',
faction=rebels,
)
ywing.save()
awing = Ship(
id='3',
name='A-Wing',
faction=rebels,
)
awing.save()
# Yeah, technically it's Corellian. But it flew in the service of the rebels,
# so for the purposes of this demo it's a rebel ship.
falcon = Ship(
id='4',
name='Millenium Falcon',
faction=rebels,
)
falcon.save()
homeOne = Ship(
id='5',
name='Home One',
faction=rebels,
)
homeOne.save()
tieFighter = Ship(
id='6',
name='TIE Fighter',
faction=empire,
)
tieFighter.save()
tieInterceptor = Ship(
id='7',
name='TIE Interceptor',
faction=empire,
)
tieInterceptor.save()
executor = Ship(
id='8',
name='Executor',
faction=empire,
)
executor.save()
def create_ship(ship_name, faction_id):
new_ship = Ship(
name=ship_name,
faction_id=faction_id
)
new_ship.save()
return new_ship
def get_ship(_id):
return Ship.objects.get(id=_id)
def get_ships():
return Ship.objects.all()
def get_faction(_id):
return Faction.objects.get(id=_id)
def get_rebels():
return get_faction(1)
def get_empire():
return get_faction(2)

26
examples/starwars/models.py

@ -0,0 +1,26 @@
from __future__ import absolute_import
from django.db import models
class Character(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Faction(models.Model):
name = models.CharField(max_length=50)
hero = models.ForeignKey(Character)
def __str__(self):
return self.name
class Ship(models.Model):
name = models.CharField(max_length=50)
faction = models.ForeignKey(Faction, related_name='ships')
def __str__(self):
return self.name

87
examples/starwars/schema.py

@ -0,0 +1,87 @@
import graphene
from graphene import relay, resolve_only_args, Schema
from graphene_django import DjangoObjectType
from .data import (create_ship, get_empire, get_faction, get_rebels, get_ship,
get_ships)
from .models import (
Character as CharacterModel,
Faction as FactionModel,
Ship as ShipModel
)
class Ship(DjangoObjectType):
class Meta:
model = ShipModel
interfaces = (relay.Node, )
@classmethod
def get_node(cls, id, context, info):
node = get_ship(id)
print(node)
return node
class Character(DjangoObjectType):
class Meta:
model = CharacterModel
class Faction(DjangoObjectType):
class Meta:
model = FactionModel
interfaces = (relay.Node, )
@classmethod
def get_node(cls, id, context, info):
return get_faction(id)
class IntroduceShip(relay.ClientIDMutation):
class Input:
ship_name = graphene.String(required=True)
faction_id = graphene.String(required=True)
ship = graphene.Field(Ship)
faction = graphene.Field(Faction)
@classmethod
def mutate_and_get_payload(cls, input, context, info):
ship_name = input.get('ship_name')
faction_id = input.get('faction_id')
ship = create_ship(ship_name, faction_id)
faction = get_faction(faction_id)
return IntroduceShip(ship=ship, faction=faction)
class Query(graphene.ObjectType):
rebels = graphene.Field(Faction)
empire = graphene.Field(Faction)
node = relay.Node.Field()
ships = relay.ConnectionField(Ship, description='All the ships.')
@resolve_only_args
def resolve_ships(self):
return get_ships()
@resolve_only_args
def resolve_rebels(self):
return get_rebels()
@resolve_only_args
def resolve_empire(self):
return get_empire()
class Mutation(graphene.ObjectType):
introduce_ship = IntroduceShip.Field()
# We register the Character Model because if not would be
# inaccessible for the schema
schema = Schema(query=Query, mutation=Mutation, types=[Ship, Character])

0
examples/starwars/tests/__init__.py

47
examples/starwars/tests/test_connections.py

@ -0,0 +1,47 @@
import pytest
from ..data import initialize
from ..schema import schema
pytestmark = pytest.mark.django_db
def test_correct_fetch_first_ship_rebels():
initialize()
query = '''
query RebelsShipsQuery {
rebels {
name,
hero {
name
}
ships(first: 1) {
edges {
node {
name
}
}
}
}
}
'''
expected = {
'rebels': {
'name': 'Alliance to Restore the Republic',
'hero': {
'name': 'Human'
},
'ships': {
'edges': [
{
'node': {
'name': 'X-Wing'
}
}
]
}
}
}
result = schema.execute(query)
assert not result.errors
assert result.data == expected

79
examples/starwars/tests/test_mutation.py

@ -0,0 +1,79 @@
import pytest
from ..data import initialize
from ..schema import schema
pytestmark = pytest.mark.django_db
def test_mutations():
initialize()
query = '''
mutation MyMutation {
introduceShip(input:{clientMutationId:"abc", shipName: "Peter", factionId: "1"}) {
ship {
id
name
}
faction {
name
ships {
edges {
node {
id
name
}
}
}
}
}
}
'''
expected = {
'introduceShip': {
'ship': {
'id': 'U2hpcDo5',
'name': 'Peter'
},
'faction': {
'name': 'Alliance to Restore the Republic',
'ships': {
'edges': [{
'node': {
'id': 'U2hpcDox',
'name': 'X-Wing'
}
}, {
'node': {
'id': 'U2hpcDoy',
'name': 'Y-Wing'
}
}, {
'node': {
'id': 'U2hpcDoz',
'name': 'A-Wing'
}
}, {
'node': {
'id': 'U2hpcDo0',
'name': 'Millenium Falcon'
}
}, {
'node': {
'id': 'U2hpcDo1',
'name': 'Home One'
}
}, {
'node': {
'id': 'U2hpcDo5',
'name': 'Peter'
}
}]
},
}
}
}
result = schema.execute(query)
assert not result.errors
assert result.data == expected

117
examples/starwars/tests/test_objectidentification.py

@ -0,0 +1,117 @@
import pytest
from ..data import initialize
from ..schema import schema
pytestmark = pytest.mark.django_db
def test_correctly_fetches_id_name_rebels():
initialize()
query = '''
query RebelsQuery {
rebels {
id
name
}
}
'''
expected = {
'rebels': {
'id': 'RmFjdGlvbjox',
'name': 'Alliance to Restore the Republic'
}
}
result = schema.execute(query)
assert not result.errors
assert result.data == expected
def test_correctly_refetches_rebels():
initialize()
query = '''
query RebelsRefetchQuery {
node(id: "RmFjdGlvbjox") {
id
... on Faction {
name
}
}
}
'''
expected = {
'node': {
'id': 'RmFjdGlvbjox',
'name': 'Alliance to Restore the Republic'
}
}
result = schema.execute(query)
assert not result.errors
assert result.data == expected
def test_correctly_fetches_id_name_empire():
initialize()
query = '''
query EmpireQuery {
empire {
id
name
}
}
'''
expected = {
'empire': {
'id': 'RmFjdGlvbjoy',
'name': 'Galactic Empire'
}
}
result = schema.execute(query)
assert not result.errors
assert result.data == expected
def test_correctly_refetches_empire():
initialize()
query = '''
query EmpireRefetchQuery {
node(id: "RmFjdGlvbjoy") {
id
... on Faction {
name
}
}
}
'''
expected = {
'node': {
'id': 'RmFjdGlvbjoy',
'name': 'Galactic Empire'
}
}
result = schema.execute(query)
assert not result.errors
assert result.data == expected
def test_correctly_refetches_xwing():
initialize()
query = '''
query XWingRefetchQuery {
node(id: "U2hpcDox") {
id
... on Ship {
name
}
}
}
'''
expected = {
'node': {
'id': 'U2hpcDox',
'name': 'X-Wing'
}
}
result = schema.execute(query)
assert not result.errors
assert result.data == expected

9
graphene_django/__init__.py

@ -0,0 +1,9 @@
from .types import (
DjangoObjectType,
)
from .fields import (
DjangoConnectionField,
)
__all__ = ['DjangoObjectType',
'DjangoConnectionField']

24
graphene_django/compat.py

@ -0,0 +1,24 @@
from django.db import models
class MissingType(object):
pass
try:
UUIDField = models.UUIDField
except AttributeError:
# Improved compatibility for Django 1.6
UUIDField = MissingType
try:
from django.db.models.related import RelatedObject
except:
# Improved compatibility for Django 1.6
RelatedObject = MissingType
try:
# Postgres fields are only available in Django 1.8+
from django.contrib.postgres.fields import ArrayField, HStoreField, JSONField, RangeField
except ImportError:
ArrayField, HStoreField, JSONField, RangeField = (MissingType, ) * 4

189
graphene_django/converter.py

@ -0,0 +1,189 @@
from django.db import models
from django.utils.encoding import force_text
from graphene import Enum, List, ID, Boolean, Float, Int, String, Field, NonNull, Field, Dynamic
from graphene.types.json import JSONString
from graphene.types.datetime import DateTime
from graphene.utils.str_converters import to_const
from graphene.relay import is_node
from .compat import (ArrayField, HStoreField, JSONField, RangeField,
RelatedObject, UUIDField)
from .utils import get_related_model, import_single_dispatch
from .fields import get_connection_field
singledispatch = import_single_dispatch()
def convert_choice_name(name):
return to_const(force_text(name))
def get_choices(choices):
for value, help_text in choices:
if isinstance(help_text, (tuple, list)):
for choice in get_choices(help_text):
yield choice
else:
name = convert_choice_name(help_text)
description = help_text
yield name, value, description
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())
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}
class EnumWithDescriptionsType(object):
@property
def description(self):
return named_choices_descriptions[self.name]
enum = Enum(name, list(named_choices), type=EnumWithDescriptionsType)
return enum(description=field.help_text)
return convert_django_field(field, registry)
@singledispatch
def convert_django_field(field, registry=None):
raise Exception(
"Don't know how to convert the Django field %s (%s)" %
(field, field.__class__))
@convert_django_field.register(models.CharField)
@convert_django_field.register(models.TextField)
@convert_django_field.register(models.EmailField)
@convert_django_field.register(models.SlugField)
@convert_django_field.register(models.URLField)
@convert_django_field.register(models.GenericIPAddressField)
@convert_django_field.register(models.FileField)
@convert_django_field.register(UUIDField)
def convert_field_to_string(field, registry=None):
return String(description=field.help_text)
@convert_django_field.register(models.AutoField)
def convert_field_to_id(field, registry=None):
return ID(description=field.help_text)
@convert_django_field.register(models.PositiveIntegerField)
@convert_django_field.register(models.PositiveSmallIntegerField)
@convert_django_field.register(models.SmallIntegerField)
@convert_django_field.register(models.BigIntegerField)
@convert_django_field.register(models.IntegerField)
def convert_field_to_int(field, registry=None):
return Int(description=field.help_text)
@convert_django_field.register(models.BooleanField)
def convert_field_to_boolean(field, registry=None):
return NonNull(Boolean, description=field.help_text)
@convert_django_field.register(models.NullBooleanField)
def convert_field_to_nullboolean(field, registry=None):
return Boolean(description=field.help_text)
@convert_django_field.register(models.DecimalField)
@convert_django_field.register(models.FloatField)
def convert_field_to_float(field, registry=None):
return Float(description=field.help_text)
@convert_django_field.register(models.DateField)
def convert_date_to_string(field, registry=None):
return DateTime(description=field.help_text)
@convert_django_field.register(models.OneToOneRel)
def convert_onetoone_field_to_djangomodel(field, registry=None):
model = get_related_model(field)
def dynamic_type():
_type = registry.get_type_for_model(model)
if not _type:
return
return Field(_type)
return Dynamic(dynamic_type)
@convert_django_field.register(models.ManyToManyField)
@convert_django_field.register(models.ManyToManyRel)
@convert_django_field.register(models.ManyToOneRel)
def convert_field_to_list_or_connection(field, registry=None):
model = get_related_model(field)
def dynamic_type():
_type = registry.get_type_for_model(model)
if not _type:
return
if is_node(_type):
return get_connection_field(_type)
return Field(List(_type))
return Dynamic(dynamic_type)
# For Django 1.6
@convert_django_field.register(RelatedObject)
def convert_relatedfield_to_djangomodel(field, registry=None):
model = field.model
def dynamic_type():
_type = registry.get_type_for_model(model)
if not _type:
return
if is_node(_type):
return get_connection_field(_type)
return Field(List(_type))
return Dynamic(dynamic_type)
@convert_django_field.register(models.OneToOneField)
@convert_django_field.register(models.ForeignKey)
def convert_field_to_djangomodel(field, registry=None):
model = get_related_model(field)
def dynamic_type():
_type = registry.get_type_for_model(model)
if not _type:
return
return Field(_type, description=field.help_text)
return Dynamic(dynamic_type)
@convert_django_field.register(ArrayField)
def convert_postgres_array_to_list(field, registry=None):
base_type = convert_django_field(field.base_field)
if not isinstance(base_type, (List, NonNull)):
base_type = type(base_type)
return List(base_type, description=field.help_text)