From 5ef8c07f306158fac8be77515889cc2eeabb6dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Sat, 18 Nov 2023 21:09:27 +0100 Subject: [PATCH] Initial working API --- games/schema.py | 82 +++++++++++++++++++++++++++++ poetry.lock | 112 +++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + tests/test_graphql.py | 35 +++++++++++++ timetracker/settings.py | 3 ++ timetracker/urls.py | 3 ++ 6 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 games/schema.py create mode 100644 tests/test_graphql.py diff --git a/games/schema.py b/games/schema.py new file mode 100644 index 0000000..a5d2908 --- /dev/null +++ b/games/schema.py @@ -0,0 +1,82 @@ +import graphene +from graphene_django import DjangoObjectType + +from .models import Device as DeviceModel +from .models import Edition as EditionModel +from .models import Game as GameModel +from .models import Platform as PlatformModel +from .models import Purchase as PurchaseModel +from .models import Session as SessionModel + + +class Game(DjangoObjectType): + class Meta: + model = GameModel + fields = "__all__" + + +class Edition(DjangoObjectType): + class Meta: + model = EditionModel + fields = "__all__" + + +class Purchase(DjangoObjectType): + class Meta: + model = PurchaseModel + fields = "__all__" + + +class Session(DjangoObjectType): + class Meta: + model = SessionModel + fields = "__all__" + + +class Platform(DjangoObjectType): + class Meta: + model = PlatformModel + fields = "__all__" + + +class Device(DjangoObjectType): + class Meta: + model = DeviceModel + fields = "__all__" + + +class Query(graphene.ObjectType): + games = graphene.List(Game) + game_by_name = graphene.Field(Game, name=graphene.String(required=True)) + purchases = graphene.List(Purchase) + editions = graphene.List(Edition) + sessions = graphene.List(Session) + platforms = graphene.List(Platform) + devices = graphene.List(Device) + + def resolve_games(self, info, **kwargs): + return GameModel.objects.all() + + def resolve_game_by_name(self, info, name): + try: + return GameModel.objects.get(name=name) + except GameModel.DoesNotExist: + return None + + def resolve_editions(self, info, **kwargs): + return EditionModel.objects.all() + + def resolve_purchases(self, info, **kwargs): + return PurchaseModel.objects.all() + + def resolve_sessions(self, info, **kwargs): + return SessionModel.objects.all() + + def resolve_platforms(self, info, **kwargs): + return PlatformModel.objects.all() + + def resolve_devices(self, info, **kwargs): + return DeviceModel.objects.all() + + +schema = graphene.Schema(query=Query) diff --git a/poetry.lock b/poetry.lock index 7afd4c3..e4a8df0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,19 @@ # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +[[package]] +name = "aniso8601" +version = "9.0.1" +description = "A library for parsing ISO 8601 strings." +optional = false +python-versions = "*" +files = [ + {file = "aniso8601-9.0.1-py2.py3-none-any.whl", hash = "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f"}, + {file = "aniso8601-9.0.1.tar.gz", hash = "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973"}, +] + +[package.extras] +dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"] + [[package]] name = "asgiref" version = "3.7.2" @@ -222,6 +236,75 @@ docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1 testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] typing = ["typing-extensions (>=4.8)"] +[[package]] +name = "graphene" +version = "3.3" +description = "GraphQL Framework for Python" +optional = false +python-versions = "*" +files = [ + {file = "graphene-3.3-py2.py3-none-any.whl", hash = "sha256:bb3810be33b54cb3e6969506671eb72319e8d7ba0d5ca9c8066472f75bf35a38"}, + {file = "graphene-3.3.tar.gz", hash = "sha256:529bf40c2a698954217d3713c6041d69d3f719ad0080857d7ee31327112446b0"}, +] + +[package.dependencies] +aniso8601 = ">=8,<10" +graphql-core = ">=3.1,<3.3" +graphql-relay = ">=3.1,<3.3" + +[package.extras] +dev = ["black (==22.3.0)", "coveralls (>=3.3,<4)", "flake8 (>=4,<5)", "iso8601 (>=1,<2)", "mock (>=4,<5)", "pytest (>=6,<7)", "pytest-asyncio (>=0.16,<2)", "pytest-benchmark (>=3.4,<4)", "pytest-cov (>=3,<4)", "pytest-mock (>=3,<4)", "pytz (==2022.1)", "snapshottest (>=0.6,<1)"] +test = ["coveralls (>=3.3,<4)", "iso8601 (>=1,<2)", "mock (>=4,<5)", "pytest (>=6,<7)", "pytest-asyncio (>=0.16,<2)", "pytest-benchmark (>=3.4,<4)", "pytest-cov (>=3,<4)", "pytest-mock (>=3,<4)", "pytz (==2022.1)", "snapshottest (>=0.6,<1)"] + +[[package]] +name = "graphene-django" +version = "3.1.5" +description = "Graphene Django integration" +optional = false +python-versions = "*" +files = [ + {file = "graphene-django-3.1.5.tar.gz", hash = "sha256:abe42f820b9731d94bebff6d73088d0dc2ffb8c8863a6d7bf3d378412d866a3b"}, + {file = "graphene_django-3.1.5-py2.py3-none-any.whl", hash = "sha256:2e42742fae21fa50e514f3acae26a9bc6cb5e51c179a97b3db5390ff258ca816"}, +] + +[package.dependencies] +Django = ">=3.2" +graphene = ">=3.0,<4" +graphql-core = ">=3.1.0,<4" +graphql-relay = ">=3.1.1,<4" +promise = ">=2.1" +text-unidecode = "*" + +[package.extras] +dev = ["black (==23.7.0)", "coveralls", "django-filter (>=22.1)", "djangorestframework (>=3.6.3)", "mock", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-django (>=4.5.2)", "pytest-random-order", "pytz", "ruff (==0.0.283)"] +rest-framework = ["djangorestframework (>=3.6.3)"] +test = ["coveralls", "django-filter (>=22.1)", "djangorestframework (>=3.6.3)", "mock", "pytest (>=7.3.1)", "pytest-cov", "pytest-django (>=4.5.2)", "pytest-random-order", "pytz"] + +[[package]] +name = "graphql-core" +version = "3.2.3" +description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "graphql-core-3.2.3.tar.gz", hash = "sha256:06d2aad0ac723e35b1cb47885d3e5c45e956a53bc1b209a9fc5369007fe46676"}, + {file = "graphql_core-3.2.3-py3-none-any.whl", hash = "sha256:5766780452bd5ec8ba133f8bf287dc92713e3868ddd83aee4faab9fc3e303dc3"}, +] + +[[package]] +name = "graphql-relay" +version = "3.2.0" +description = "Relay library for graphql-core" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "graphql-relay-3.2.0.tar.gz", hash = "sha256:1ff1c51298356e481a0be009ccdff249832ce53f30559c1338f22a0e0d17250c"}, + {file = "graphql_relay-3.2.0-py3-none-any.whl", hash = "sha256:c9b22bd28b170ba1fe674c74384a8ff30a76c8e26f88ac3aa1584dd3179953e5"}, +] + +[package.dependencies] +graphql-core = ">=3.2,<3.3" + [[package]] name = "gunicorn" version = "20.1.0" @@ -558,6 +641,22 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "promise" +version = "2.3" +description = "Promises/A+ implementation for Python" +optional = false +python-versions = "*" +files = [ + {file = "promise-2.3.tar.gz", hash = "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0"}, +] + +[package.dependencies] +six = "*" + +[package.extras] +test = ["coveralls", "futures", "mock", "pytest (>=2.7.3)", "pytest-benchmark", "pytest-cov"] + [[package]] name = "pytest" version = "7.4.3" @@ -777,6 +876,17 @@ dev = ["build", "flake8"] doc = ["sphinx"] test = ["pytest", "pytest-cov"] +[[package]] +name = "text-unidecode" +version = "1.3" +description = "The most basic Text::Unidecode port" +optional = false +python-versions = "*" +files = [ + {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, + {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, +] + [[package]] name = "tqdm" version = "4.66.1" @@ -877,4 +987,4 @@ watchdog = ["watchdog (>=2.3)"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "498b3358998a9f3bbfb74fae7d6a90de7b55b9bdc76845bce52f65785afd0c1e" +content-hash = "49b33333953d875c6c2a26ffd1a1a2d21f75e06fe59e6619ba2900e39d2cf1bf" diff --git a/pyproject.toml b/pyproject.toml index e13ac5a..d17eda8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ python = "^3.12" django = "^4.2.0" gunicorn = "^20.1.0" uvicorn = "^0.20.0" +graphene-django = "^3.1.5" [tool.poetry.group.dev.dependencies] black = "^22.12.0" diff --git a/tests/test_graphql.py b/tests/test_graphql.py new file mode 100644 index 0000000..24471a8 --- /dev/null +++ b/tests/test_graphql.py @@ -0,0 +1,35 @@ +import json +import os + +import django + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "timetracker.settings") +django.setup() + +from django.test import TestCase +from graphene_django.utils.testing import GraphQLTestCase + +from games import schema +from games.models import Game + + +class GameAPITestCase(GraphQLTestCase): + GRAPHENE_SCHEMA = schema.schema + + def test_query_all_games(self): + response = self.query( + """ + query { + games { + id + name + } + } + """ + ) + + self.assertResponseNoErrors(response) + self.assertEqual( + len(json.loads(response.content)["data"]["games"]), + Game.objects.count(), + ) diff --git a/timetracker/settings.py b/timetracker/settings.py index 7238b9b..6386976 100644 --- a/timetracker/settings.py +++ b/timetracker/settings.py @@ -38,8 +38,11 @@ INSTALLED_APPS = [ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", + "graphene_django", ] +GRAPHENE = {"SCHEMA": "games.schema.schema"} + if DEBUG: INSTALLED_APPS.append("django_extensions") INSTALLED_APPS.append("django.contrib.admin") diff --git a/timetracker/urls.py b/timetracker/urls.py index 0515e99..3f91c05 100644 --- a/timetracker/urls.py +++ b/timetracker/urls.py @@ -16,11 +16,14 @@ Including another URLconf from django.conf import settings from django.contrib import admin from django.urls import include, path +from django.views.decorators.csrf import csrf_exempt from django.views.generic import RedirectView +from graphene_django.views import GraphQLView urlpatterns = [ path("", RedirectView.as_view(url="/tracker")), path("tracker/", include("games.urls")), + path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))), ] if settings.DEBUG: