Update travis and tox (#667)

* Update travis and tox

* Use xenial distribution

* Don't install coveralls twice

* Add black and flake8 tox commands

* Remove Python 3.5 test for Django master

* Fix indent

* Ignore migrations

* Remove black for now

* Run black formatting (#668)

* Run black format

* Update makefile

* Add black to travis build
pull/514/head
Jonathan Kim 3 years ago committed by GitHub
parent 44e9b0d0c5
commit 775d2e3523
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 74
      .travis.yml
  2. 4
      Makefile
  3. 4
      examples/cookbook-plain/cookbook/ingredients/admin.py
  4. 6
      examples/cookbook-plain/cookbook/ingredients/apps.py
  5. 7
      examples/cookbook-plain/cookbook/ingredients/models.py
  6. 12
      examples/cookbook-plain/cookbook/ingredients/schema.py
  7. 1
      examples/cookbook-plain/cookbook/ingredients/tests.py
  8. 1
      examples/cookbook-plain/cookbook/ingredients/views.py
  9. 6
      examples/cookbook-plain/cookbook/recipes/apps.py
  10. 22
      examples/cookbook-plain/cookbook/recipes/models.py
  11. 9
      examples/cookbook-plain/cookbook/recipes/schema.py
  12. 1
      examples/cookbook-plain/cookbook/recipes/tests.py
  13. 1
      examples/cookbook-plain/cookbook/recipes/views.py
  14. 10
      examples/cookbook-plain/cookbook/schema.py
  15. 95
      examples/cookbook-plain/cookbook/settings.py
  16. 4
      examples/cookbook-plain/cookbook/urls.py
  17. 4
      examples/cookbook/cookbook/ingredients/admin.py
  18. 6
      examples/cookbook/cookbook/ingredients/apps.py
  19. 2
      examples/cookbook/cookbook/ingredients/models.py
  20. 16
      examples/cookbook/cookbook/ingredients/schema.py
  21. 1
      examples/cookbook/cookbook/ingredients/tests.py
  22. 1
      examples/cookbook/cookbook/ingredients/views.py
  23. 6
      examples/cookbook/cookbook/recipes/apps.py
  24. 19
      examples/cookbook/cookbook/recipes/models.py
  25. 15
      examples/cookbook/cookbook/recipes/schema.py
  26. 1
      examples/cookbook/cookbook/recipes/tests.py
  27. 1
      examples/cookbook/cookbook/recipes/views.py
  28. 10
      examples/cookbook/cookbook/schema.py
  29. 97
      examples/cookbook/cookbook/settings.py
  30. 4
      examples/cookbook/cookbook/urls.py
  31. 73
      examples/starwars/data.py
  32. 10
      examples/starwars/models.py
  33. 17
      examples/starwars/schema.py
  34. 35
      examples/starwars/tests/test_connections.py
  35. 58
      examples/starwars/tests/test_mutation.py
  36. 51
      examples/starwars/tests/test_objectidentification.py
  37. 6
      graphene_django/converter.py
  38. 5
      graphene_django/filter/fields.py
  39. 1
      graphene_django/filter/tests/test_fields.py
  40. 8
      graphene_django/rest_framework/tests/test_mutation.py
  41. 4
      graphene_django/tests/test_query.py
  42. 10
      graphene_django/types.py
  43. 46
      tox.ini

@ -1,58 +1,58 @@
language: python
sudo: required
cache: pip
dist: xenial
python:
- 2.7
- 3.4
- 3.5
- 3.6
- 3.7
env:
matrix:
- DJANGO=1.11
- DJANGO=2.1
- DJANGO=2.2
- DJANGO=master
install:
- TOX_ENV=py${TRAVIS_PYTHON_VERSION}-django${DJANGO}
- pip install tox
- tox -e $TOX_ENV --notest
script:
- tox -e $TOX_ENV
- pip install tox tox-travis
after_success:
- tox -e $TOX_ENV -- pip install coveralls
- tox -e $TOX_ENV -- coveralls $COVERALLS_OPTION
script:
- tox
after_success:
- pip install coveralls
- coveralls
matrix:
fast_finish: true
include:
- python: 3.5
script: tox -e lint
exclude:
- python: 2.7
env: DJANGO=1.11
- python: 3.5
env: DJANGO=1.11
- python: 3.5
env: DJANGO=2.0
- python: 3.5
env: DJANGO=2.1
- python: 2.7
- python: 3.5
env: DJANGO=2.2
- python: 2.7
env: DJANGO=master
- python: 3.4
- python: 3.6
env: DJANGO=1.11
- python: 3.6
env: DJANGO=2.0
- python: 3.6
env: DJANGO=2.1
- python: 3.4
- python: 3.6
env: DJANGO=2.2
- python: 3.4
- python: 3.6
env: DJANGO=master
- python: 3.5
env: DJANGO=master
- python: 3.7
env: DJANGO=1.10
- python: 3.7
env: DJANGO=1.11
allow_failures:
- python: 3.7
env: DJANGO=2.0
- python: 3.7
env: DJANGO=2.1
- python: 3.7
env: DJANGO=2.2
- python: 3.7
env: DJANGO=master
- python: 3.7
env: TOXENV=black,flake8
allow_failures:
- env: DJANGO=master
deploy:

@ -5,7 +5,7 @@ tests:
py.test graphene_django --cov=graphene_django -vv
format:
black graphene_django
black --exclude "/migrations/" graphene_django examples
lint:
flake8 graphene_django
flake8 graphene_django examples

@ -5,8 +5,8 @@ from cookbook.ingredients.models import Category, Ingredient
@admin.register(Ingredient)
class IngredientAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'category')
list_editable = ('name', 'category')
list_display = ("id", "name", "category")
list_editable = ("name", "category")
admin.site.register(Category)

@ -2,6 +2,6 @@ from django.apps import AppConfig
class IngredientsConfig(AppConfig):
name = 'cookbook.ingredients'
label = 'ingredients'
verbose_name = 'Ingredients'
name = "cookbook.ingredients"
label = "ingredients"
verbose_name = "Ingredients"

@ -3,7 +3,8 @@ from django.db import models
class Category(models.Model):
class Meta:
verbose_name_plural = 'Categories'
verbose_name_plural = "Categories"
name = models.CharField(max_length=100)
def __str__(self):
@ -13,7 +14,9 @@ class Category(models.Model):
class Ingredient(models.Model):
name = models.CharField(max_length=100)
notes = models.TextField(null=True, blank=True)
category = models.ForeignKey(Category, related_name='ingredients', on_delete=models.CASCADE)
category = models.ForeignKey(
Category, related_name="ingredients", on_delete=models.CASCADE
)
def __str__(self):
return self.name

@ -15,14 +15,12 @@ class IngredientType(DjangoObjectType):
class Query(object):
category = graphene.Field(CategoryType,
id=graphene.Int(),
name=graphene.String())
category = graphene.Field(CategoryType, id=graphene.Int(), name=graphene.String())
all_categories = graphene.List(CategoryType)
ingredient = graphene.Field(IngredientType,
id=graphene.Int(),
name=graphene.String())
ingredient = graphene.Field(
IngredientType, id=graphene.Int(), name=graphene.String()
)
all_ingredients = graphene.List(IngredientType)
def resolve_all_categories(self, context):
@ -30,7 +28,7 @@ class Query(object):
def resolve_all_ingredients(self, context):
# We can easily optimize query count in the resolve method
return Ingredient.objects.select_related('category').all()
return Ingredient.objects.select_related("category").all()
def resolve_category(self, context, id=None, name=None):
if id is not None:

@ -2,6 +2,6 @@ from django.apps import AppConfig
class RecipesConfig(AppConfig):
name = 'cookbook.recipes'
label = 'recipes'
verbose_name = 'Recipes'
name = "cookbook.recipes"
label = "recipes"
verbose_name = "Recipes"

@ -6,17 +6,23 @@ from ..ingredients.models import Ingredient
class Recipe(models.Model):
title = models.CharField(max_length=100)
instructions = models.TextField()
def __str__(self):
return self.title
class RecipeIngredient(models.Model):
recipe = models.ForeignKey(Recipe, related_name='amounts', on_delete=models.CASCADE)
ingredient = models.ForeignKey(Ingredient, related_name='used_by', on_delete=models.CASCADE)
recipe = models.ForeignKey(Recipe, related_name="amounts", on_delete=models.CASCADE)
ingredient = models.ForeignKey(
Ingredient, related_name="used_by", on_delete=models.CASCADE
)
amount = models.FloatField()
unit = models.CharField(max_length=20, choices=(
('unit', 'Units'),
('kg', 'Kilograms'),
('l', 'Litres'),
('st', 'Shots'),
))
unit = models.CharField(
max_length=20,
choices=(
("unit", "Units"),
("kg", "Kilograms"),
("l", "Litres"),
("st", "Shots"),
),
)

@ -15,13 +15,10 @@ class RecipeIngredientType(DjangoObjectType):
class Query(object):
recipe = graphene.Field(RecipeType,
id=graphene.Int(),
title=graphene.String())
recipe = graphene.Field(RecipeType, id=graphene.Int(), title=graphene.String())
all_recipes = graphene.List(RecipeType)
recipeingredient = graphene.Field(RecipeIngredientType,
id=graphene.Int())
recipeingredient = graphene.Field(RecipeIngredientType, id=graphene.Int())
all_recipeingredients = graphene.List(RecipeIngredientType)
def resolve_recipe(self, context, id=None, title=None):
@ -43,5 +40,5 @@ class Query(object):
return Recipe.objects.all()
def resolve_all_recipeingredients(self, context):
related = ['recipe', 'ingredient']
related = ["recipe", "ingredient"]
return RecipeIngredient.objects.select_related(*related).all()

@ -5,10 +5,12 @@ import graphene
from graphene_django.debug import DjangoDebug
class Query(cookbook.ingredients.schema.Query,
cookbook.recipes.schema.Query,
graphene.ObjectType):
debug = graphene.Field(DjangoDebug, name='_debug')
class Query(
cookbook.ingredients.schema.Query,
cookbook.recipes.schema.Query,
graphene.ObjectType,
):
debug = graphene.Field(DjangoDebug, name="_debug")
schema = graphene.Schema(query=Query)

@ -21,7 +21,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 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'
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
@ -32,64 +32,61 @@ ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'graphene_django',
'cookbook.ingredients.apps.IngredientsConfig',
'cookbook.recipes.apps.RecipesConfig',
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"graphene_django",
"cookbook.ingredients.apps.IngredientsConfig",
"cookbook.recipes.apps.RecipesConfig",
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
GRAPHENE = {
'SCHEMA': 'cookbook.schema.schema',
'SCHEMA_INDENT': 2,
'MIDDLEWARE': (
'graphene_django.debug.DjangoDebugMiddleware',
)
"SCHEMA": "cookbook.schema.schema",
"SCHEMA_INDENT": 2,
"MIDDLEWARE": ("graphene_django.debug.DjangoDebugMiddleware",),
}
ROOT_URLCONF = 'cookbook.urls'
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',
],
"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'
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'),
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
}
@ -99,26 +96,20 @@ DATABASES = {
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',
"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'
LANGUAGE_CODE = "en-us"
TIME_ZONE = 'UTC'
TIME_ZONE = "UTC"
USE_I18N = True
@ -130,4 +121,4 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/
STATIC_URL = '/static/'
STATIC_URL = "/static/"

@ -5,6 +5,6 @@ from graphene_django.views import GraphQLView
urlpatterns = [
path('admin/', admin.site.urls),
path('graphql/', GraphQLView.as_view(graphiql=True)),
path("admin/", admin.site.urls),
path("graphql/", GraphQLView.as_view(graphiql=True)),
]

@ -5,8 +5,8 @@ from cookbook.ingredients.models import Category, Ingredient
@admin.register(Ingredient)
class IngredientAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'category')
list_editable = ('name', 'category')
list_display = ("id", "name", "category")
list_editable = ("name", "category")
admin.site.register(Category)

