Compare commits
11 Commits
9573c3b8ff
...
purchase_s
Author | SHA1 | Date | |
---|---|---|---|
a30c54ef44
|
|||
d9fbb4b896
|
|||
4ff3692606
|
|||
8289c48896
|
|||
d1b9202337
|
|||
fde93cb875
|
|||
d1c3ac6079
|
|||
d921c2d8a6
|
|||
52513e1ed8
|
|||
cb380814a7
|
|||
5ef8c07f30
|
2
.github/workflows/build-docker.yml
vendored
2
.github/workflows/build-docker.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
poetry install
|
poetry install
|
||||||
poetry env info
|
poetry env info
|
||||||
poetry run python manage.py migrate
|
poetry run python manage.py migrate
|
||||||
poetry run pytest
|
PROD=1 poetry run pytest
|
||||||
build-and-push:
|
build-and-push:
|
||||||
needs: test
|
needs: test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,7 +1,7 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
.pytest_cache
|
.pytest_cache
|
||||||
.venv
|
.venv/
|
||||||
node_modules
|
node_modules
|
||||||
package-lock.json
|
package-lock.json
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
|
@ -91,6 +91,7 @@ class PurchaseForm(forms.ModelForm):
|
|||||||
"date_purchased",
|
"date_purchased",
|
||||||
"date_refunded",
|
"date_refunded",
|
||||||
"date_finished",
|
"date_finished",
|
||||||
|
"status",
|
||||||
"price",
|
"price",
|
||||||
"price_currency",
|
"price_currency",
|
||||||
"ownership_type",
|
"ownership_type",
|
||||||
|
1
games/graphql/mutations/__init__.py
Normal file
1
games/graphql/mutations/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .game import Mutation as GameMutation
|
29
games/graphql/mutations/game.py
Normal file
29
games/graphql/mutations/game.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import graphene
|
||||||
|
|
||||||
|
from games.graphql.types import Game
|
||||||
|
from games.models import Game as GameModel
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateGameMutation(graphene.Mutation):
|
||||||
|
class Arguments:
|
||||||
|
id = graphene.ID(required=True)
|
||||||
|
name = graphene.String()
|
||||||
|
year_released = graphene.Int()
|
||||||
|
wikidata = graphene.String()
|
||||||
|
|
||||||
|
game = graphene.Field(Game)
|
||||||
|
|
||||||
|
def mutate(self, info, id, name=None, year_released=None, wikidata=None):
|
||||||
|
game_instance = GameModel.objects.get(pk=id)
|
||||||
|
if name is not None:
|
||||||
|
game_instance.name = name
|
||||||
|
if year_released is not None:
|
||||||
|
game_instance.year_released = year_released
|
||||||
|
if wikidata is not None:
|
||||||
|
game_instance.wikidata = wikidata
|
||||||
|
game_instance.save()
|
||||||
|
return UpdateGameMutation(game=game_instance)
|
||||||
|
|
||||||
|
|
||||||
|
class Mutation(graphene.ObjectType):
|
||||||
|
update_game = UpdateGameMutation.Field()
|
6
games/graphql/queries/__init__.py
Normal file
6
games/graphql/queries/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from .device import Query as DeviceQuery
|
||||||
|
from .edition import Query as EditionQuery
|
||||||
|
from .game import Query as GameQuery
|
||||||
|
from .platform import Query as PlatformQuery
|
||||||
|
from .purchase import Query as PurchaseQuery
|
||||||
|
from .session import Query as SessionQuery
|
11
games/graphql/queries/device.py
Normal file
11
games/graphql/queries/device.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import graphene
|
||||||
|
|
||||||
|
from games.graphql.types import Device
|
||||||
|
from games.models import Device as DeviceModel
|
||||||
|
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
devices = graphene.List(Device)
|
||||||
|
|
||||||
|
def resolve_devices(self, info, **kwargs):
|
||||||
|
return DeviceModel.objects.all()
|
11
games/graphql/queries/edition.py
Normal file
11
games/graphql/queries/edition.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import graphene
|
||||||
|
|
||||||
|
from games.graphql.types import Edition
|
||||||
|
from games.models import Game as EditionModel
|
||||||
|
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
editions = graphene.List(Edition)
|
||||||
|
|
||||||
|
def resolve_editions(self, info, **kwargs):
|
||||||
|
return EditionModel.objects.all()
|
18
games/graphql/queries/game.py
Normal file
18
games/graphql/queries/game.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import graphene
|
||||||
|
|
||||||
|
from games.graphql.types import Game
|
||||||
|
from games.models import Game as GameModel
|
||||||
|
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
games = graphene.List(Game)
|
||||||
|
game_by_name = graphene.Field(Game, name=graphene.String(required=True))
|
||||||
|
|
||||||
|
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
|
11
games/graphql/queries/platform.py
Normal file
11
games/graphql/queries/platform.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import graphene
|
||||||
|
|
||||||
|
from games.graphql.types import Platform
|
||||||
|
from games.models import Platform as PlatformModel
|
||||||
|
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
platforms = graphene.List(Platform)
|
||||||
|
|
||||||
|
def resolve_platforms(self, info, **kwargs):
|
||||||
|
return PlatformModel.objects.all()
|
11
games/graphql/queries/purchase.py
Normal file
11
games/graphql/queries/purchase.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import graphene
|
||||||
|
|
||||||
|
from games.graphql.types import Purchase
|
||||||
|
from games.models import Purchase as PurchaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
purchases = graphene.List(Purchase)
|
||||||
|
|
||||||
|
def resolve_purchases(self, info, **kwargs):
|
||||||
|
return PurchaseModel.objects.all()
|
11
games/graphql/queries/session.py
Normal file
11
games/graphql/queries/session.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import graphene
|
||||||
|
|
||||||
|
from games.graphql.types import Session
|
||||||
|
from games.models import Session as SessionModel
|
||||||
|
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
sessions = graphene.List(Session)
|
||||||
|
|
||||||
|
def resolve_sessions(self, info, **kwargs):
|
||||||
|
return SessionModel.objects.all()
|
44
games/graphql/types.py
Normal file
44
games/graphql/types.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
from graphene_django import DjangoObjectType
|
||||||
|
|
||||||
|
from games.models import Device as DeviceModel
|
||||||
|
from games.models import Edition as EditionModel
|
||||||
|
from games.models import Game as GameModel
|
||||||
|
from games.models import Platform as PlatformModel
|
||||||
|
from games.models import Purchase as PurchaseModel
|
||||||
|
from games.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__"
|
26
games/migrations/0034_purchase_status.py
Normal file
26
games/migrations/0034_purchase_status.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2023-12-22 10:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("games", "0033_alter_edition_unique_together"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="purchase",
|
||||||
|
name="status",
|
||||||
|
field=models.IntegerField(
|
||||||
|
choices=[
|
||||||
|
(0, "Unplayed"),
|
||||||
|
(1, "Playing"),
|
||||||
|
(2, "Dropped"),
|
||||||
|
(3, "Finished"),
|
||||||
|
],
|
||||||
|
default=0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
22
games/migrations/0035_auto_20231222_1109.py
Normal file
22
games/migrations/0035_auto_20231222_1109.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2023-12-22 10:09
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
from games.models import Purchase
|
||||||
|
|
||||||
|
|
||||||
|
def set_default_state(apps, schema_editor):
|
||||||
|
Purchase.objects.filter(session__isnull=False).update(
|
||||||
|
status=Purchase.PurchaseState.PLAYING
|
||||||
|
)
|
||||||
|
Purchase.objects.filter(date_finished__isnull=False).update(
|
||||||
|
status=Purchase.PurchaseState.FINISHED
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("games", "0034_purchase_status"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [migrations.RunPython(set_default_state)]
|
@ -133,6 +133,25 @@ class Purchase(models.Model):
|
|||||||
)
|
)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class PurchaseState(models.IntegerChoices):
|
||||||
|
UNPLAYED = (
|
||||||
|
0,
|
||||||
|
"Unplayed",
|
||||||
|
)
|
||||||
|
PLAYING = (1, "Playing")
|
||||||
|
DROPPED = (
|
||||||
|
2,
|
||||||
|
"Dropped",
|
||||||
|
)
|
||||||
|
FINISHED = (
|
||||||
|
3,
|
||||||
|
"Finished",
|
||||||
|
)
|
||||||
|
|
||||||
|
status = models.IntegerField(
|
||||||
|
choices=PurchaseState.choices, default=PurchaseState.UNPLAYED
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
additional_info = [
|
additional_info = [
|
||||||
self.get_type_display() if self.type != Purchase.GAME else "",
|
self.get_type_display() if self.type != Purchase.GAME else "",
|
||||||
|
30
games/schema.py
Normal file
30
games/schema.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import graphene
|
||||||
|
|
||||||
|
from games.graphql.mutations import GameMutation
|
||||||
|
from games.graphql.queries import (
|
||||||
|
DeviceQuery,
|
||||||
|
EditionQuery,
|
||||||
|
GameQuery,
|
||||||
|
PlatformQuery,
|
||||||
|
PurchaseQuery,
|
||||||
|
SessionQuery,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Query(
|
||||||
|
GameQuery,
|
||||||
|
EditionQuery,
|
||||||
|
DeviceQuery,
|
||||||
|
PlatformQuery,
|
||||||
|
PurchaseQuery,
|
||||||
|
SessionQuery,
|
||||||
|
graphene.ObjectType,
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Mutation(GameMutation, graphene.ObjectType):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
schema = graphene.Schema(query=Query, mutation=Mutation)
|
@ -75,10 +75,6 @@ function syncSelectInputUntilChanged(syncData, parentSelector = document) {
|
|||||||
* @param {string} property - The property to retrieve the value from.
|
* @param {string} property - The property to retrieve the value from.
|
||||||
*/
|
*/
|
||||||
function getValueFromProperty(sourceElement, property) {
|
function getValueFromProperty(sourceElement, property) {
|
||||||
let source =
|
|
||||||
sourceElement instanceof HTMLSelectElement
|
|
||||||
? sourceElement.selectedOptions[0]
|
|
||||||
: sourceElement;
|
|
||||||
let source =
|
let source =
|
||||||
sourceElement instanceof HTMLSelectElement
|
sourceElement instanceof HTMLSelectElement
|
||||||
? sourceElement.selectedOptions[0]
|
? sourceElement.selectedOptions[0]
|
||||||
|
@ -137,6 +137,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="px-2 sm:px-4 md:px-6 md:py-2 purchase-name truncate max-w-20char">Name</th>
|
<th class="px-2 sm:px-4 md:px-6 md:py-2 purchase-name truncate max-w-20char">Name</th>
|
||||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">Date</th>
|
<th class="px-2 sm:px-4 md:px-6 md:py-2">Date</th>
|
||||||
|
<th class="px-2 sm:px-4 md:px-6 md:py-2">Playtime</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -153,6 +154,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_finished | date:"d/m/Y" }}</td>
|
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_finished | date:"d/m/Y" }}</td>
|
||||||
|
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.formatted_playtime }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -371,6 +371,16 @@ def stats(request, year: int = 0):
|
|||||||
)
|
)
|
||||||
|
|
||||||
purchases_finished_this_year = Purchase.objects.filter(date_finished__year=year)
|
purchases_finished_this_year = Purchase.objects.filter(date_finished__year=year)
|
||||||
|
purchases_finished_this_year_with_playtime = purchases_finished_this_year.annotate(
|
||||||
|
total_playtime=Sum(
|
||||||
|
F("session__duration_calculated") + F("session__duration_manual")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for purchase in purchases_finished_this_year_with_playtime:
|
||||||
|
formatted_playtime = format_duration(purchase.total_playtime, "%2.0H")
|
||||||
|
setattr(purchase, "formatted_playtime", formatted_playtime)
|
||||||
|
|
||||||
purchases_finished_this_year_released_this_year = (
|
purchases_finished_this_year_released_this_year = (
|
||||||
purchases_finished_this_year.filter(edition__year_released=year).order_by(
|
purchases_finished_this_year.filter(edition__year_released=year).order_by(
|
||||||
"date_finished"
|
"date_finished"
|
||||||
@ -442,7 +452,7 @@ def stats(request, year: int = 0):
|
|||||||
"spent_per_game": int(
|
"spent_per_game": int(
|
||||||
safe_division(total_spent, this_year_purchases_without_refunded.count())
|
safe_division(total_spent, this_year_purchases_without_refunded.count())
|
||||||
),
|
),
|
||||||
"all_finished_this_year": purchases_finished_this_year.order_by(
|
"all_finished_this_year": purchases_finished_this_year_with_playtime.order_by(
|
||||||
"date_finished"
|
"date_finished"
|
||||||
),
|
),
|
||||||
"this_year_finished_this_year": purchases_finished_this_year_released_this_year.order_by(
|
"this_year_finished_this_year": purchases_finished_this_year_released_this_year.order_by(
|
||||||
@ -479,6 +489,7 @@ def stats(request, year: int = 0):
|
|||||||
highest_session_average_game.session_average, "%2.0Hh %2.0mm"
|
highest_session_average_game.session_average, "%2.0Hh %2.0mm"
|
||||||
),
|
),
|
||||||
"highest_session_average_game": highest_session_average_game,
|
"highest_session_average_game": highest_session_average_game,
|
||||||
|
"title": f"{year} Stats",
|
||||||
}
|
}
|
||||||
|
|
||||||
request.session["return_path"] = request.path
|
request.session["return_path"] = request.path
|
||||||
|
112
poetry.lock
generated
112
poetry.lock
generated
@ -1,5 +1,19 @@
|
|||||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
# 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]]
|
[[package]]
|
||||||
name = "asgiref"
|
name = "asgiref"
|
||||||
version = "3.7.2"
|
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)"]
|
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)"]
|
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]]
|
[[package]]
|
||||||
name = "gunicorn"
|
name = "gunicorn"
|
||||||
version = "20.1.0"
|
version = "20.1.0"
|
||||||
@ -558,6 +641,22 @@ nodeenv = ">=0.11.1"
|
|||||||
pyyaml = ">=5.1"
|
pyyaml = ">=5.1"
|
||||||
virtualenv = ">=20.10.0"
|
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]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "7.4.3"
|
version = "7.4.3"
|
||||||
@ -777,6 +876,17 @@ dev = ["build", "flake8"]
|
|||||||
doc = ["sphinx"]
|
doc = ["sphinx"]
|
||||||
test = ["pytest", "pytest-cov"]
|
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]]
|
[[package]]
|
||||||
name = "tqdm"
|
name = "tqdm"
|
||||||
version = "4.66.1"
|
version = "4.66.1"
|
||||||
@ -877,4 +987,4 @@ watchdog = ["watchdog (>=2.3)"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "498b3358998a9f3bbfb74fae7d6a90de7b55b9bdc76845bce52f65785afd0c1e"
|
content-hash = "e864dc8abf6c84e5bb16ac2aa937c2a70561d15f3e8a1459866b9d6507e8773e"
|
||||||
|
@ -12,6 +12,7 @@ python = "^3.12"
|
|||||||
django = "^4.2.0"
|
django = "^4.2.0"
|
||||||
gunicorn = "^20.1.0"
|
gunicorn = "^20.1.0"
|
||||||
uvicorn = "^0.20.0"
|
uvicorn = "^0.20.0"
|
||||||
|
graphene-django = "^3.1.5"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^22.12.0"
|
black = "^22.12.0"
|
||||||
|
35
tests/test_graphql.py
Normal file
35
tests/test_graphql.py
Normal file
@ -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(),
|
||||||
|
)
|
@ -38,8 +38,11 @@ INSTALLED_APPS = [
|
|||||||
"django.contrib.sessions",
|
"django.contrib.sessions",
|
||||||
"django.contrib.messages",
|
"django.contrib.messages",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
|
"graphene_django",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
GRAPHENE = {"SCHEMA": "games.schema.schema"}
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
INSTALLED_APPS.append("django_extensions")
|
INSTALLED_APPS.append("django_extensions")
|
||||||
INSTALLED_APPS.append("django.contrib.admin")
|
INSTALLED_APPS.append("django.contrib.admin")
|
||||||
|
@ -16,11 +16,14 @@ Including another URLconf
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
|
from graphene_django.views import GraphQLView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", RedirectView.as_view(url="/tracker")),
|
path("", RedirectView.as_view(url="/tracker")),
|
||||||
path("tracker/", include("games.urls")),
|
path("tracker/", include("games.urls")),
|
||||||
|
path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
Reference in New Issue
Block a user