Skip to content
This repository was archived by the owner on Nov 16, 2018. It is now read-only.

Upgrades to Graphene 2 and Apollo 2 #13

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion backend/backend/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@


class JWTMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
response = self.get_response(request)
return response

def process_view(self, request, view_func, view_args, view_kwargs):
token = request.META.get('HTTP_AUTHORIZATION', '')
if not token.startswith('JWT'):
Expand All @@ -12,4 +19,4 @@ def process_view(self, request, view_func, view_args, view_kwargs):
auth = jwt_auth.authenticate(request)
except Exception:
return
request.user = auth[0]
request.user = auth[0]
6 changes: 4 additions & 2 deletions backend/backend/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import simple_app.schema

import graphene
import simple_app.schema


class Mutations(
simple_app.schema.Mutation,
graphene.ObjectType,
graphene.ObjectType
):
pass

Expand All @@ -16,5 +19,4 @@ class Queries(
):
pass


schema = graphene.Schema(query=Queries, mutation=Mutations)
2 changes: 1 addition & 1 deletion backend/backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@

CORS_ORIGIN_ALLOW_ALL = True

MIDDLEWARE_CLASSES = [
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'backend.middleware.JWTMiddleware',
Expand Down
25 changes: 25 additions & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
coverage==4.4.2
Django==1.11.7
django-cors-headers==2.1.0
django-filter==1.1.0
djangorestframework==3.7.3
djangorestframework-jwt==1.11.0
Faker==0.7.3
graphene==2.0.1
graphene-django==2.0.0
graphql-core==2.0
graphql-relay==0.4.5
iso8601==0.1.12
mixer==5.6.6
promise==2.1
py==1.5.0
PyJWT==1.5.3
pytest==3.2.5
pytest-cov==2.5.1
pytest-django==3.1.2
python-dateutil==2.6.1
pytz==2017.3
Rx==1.6.0
singledispatch==3.4.0.3
six==1.11.0
typing==3.6.2
59 changes: 33 additions & 26 deletions backend/simple_app/schema.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User
import json

import graphene
from django.contrib.auth.models import User
from graphene_django.types import DjangoObjectType
from graphene_django.filter.fields import DjangoFilterConnectionField
from graphql_relay.node.node import from_global_id
Expand All @@ -21,48 +23,53 @@ class Meta:


class CreateMessageMutation(graphene.Mutation):
class Input:
class Arguments:
message = graphene.String()

status = graphene.Int()
formErrors = graphene.String()
message = graphene.Field(MessageType)

@staticmethod
def mutate(root, args, context, info):
if not context.user.is_authenticated():
def mutate(self, info, message):
context = info.context
message = models.Message(user=context.user, message=message)
if not context.user.is_authenticated:
return CreateMessageMutation(status=403)
message = args.get('message', '').strip()
if not message:
return CreateMessageMutation(
status=400,
formErrors=json.dumps(
{'message': ['Please enter a message.']}))
obj = models.Message.objects.create(
user=context.user, message=message
)
return CreateMessageMutation(status=200, message=obj)

try:
message.full_clean()

except ValidationError as e:
return CreateMessageMutation(status=400, formErrors=json.dumps(e))
else:
message.save()
return CreateMessageMutation(status=200, message=message)


class Mutation(graphene.AbstractType):
create_message = CreateMessageMutation.Field()


class Query(graphene.AbstractType):
current_user = graphene.Field(UserType)
all_messages = DjangoFilterConnectionField(MessageType)
current_user = graphene.Field(UserType,
id=graphene.Int(),
name=graphene.String())
message = graphene.Field(MessageType,
id=graphene.ID(),
message=graphene.String())

def resolve_current_user(self, args, context, info):
if not context.user.is_authenticated():
return None
return context.user

message = graphene.Field(MessageType, id=graphene.ID())
def resolve_all_messages(self, info, **kwargs):
return models.Message.objects.all()

def resolve_message(self, args, context, info):
rid = from_global_id(args.get('id'))
def resolve_message(self, info, id):
rid = from_global_id(id)
return models.Message.objects.get(pk=rid[1])

all_messages = DjangoFilterConnectionField(MessageType)

def resolve_all_messages(self, args, context, info):
return models.Message.objects.all()
def resolve_current_user(self, info, **kwargs):
context = info.context
if not context.user.is_authenticated:
return None
return context.user
71 changes: 41 additions & 30 deletions backend/simple_app/tests/test_schema.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import pytest
from django.contrib.auth.models import AnonymousUser
from django.test import RequestFactory

import pytest
from graphene.test import Client

from mixer.backend.django import mixer
from graphql_relay.node.node import to_global_id

from .. import schema

from backend.schema import schema as my_schema

pytestmark = pytest.mark.django_db

Expand All @@ -21,51 +24,59 @@ def test_message_type():


def test_resolve_current_user():
q = schema.Query()
req = RequestFactory().get('/')
req.user = AnonymousUser()
res = q.resolve_current_user(None, req, None)
assert res is None, 'Should return None if user is not authenticated'
client = Client(my_schema)
client.user = AnonymousUser()
executed = client.execute(''' { currentUser { id } }''', context_value=req)

user = mixer.blend('auth.User')
req.user = user
res = q.resolve_current_user(None, req, None)
assert res == user, 'Should return the current user if is authenticated'
assert executed['data']['currentUser'] is None, 'Should return None if user is not authenticated'

req.user = mixer.blend('auth.User')
executed = client.execute('''{ currentUser { id } }''', context_value=req)
assert executed['data']['currentUser']['id'] == str(req.user.id), 'Should return the current user if is authenticated'


def test_resolve_message():
msg = mixer.blend('simple_app.Message')
q = schema.Query()
id = to_global_id('MessageType', msg.pk)
res = q.resolve_message({'id': id}, None, None)
assert res == msg, 'Should return the requested message'
message_id = to_global_id('MessageType', msg.pk)
res = q.resolve_message(None, message_id)
assert res == msg, 'Should return requested message'


def test_resolve_all_messages():
mixer.blend('simple_app.Message')
mixer.blend('simple_app.Message')
q = schema.Query()
res = q.resolve_all_messages(None, None, None)
res = q.resolve_all_messages(None)
assert res.count() == 2, 'Should return all messages'


def test_create_message_mutation():
user = mixer.blend('auth.User')
mut = schema.CreateMessageMutation()

data = {'message': 'Test'}
req = RequestFactory().get('/')
req.user = AnonymousUser()
res = mut.mutate(None, data, req, None)
assert res.status == 403, 'Should return 403 if user is not logged in'

req.user = user
res = mut.mutate(None, {}, req, None)
assert res.status == 400, 'Should return 400 if there are form errors'
assert 'message' in res.formErrors, (
'Should have form error for message field')

req.user = user
res = mut.mutate(None, {'message': 'Test'}, req, None)
assert res.status == 200, 'Should return 400 if there are form errors'
assert res.message.pk == 1, 'Should create new message'
client = Client(my_schema)
executed = client.execute('''
mutation {
createMessage(message: "Murat") {
status
formErrors
}
}
''', context_value=req)

assert 'errors' in executed, 'Should contain error message'

req.user = mixer.blend('auth.User')
executed = client.execute('''
mutation {
createMessage(message: "Murat") {
status
formErrors
}
}
''', context_value=req)

assert executed['data']['createMessage']['status'] == 200
assert executed['data']['createMessage']['formErrors'] is None
8 changes: 7 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"query-string": "^4.3.4",
"apollo-cache-inmemory": "^1.1.4",
"apollo-client": "^2.0.4",
"apollo-link": "^1.0.6",
"apollo-link-batch-http": "^1.0.3",
"apollo-link-context": "^1.0.3",
"apollo-link-http": "^1.3.1",
"query-string": "^5.0.1",
"react": "^15.6.1",
"react-apollo": "^1.4.2",
"react-dom": "^15.6.1",
Expand Down
59 changes: 29 additions & 30 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,42 @@
import React, { Component } from 'react'
import {
ApolloProvider,
ApolloClient,
createBatchingNetworkInterface,
} from 'react-apollo'
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom'
import React, { Component } from 'react';
import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';

import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloProvider } from 'react-apollo';
import { BatchHttpLink } from "apollo-link-batch-http";
import { ApolloLink, concat } from 'apollo-link';


import CreateView from './views/CreateView'
import DetailView from './views/DetailView'
import ListView from './views/ListView'
import LoginView from './views/LoginView'
import LogoutView from './views/LogoutView'

const networkInterface = createBatchingNetworkInterface({
uri: 'http://localhost:8000/gql',
batchInterval: 10,
opts: {
credentials: 'same-origin',
},
})
import './App.css';
import logo from './logo.svg';

const authMiddleware = new ApolloLink((operation, forward) => {
const token = localStorage.getItem('token') ? localStorage.getItem('token') : null;
operation.setContext({
headers: {
authorization: `JWT ${token}`
}
});
return forward(operation)
});

networkInterface.use([
{
applyBatchMiddleware(req, next) {
if (!req.options.headers) {
req.options.headers = {}
}

const token = localStorage.getItem('token')
? localStorage.getItem('token')
: null
req.options.headers['authorization'] = `JWT ${token}`
next()
},
},
])
const link = new BatchHttpLink({ uri: "http://localhost:8000/gql", credentials: 'same-origin' });

const client = new ApolloClient({
networkInterface: networkInterface,
})
link: concat(authMiddleware, link),
cache: new InMemoryCache(),
connectToDevTools: true,
});

class App extends Component {
render() {
Expand Down
Loading