@ -2,6 +2,6 @@ from django.apps import AppConfig
class IngredientsConfig(AppConfig):
name = 'cookbook.ingredients'
label = 'ingredients'
verbose_name = 'Ingredients'
name = "cookbook.ingredients"
label = "ingredients"
verbose_name = "Ingredients"

@ -11,7 +11,7 @@ class Category(models.Model):
class Ingredient(models.Model):
name = models.CharField(max_length=100)
notes = models.TextField(null=True, blank=True)
category = models.ForeignKey(Category, related_name='ingredients')
category = models.ForeignKey(Category, related_name="ingredients")
def __str__(self):
return self.name

@ -7,24 +7,22 @@ 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']
interfaces = (Node,)
filter_fields = ["name", "ingredients"]
class IngredientNode(DjangoObjectType):
class Meta:
model = Ingredient
# Allow for some more advanced filtering here
interfaces = (Node, )
interfaces = (Node,)
filter_fields = {
'name': ['exact', 'icontains', 'istartswith'],
'notes': ['exact', 'icontains'],
'category': ['exact'],
'category__name': ['exact'],
"name": ["exact", "icontains", "istartswith"],
"notes": ["exact", "icontains"],
"category": ["exact"],
"category__name": ["exact"],
}

@ -2,6 +2,6 @@ from django.apps import AppConfig
class RecipesConfig(AppConfig):
name = 'cookbook.recipes'
label = 'recipes'
verbose_name = 'Recipes'
name = "cookbook.recipes"
label = "recipes"
verbose_name = "Recipes"

