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
This commit is contained in:
Jonathan Kim 2019-06-10 20:54:30 -07:00 committed by GitHub
parent 44e9b0d0c5
commit 775d2e3523
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 331 additions and 439 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,8 @@ from django.db import models
class Category(models.Model): class Category(models.Model):
class Meta: class Meta:
verbose_name_plural = 'Categories' verbose_name_plural = "Categories"
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
def __str__(self): def __str__(self):
@ -13,7 +14,9 @@ class Category(models.Model):
class Ingredient(models.Model): class Ingredient(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
notes = models.TextField(null=True, blank=True) 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): def __str__(self):
return self.name return self.name

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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/ # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # 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! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
@ -32,64 +32,61 @@ ALLOWED_HOSTS = []
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.admin', "django.contrib.admin",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.staticfiles', "django.contrib.staticfiles",
'graphene_django', "graphene_django",
"cookbook.ingredients.apps.IngredientsConfig",
'cookbook.ingredients.apps.IngredientsConfig', "cookbook.recipes.apps.RecipesConfig",
'cookbook.recipes.apps.RecipesConfig',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', "django.middleware.security.SecurityMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
] ]
GRAPHENE = { GRAPHENE = {
'SCHEMA': 'cookbook.schema.schema', "SCHEMA": "cookbook.schema.schema",
'SCHEMA_INDENT': 2, "SCHEMA_INDENT": 2,
'MIDDLEWARE': ( "MIDDLEWARE": ("graphene_django.debug.DjangoDebugMiddleware",),
'graphene_django.debug.DjangoDebugMiddleware',
)
} }
ROOT_URLCONF = 'cookbook.urls' ROOT_URLCONF = "cookbook.urls"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [], "DIRS": [],
'APP_DIRS': True, "APP_DIRS": True,
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
'django.template.context_processors.debug', "django.template.context_processors.debug",
'django.template.context_processors.request', "django.template.context_processors.request",
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
], ]
}, },
}, }
] ]
WSGI_APPLICATION = 'cookbook.wsgi.application' WSGI_APPLICATION = "cookbook.wsgi.application"
# Database # Database
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases # https://docs.djangoproject.com/en/1.9/ref/settings/#databases
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
} }
} }
@ -99,26 +96,20 @@ DATABASES = {
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', "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.MinimumLengthValidator"},
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
] ]
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/ # 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 USE_I18N = True
@ -130,4 +121,4 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/ # https://docs.djangoproject.com/en/1.9/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = "/static/"

View File

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

View File

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

View File

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

View File

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

View File

