-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/graphql scaffold #81
base: develop
Are you sure you want to change the base?
Changes from all commits
5679c20
b89e603
3740504
8ad187d
11b7922
9a468aa
cf122bf
07c3be3
0a2be43
59a7f6e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from django.contrib import admin | ||
|
||
# Register your models here. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class ApiConfig(AppConfig): | ||
default_auto_field = "django.db.models.BigAutoField" | ||
name = "api" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
from graphene import ObjectType, Schema | ||
|
||
from source.mutations.DeleteSourceMutation import DeleteSourceMutation | ||
from source.queries import SourceQueries | ||
|
||
from source.mutations.UpdateOrCreateSourceMutation import UpdateOrCreateSourceMutation | ||
|
||
|
||
class Query(SourceQueries, ObjectType): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not everyone is a fan of inheritance like this. We can also write all the queries (and their resolvers here), but that usually ends up being a very long list. |
||
pass | ||
|
||
|
||
class Mutation(ObjectType): | ||
update_or_create_source = UpdateOrCreateSourceMutation.Field() | ||
delete_source = DeleteSourceMutation.Field() | ||
|
||
|
||
schema = Schema(query=Query, mutation=Mutation) |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This app includes some files generated by |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from django.db import models | ||
|
||
# Create your models here. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from django.test import TestCase | ||
|
||
# Create your tests here. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from django.conf import settings | ||
from django.urls import path | ||
from graphene_django.views import GraphQLView | ||
from django.views.decorators.csrf import csrf_exempt | ||
|
||
# TODO: Handle CSRF correctly. | ||
urlpatterns = [ | ||
path( | ||
"graphql", csrf_exempt(GraphQLView.as_view(graphiql=settings.ENABLE_GRAPHIQL)) | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from django.shortcuts import render | ||
|
||
# Create your views here. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,5 @@ pytest-django | |
pytest-xdist | ||
dj_rest_auth | ||
django-allauth | ||
graphene | ||
graphene-django |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from graphene import Mutation, ID, Boolean, ResolveInfo, String | ||
from source.models import Source | ||
|
||
|
||
class DeleteSourceMutation(Mutation): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The implementation of this looks fine, but deleting sources is quite destructive, so it may be better to restrict this to the admin interface. |
||
ok = Boolean(required=True) | ||
error = String() | ||
|
||
class Arguments: | ||
id = ID(required=True) | ||
|
||
@classmethod | ||
def mutate(cls, root: None, info: ResolveInfo, id: str): | ||
try: | ||
source = Source.objects.get(pk=id) | ||
except Source.DoesNotExist: | ||
return cls(ok=False, error="Source not found.") # type: ignore | ||
|
||
source.delete() | ||
|
||
return cls(ok=True) # type: ignore |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
from graphene import ID, Field, InputObjectType, List, Mutation, ResolveInfo, String | ||
|
||
from source.models import Source | ||
from source.types.SourceType import SourceType | ||
|
||
|
||
class UpdateCreateSourceInput(InputObjectType): | ||
id = ID() | ||
name = String(required=True) | ||
edition_author = String() | ||
edition_title = String() | ||
medieval_author = String() | ||
medieval_title = String() | ||
|
||
|
||
class UpdateOrCreateSourceMutation(Mutation): | ||
source = Field(SourceType) | ||
errors = List(String) | ||
|
||
class Arguments: | ||
input = UpdateCreateSourceInput(required=True) | ||
|
||
@classmethod | ||
def mutate(cls, root: None, info: ResolveInfo, input: UpdateCreateSourceInput): | ||
source_id = getattr(input, "id", None) | ||
|
||
if source_id: | ||
try: | ||
source = SourceType.get_queryset(Source.objects, info).get(pk=source_id) | ||
except Source.DoesNotExist: | ||
return cls(errors=["Source not found."]) # type: ignore | ||
else: | ||
source = Source() | ||
|
||
for field, value in input.items(): # type: ignore | ||
setattr(source, field, value) | ||
|
||
source.save() | ||
|
||
return cls(source=source) # type: ignore |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from graphene import ObjectType | ||
from graphene_django import DjangoListField | ||
|
||
from source.types.SourceType import SourceType | ||
|
||
|
||
class SourceQueries(ObjectType): | ||
sources = DjangoListField(SourceType) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DjangoListField is a handy util from |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from graphene import ResolveInfo | ||
from django.db.models import QuerySet | ||
from graphene_django import DjangoObjectType | ||
from source.models import Source | ||
|
||
|
||
class SourceType(DjangoObjectType): | ||
class Meta: | ||
model = Source | ||
|
||
@classmethod | ||
def get_queryset( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I usually define |
||
cls, queryset: QuerySet[Source], info: ResolveInfo | ||
) -> QuerySet[Source]: | ||
return queryset.all() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,3 +40,6 @@ testem.log | |
# System files | ||
.DS_Store | ||
Thumbs.db | ||
|
||
# Generated files | ||
/generated/*.* |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import type { CodegenConfig } from '@graphql-codegen/cli' | ||
|
||
const config: CodegenConfig = { | ||
schema: 'http://localhost:8000/api/graphql', | ||
generates: { | ||
'./generated/graphql.ts': { | ||
plugins: [ | ||
'typescript', | ||
'typescript-operations', | ||
'typescript-apollo-angular' | ||
], | ||
config: { | ||
addExplicitOverride: true | ||
} | ||
}, | ||
'./generated/schema.graphql': { | ||
plugins: ['schema-ast'] | ||
} | ||
}, | ||
ignoreNoDocuments: true, | ||
documents: [ | ||
"src/**/*.ts", | ||
"src/**/*.graphql", | ||
], | ||
} | ||
|
||
export default config | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { APOLLO_OPTIONS, ApolloModule } from 'apollo-angular'; | ||
import { HttpLink } from 'apollo-angular/http'; | ||
import { NgModule } from '@angular/core'; | ||
import { ApolloClientOptions, InMemoryCache } from '@apollo/client/core'; | ||
|
||
const uri = 'http://localhost:8000/api/graphql'; // <-- add the URL of the GraphQL server here | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This URL is now hardcoded. I can move it to I'm sure you have run into this issue before in other projects, so any advice would be helpful! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In most cases, you use absolute URLs without a hostname (+ port), e.g. If you're accessing the frontend development server on On production, you don't have this kind of proxying, as the endpoints for the frontend client and the backend API will have the same origin (e.g. The example in the apollo-angular documentation uses a URL like this, did you try that? |
||
export function createApollo(httpLink: HttpLink): ApolloClientOptions<any> { | ||
return { | ||
link: httpLink.create({ uri }), | ||
cache: new InMemoryCache(), | ||
}; | ||
} | ||
|
||
@NgModule({ | ||
exports: [ApolloModule], | ||
providers: [ | ||
{ | ||
provide: APOLLO_OPTIONS, | ||
useFactory: createApollo, | ||
deps: [HttpLink], | ||
}, | ||
], | ||
}) | ||
export class GraphQLModule {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this app seem to be specifically handling the graphql portion of the API, perhaps
graphql
would be a more appropriate name thanapi
.