@ -10,12 +10,15 @@ class Recipe(models.Model):
class RecipeIngredient(models.Model):
recipe = models.ForeignKey(Recipe, related_name='amounts')
ingredient = models.ForeignKey(Ingredient, related_name='used_by')
recipe = models.ForeignKey(Recipe, related_name="amounts")
ingredient = models.ForeignKey(Ingredient, related_name="used_by")
amount = models.FloatField()
unit = models.CharField(max_length=20, choices=(
('unit', 'Units'),
('kg', 'Kilograms'),
('l', 'Litres'),
('st', 'Shots'),
))
unit = models.CharField(
max_length=20,
choices=(
("unit", "Units"),
("kg", "Kilograms"),
("l", "Litres"),
("st", "Shots"),
),
)

@ -3,24 +3,23 @@ from graphene import Node
from graphene_django.filter import DjangoFilterConnectionField
from graphene_django.types import DjangoObjectType
class RecipeNode(DjangoObjectType):
class RecipeNode(DjangoObjectType):
class Meta:
model = Recipe
interfaces = (Node, )
filter_fields = ['title','amounts']
interfaces = (Node,)
filter_fields = ["title", "amounts"]
class RecipeIngredientNode(DjangoObjectType):
class Meta:
model = RecipeIngredient
# Allow for some more advanced filtering here
interfaces = (Node, )
interfaces = (Node,)
filter_fields = {
'ingredient__name': ['exact', 'icontains', 'istartswith'],
'recipe': ['exact'],
'recipe__title': ['icontains'],
"ingredient__name": ["exact", "icontains", "istartswith"],
"recipe": ["exact"],
"recipe__title": ["icontains"],
}

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

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