@ -7,24 +7,22 @@ from graphene_django.types import DjangoObjectType
# Graphene will automatically map the Category model's fields onto the CategoryNode. # 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) # This is configured in the CategoryNode's Meta class (as you can see below)
class CategoryNode(DjangoObjectType): class CategoryNode(DjangoObjectType):
class Meta: class Meta:
model = Category model = Category
interfaces = (Node, ) interfaces = (Node,)
filter_fields = ['name', 'ingredients'] filter_fields = ["name", "ingredients"]
class IngredientNode(DjangoObjectType): class IngredientNode(DjangoObjectType):
class Meta: class Meta:
model = Ingredient model = Ingredient
# Allow for some more advanced filtering here # Allow for some more advanced filtering here
interfaces = (Node, ) interfaces = (Node,)
filter_fields = { filter_fields = {
'name': ['exact', 'icontains', 'istartswith'], "name": ["exact", "icontains", "istartswith"],
'notes': ['exact', 'icontains'], "notes": ["exact", "icontains"],
'category': ['exact'], "category": ["exact"],
'category__name': ['exact'], "category__name": ["exact"],
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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/ # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # 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! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
@ -32,65 +32,62 @@ ALLOWED_HOSTS = []
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.admin', "django.contrib.admin",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.staticfiles', "django.contrib.staticfiles",
'graphene_django', "graphene_django",
"cookbook.ingredients.apps.IngredientsConfig",
'cookbook.ingredients.apps.IngredientsConfig', "cookbook.recipes.apps.RecipesConfig",
'cookbook.recipes.apps.RecipesConfig',
] ]
MIDDLEWARE_CLASSES = [ MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware', "django.middleware.security.SecurityMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.auth.middleware.SessionAuthenticationMiddleware', "django.contrib.auth.middleware.SessionAuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
] ]
GRAPHENE = { GRAPHENE = {
'SCHEMA': 'cookbook.schema.schema', "SCHEMA": "cookbook.schema.schema",
'SCHEMA_INDENT': 2, "SCHEMA_INDENT": 2,
'MIDDLEWARE': ( "MIDDLEWARE": ("graphene_django.debug.DjangoDebugMiddleware",),
'graphene_django.debug.DjangoDebugMiddleware',
)
} }
ROOT_URLCONF = 'cookbook.urls' ROOT_URLCONF = "cookbook.urls"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [], "DIRS": [],
'APP_DIRS': True, "APP_DIRS": True,
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
'django.template.context_processors.debug', "django.template.context_processors.debug",
'django.template.context_processors.request', "django.template.context_processors.request",
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
], ]
}, },
}, }
] ]
WSGI_APPLICATION = 'cookbook.wsgi.application' WSGI_APPLICATION = "cookbook.wsgi.application"
# Database # Database
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases # https://docs.djangoproject.com/en/1.9/ref/settings/#databases
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
} }
} }
@ -100,26 +97,20 @@ DATABASES = {
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', "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.MinimumLengthValidator"},
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
] ]
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/ # 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 USE_I18N = True
@ -131,4 +122,4 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/ # https://docs.djangoproject.com/en/1.9/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = "/static/"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ pytestmark = pytest.mark.django_db
def test_mutations(): def test_mutations():
initialize() initialize()
query = ''' query = """
mutation MyMutation { mutation MyMutation {
introduceShip(input:{clientMutationId:"abc", shipName: "Peter", factionId: "1"}) { introduceShip(input:{clientMutationId:"abc", shipName: "Peter", factionId: "1"}) {
ship { ship {
@ -29,49 +29,23 @@ def test_mutations():
} }
} }
} }
''' """
expected = { expected = {
'introduceShip': { "introduceShip": {
'ship': { "ship": {"id": "U2hpcDo5", "name": "Peter"},
'id': 'U2hpcDo5', "faction": {
'name': 'Peter' "name": "Alliance to Restore the Republic",
}, "ships": {
'faction': { "edges": [
'name': 'Alliance to Restore the Republic', {"node": {"id": "U2hpcDox", "name": "X-Wing"}},
'ships': { {"node": {"id": "U2hpcDoy", "name": "Y-Wing"}},
'edges': [{ {"node": {"id": "U2hpcDoz", "name": "A-Wing"}},
'node': { {"node": {"id": "U2hpcDo0", "name": "Millenium Falcon"}},
'id': 'U2hpcDox', {"node": {"id": "U2hpcDo1", "name": "Home One"}},
'name': 'X-Wing' {"node": {"id": "U2hpcDo5", "name": "Peter"}},
} ]
}, {
'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) result = schema.execute(query)

View File

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

View File

@ -177,7 +177,11 @@ def convert_field_to_list_or_connection(field, registry=None):
if not _type: if not _type:
return return
description = field.help_text if isinstance(field, models.ManyToManyField) else field.field.help_text description = (
field.help_text
if isinstance(field, models.ManyToManyField)
else field.field.help_text
)
# If there is a connection, we should transform the field # If there is a connection, we should transform the field
# into a DjangoConnectionField # into a DjangoConnectionField

View File

@ -41,10 +41,9 @@ class DjangoFilterConnectionField(DjangoConnectionField):
meta.update(self._extra_filter_meta) meta.update(self._extra_filter_meta)
filterset_class = self._provided_filterset_class or ( filterset_class = self._provided_filterset_class or (
self.node_type._meta.filterset_class) self.node_type._meta.filterset_class
self._filterset_class = get_filterset_class(
filterset_class, **meta
) )
self._filterset_class = get_filterset_class(filterset_class, **meta)
return self._filterset_class return self._filterset_class

View File

@ -229,6 +229,7 @@ def test_filter_filterset_information_on_meta_related():
def test_filter_filterset_class_filter_fields_exception(): def test_filter_filterset_class_filter_fields_exception():
with pytest.raises(Exception): with pytest.raises(Exception):
class ReporterFilter(FilterSet): class ReporterFilter(FilterSet):
class Meta: class Meta:
model = Reporter model = Reporter

View File

@ -104,7 +104,9 @@ def test_write_only_field():
) )
assert hasattr(result, "cool_name") assert hasattr(result, "cool_name")
assert not hasattr(result, "password"), "'password' is write_only field and shouldn't be visible" assert not hasattr(
result, "password"
), "'password' is write_only field and shouldn't be visible"
@mark.django_db @mark.django_db
@ -124,7 +126,9 @@ def test_write_only_field_using_extra_kwargs():
) )
assert hasattr(result, "cool_name") assert hasattr(result, "cool_name")
assert not hasattr(result, "password"), "'password' is write_only field and shouldn't be visible" assert not hasattr(
result, "password"
), "'password' is write_only field and shouldn't be visible"
def test_nested_model(): def test_nested_model():

View File

@ -1015,13 +1015,13 @@ def test_proxy_model_support():
"edges": [ "edges": [
{"node": {"id": to_global_id("CNNReporterType", cnn_reporter.id)}} {"node": {"id": to_global_id("CNNReporterType", cnn_reporter.id)}}
] ]
} },
} }
result = schema.execute(query) result = schema.execute(query)
assert not result.errors assert not result.errors
assert result.data == expected assert result.data == expected
def test_should_resolve_get_queryset_connectionfields(): def test_should_resolve_get_queryset_connectionfields():
reporter_1 = Reporter.objects.create( reporter_1 = Reporter.objects.create(

View File

@ -82,10 +82,12 @@ class DjangoObjectType(ObjectType):
raise Exception("Can't set both filter_fields and filterset_class") raise Exception("Can't set both filter_fields and filterset_class")
if not DJANGO_FILTER_INSTALLED and (filter_fields or filterset_class): if not DJANGO_FILTER_INSTALLED and (filter_fields or filterset_class):
raise Exception(( raise Exception(
"Can only set filter_fields or filterset_class if " (
"Django-Filter is installed" "Can only set filter_fields or filterset_class if "
)) "Django-Filter is installed"
)
)
django_fields = yank_fields_from_attrs( django_fields = yank_fields_from_attrs(
construct_fields(model, registry, only_fields, exclude_fields), _as=Field construct_fields(model, registry, only_fields, exclude_fields), _as=Field

46
tox.ini
View File

@ -1,31 +1,39 @@
[tox] [tox]
envlist = py{2.7,3.4,3.5,3.6,3.7,pypy,pypy3}-django{1.10,1.11,2.0,2.1,2.2,master},lint envlist =
py{27,35,36,37}-django{111,20,21,22,master},
black,flake8
[travis:env]
DJANGO =
1.11: django111
2.0: django20
2.1: django21
2.2: django22
master: djangomaster
[testenv] [testenv]
passenv = * passenv = *
usedevelop = True usedevelop = True
setenv = setenv =
DJANGO_SETTINGS_MODULE=django_test_settings DJANGO_SETTINGS_MODULE=django_test_settings
basepython =
py2.7: python2.7
py3.4: python3.4
py3.5: python3.5
py3.6: python3.6
py3.7: python3.7
pypypy: pypy
pypypy3: pypy3
deps = deps =
-e.[test] -e.[test]
psycopg2 psycopg2
django1.10: Django>=1.10,<1.11 django111: Django>=1.11,<2.0
django1.11: Django>=1.11,<1.12 django20: Django>=2.0,<2.1
django2.0: Django>=2.0 django21: Django>=2.1,<2.2
django2.1: Django>=2.1 django22: Django>=2.2,<3.0
djangomaster: https://github.com/django/django/archive/master.zip djangomaster: https://github.com/django/django/archive/master.zip
commands = {posargs:py.test --cov=graphene_django graphene_django examples} commands = {posargs:py.test --cov=graphene_django graphene_django examples}
[testenv:lint] [testenv:black]
basepython = python basepython = python3.7
deps = deps = black
prospector commands =
commands = prospector graphene_django -0 black --exclude "/migrations/" graphene_django examples --check
[testenv:flake8]
basepython = python3.7
deps = flake8
commands =
flake8 graphene_django examples