@ -5,10 +5,12 @@ import graphene
from graphene_django.debug import DjangoDebug
class Query(cookbook.ingredients.schema.Query,
cookbook.recipes.schema.Query,
graphene.ObjectType):
debug = graphene.Field(DjangoDebug, name='_debug')
class Query(
cookbook.ingredients.schema.Query,
cookbook.recipes.schema.Query,
graphene.ObjectType,
):
debug = graphene.Field(DjangoDebug, name="_debug")
schema = graphene.Schema(query=Query)

@ -21,7 +21,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 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'
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
@ -32,65 +32,62 @@ ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'graphene_django',
'cookbook.ingredients.apps.IngredientsConfig',
'cookbook.recipes.apps.RecipesConfig',
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"graphene_django",
"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',
"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",
]
GRAPHENE = {
'SCHEMA': 'cookbook.schema.schema',
'SCHEMA_INDENT': 2,
'MIDDLEWARE': (
'graphene_django.debug.DjangoDebugMiddleware',
)
"SCHEMA": "cookbook.schema.schema",
"SCHEMA_INDENT": 2,
"MIDDLEWARE": ("graphene_django.debug.DjangoDebugMiddleware",),
}
ROOT_URLCONF = 'cookbook.urls'
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',
],
"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'
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'),
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
}
@ -100,26 +97,20 @@ DATABASES = {
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',
"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'
LANGUAGE_CODE = "en-us"
TIME_ZONE = 'UTC'
TIME_ZONE = "UTC"
USE_I18N = True
@ -131,4 +122,4 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/
STATIC_URL = '/static/'
STATIC_URL = "/static/"

@ -5,6 +5,6 @@ from graphene_django.views import GraphQLView
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^graphql$', GraphQLView.as_view(graphiql=True)),
url(r"^admin/", admin.site.urls),
url(r"^graphql$", GraphQLView.as_view(graphiql=True)),
]

@ -2,97 +2,50 @@ from .models import Character, Faction, Ship
def initialize():
human = Character(
name='Human'
)
human = Character(name="Human")
human.save()
droid = Character(
name='Droid'
)
droid = Character(name="Droid")
droid.save()
rebels = Faction(
id='1',
name='Alliance to Restore the Republic',
hero=human
)
rebels = Faction(id="1", name="Alliance to Restore the Republic", hero=human)
rebels.save()
empire = Faction(
id='2',
name='Galactic Empire',
hero=droid
)
empire = Faction(id="2", name="Galactic Empire", hero=droid)
empire.save()
xwing = Ship(
id='1',
name='X-Wing',
faction=rebels,
)
xwing = Ship(id="1", name="X-Wing", faction=rebels)
xwing.save()
human.ship = xwing
human.save()
ywing = Ship(
id='2',
name='Y-Wing',
faction=rebels,
)
ywing = Ship(id="2", name="Y-Wing", faction=rebels)
ywing.save()
awing = Ship(
id='3',
name='A-Wing',
faction=rebels,
)
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 = Ship(id="4", name="Millenium Falcon", faction=rebels)
falcon.save()
homeOne = Ship(
id='5',
name='Home One',
faction=rebels,
)
homeOne = Ship(id="5", name="Home One", faction=rebels)
homeOne.save()
tieFighter = Ship(
id='6',
name='TIE Fighter',
faction=empire,
)
tieFighter = Ship(id="6", name="TIE Fighter", faction=empire)
tieFighter.save()
tieInterceptor = Ship(
id='7',
name='TIE Interceptor',
faction=empire,
)
tieInterceptor = Ship(id="7", name="TIE Interceptor", faction=empire)
tieInterceptor.save()
executor = Ship(
id='8',
name='Executor',
faction=empire,
)
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 = Ship(name=ship_name, faction_id=faction_id)
new_ship.save()
return new_ship

@ -5,7 +5,13 @@ from django.db import models
class Character(models.Model):
name = models.CharField(max_length=50)
ship = models.ForeignKey('Ship', on_delete=models.CASCADE, 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
@ -21,7 +27,7 @@ class Faction(models.Model):
class Ship(models.Model):
name = models.CharField(max_length=50)
faction = models.ForeignKey(Faction, on_delete=models.CASCADE, related_name='ships')
faction = models.ForeignKey(Faction, on_delete=models.CASCADE, related_name="ships")
def __str__(self):
return self.name

@ -2,18 +2,16 @@ import graphene
from graphene import Schema, relay, resolve_only_args
from graphene_django import DjangoConnectionField, DjangoObjectType
from .data import (create_ship, get_empire, get_faction, get_rebels, get_ship,
get_ships)
from .data import create_ship, get_empire, get_faction, get_rebels, get_ship, get_ships
from .models import Character as CharacterModel
from .models import Faction as FactionModel
from .models import Ship as ShipModel
class Ship(DjangoObjectType):
class Meta:
model = ShipModel
interfaces = (relay.Node, )
interfaces = (relay.Node,)
@classmethod
def get_node(cls, info, id):
@ -22,16 +20,14 @@ class Ship(DjangoObjectType):
class Character(DjangoObjectType):
class Meta:
model = CharacterModel
class Faction(DjangoObjectType):
class Meta:
model = FactionModel
interfaces = (relay.Node, )
interfaces = (relay.Node,)
@classmethod
def get_node(cls, info, id):
@ -39,7 +35,6 @@ class Faction(DjangoObjectType):
class IntroduceShip(relay.ClientIDMutation):
class Input:
ship_name = graphene.String(required=True)
faction_id = graphene.String(required=True)
@ -48,7 +43,9 @@ class IntroduceShip(relay.ClientIDMutation):
faction = graphene.Field(Faction)
@classmethod
def mutate_and_get_payload(cls, root, info, ship_name, faction_id, client_mutation_id=None):
def mutate_and_get_payload(
cls, root, info, ship_name, faction_id, client_mutation_id=None
):
ship = create_ship(ship_name, faction_id)
faction = get_faction(faction_id)
return IntroduceShip(ship=ship, faction=faction)
@ -58,7 +55,7 @@ class Query(graphene.ObjectType):
rebels = graphene.Field(Faction)
empire = graphene.Field(Faction)
node = relay.Node.Field()
ships = DjangoConnectionField(Ship, description='All the ships.')
ships = DjangoConnectionField(Ship, description="All the ships.")
@resolve_only_args
def resolve_ships(self):

@ -8,7 +8,7 @@ pytestmark = pytest.mark.django_db
def test_correct_fetch_first_ship_rebels():
initialize()
query = '''
query = """
query RebelsShipsQuery {
rebels {
name,
@ -24,22 +24,12 @@ def test_correct_fetch_first_ship_rebels():
}
}
}
'''
"""
expected = {
'rebels': {
'name': 'Alliance to Restore the Republic',
'hero': {
'name': 'Human'
},
'ships': {
'edges': [
{
'node': {
'name': 'X-Wing'
}
}
]
}
"rebels": {
"name": "Alliance to Restore the Republic",
"hero": {"name": "Human"},
"ships": {"edges": [{"node": {"name": "X-Wing"}}]},
}
}
result = schema.execute(query)
@ -49,7 +39,7 @@ def test_correct_fetch_first_ship_rebels():
def test_correct_list_characters():
initialize()
query = '''
query = """
query RebelsShipsQuery {
node(id: "U2hpcDox") {
... on Ship {
@ -60,15 +50,8 @@ def test_correct_list_characters():
}
}
}
'''
expected = {
'node': {
'name': 'X-Wing',
'characters': [{
'name': 'Human'
}],
}
}
"""
expected = {"node": {"name": "X-Wing", "characters": [{"name": "Human"}]}}
result = schema.execute(query)
assert not result.errors
assert result.data == expected

@ -9,7 +9,7 @@ pytestmark = pytest.mark.django_db
def test_mutations():
initialize()
query = '''
query = """
mutation MyMutation {
introduceShip(input:{clientMutationId:"abc", shipName: "Peter", factionId: "1"}) {
ship {
@ -29,49 +29,23 @@ def test_mutations():
}
}
}
'''
"""
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'
}
}]
"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)

@ -8,19 +8,16 @@ pytestmark = pytest.mark.django_db
def test_correctly_fetches_id_name_rebels():
initialize()
query = '''
query = """
query RebelsQuery {
rebels {
id
name
}
}
'''
"""
expected = {
'rebels': {
'id': 'RmFjdGlvbjox',
'name': 'Alliance to Restore the Republic'
}
"rebels": {"id": "RmFjdGlvbjox", "name": "Alliance to Restore the Republic"}
}
result = schema.execute(query)
assert not result.errors
@ -29,7 +26,7 @@ def test_correctly_fetches_id_name_rebels():
def test_correctly_refetches_rebels():
initialize()
query = '''
query = """
query RebelsRefetchQuery {
node(id: "RmFjdGlvbjox") {
id
@ -38,12 +35,9 @@ def test_correctly_refetches_rebels():
}
}
}
'''
"""
expected = {
'node': {
'id': 'RmFjdGlvbjox',
'name': 'Alliance to Restore the Republic'
}
"node": {"id": "RmFjdGlvbjox", "name": "Alliance to Restore the Republic"}
}
result = schema.execute(query)
assert not result.errors
@ -52,20 +46,15 @@ def test_correctly_refetches_rebels():
def test_correctly_fetches_id_name_empire():
initialize()
query = '''
query = """
query EmpireQuery {
empire {
id
name
}
}
'''
expected = {
'empire': {
'id': 'RmFjdGlvbjoy',
'name': 'Galactic Empire'
}
}
"""
expected = {"empire": {"id": "RmFjdGlvbjoy", "name": "Galactic Empire"}}
result = schema.execute(query)
assert not result.errors
assert result.data == expected
@ -73,7 +62,7 @@ def test_correctly_fetches_id_name_empire():
def test_correctly_refetches_empire():
initialize()
query = '''
query = """
query EmpireRefetchQuery {
node(id: "RmFjdGlvbjoy") {
id
@ -82,13 +71,8 @@ def test_correctly_refetches_empire():
}
}
}
'''
expected = {
'node': {
'id': 'RmFjdGlvbjoy',
<