8 Commits

Author SHA1 Message Date
6e4dd4bf96 Organize better
All checks were successful
Django CI/CD / test (push) Successful in 59s
Django CI/CD / build-and-push (push) Has been skipped
2023-11-29 21:58:33 +01:00
7a6a56a589 Revert "Move GraphQL to separata app"
This reverts commit 6ac4209492.
2023-11-29 21:18:02 +01:00
63c70d4b31 Revert "Add UpdateGameMutation"
This reverts commit e9e61403a9.
2023-11-29 21:17:41 +01:00
e9e61403a9 Add UpdateGameMutation 2023-11-29 21:05:34 +01:00
6ac4209492 Move GraphQL to separata app 2023-11-29 21:04:35 +01:00
f0ac07dc6d Allow DLC to have date_finished set
All checks were successful
Django CI/CD / test (push) Successful in 1m11s
Django CI/CD / build-and-push (push) Has been skipped
2023-11-20 21:20:10 +01:00
d947c6efe1 Remote JavaScript files 2023-11-20 21:15:18 +01:00
600c0d284c Initial working API
All checks were successful
Django CI/CD / test (push) Successful in 1m5s
Django CI/CD / build-and-push (push) Has been skipped
2023-11-18 21:09:27 +01:00
20 changed files with 110 additions and 295 deletions

View File

@ -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
PROD=1 poetry run pytest poetry run pytest
build-and-push: build-and-push:
needs: test needs: test
runs-on: ubuntu-latest runs-on: ubuntu-latest

4
.gitignore vendored
View File

@ -1,9 +1,9 @@
__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
/static/ /static/
dist/ dist/

View File

@ -8,8 +8,3 @@ repos:
hooks: hooks:
- id: isort - id: isort
name: isort (python) name: isort (python)
- repo: https://github.com/Riverside-Healthcare/djLint
rev: v1.34.0
hooks:
- id: djlint-reformat-django
- id: djlint-django

View File

@ -23,12 +23,6 @@
font-style: normal; font-style: normal;
} }
a:hover {
text-decoration-color: #ff4400;
color: rgb(254, 185, 160);
transition: all 0.2s ease-out;
}
form label { form label {
@apply dark:text-slate-400; @apply dark:text-slate-400;
} }

View File

@ -91,7 +91,6 @@ 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",

View File

@ -1,17 +0,0 @@
# Generated by Django 4.2.7 on 2023-11-28 13:43
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("games", "0032_alter_session_options_session_modified_at_and_more"),
]
operations = [
migrations.AlterUniqueTogether(
name="edition",
unique_together={("name", "platform", "year_released")},
),
]

View File

@ -1,26 +0,0 @@
# 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,
),
),
]

View File

@ -1,22 +0,0 @@
# 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)]

View File

@ -34,7 +34,7 @@ class Game(models.Model):
class Edition(models.Model): class Edition(models.Model):
class Meta: class Meta:
unique_together = [["name", "platform", "year_released"]] unique_together = [["name", "platform"]]
game = models.ForeignKey("Game", on_delete=models.CASCADE) game = models.ForeignKey("Game", on_delete=models.CASCADE)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
@ -133,25 +133,6 @@ 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 "",

View File

@ -1173,12 +1173,6 @@ select {
font-style: normal; font-style: normal;
} }
a:hover {
text-decoration-color: #ff4400;
color: rgb(254, 185, 160);
transition: all 0.2s ease-out;
}
:is(.dark form label) { :is(.dark form label) {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(148 163 184 / var(--tw-text-opacity)); color: rgb(148 163 184 / var(--tw-text-opacity));
@ -1483,6 +1477,10 @@ th label {
display: block; display: block;
} }
.md\:w-1\/2 {
width: 50%;
}
.md\:w-auto { .md\:w-auto {
width: auto; width: auto;
} }

View File

@ -75,6 +75,10 @@ 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]
@ -201,7 +205,7 @@ export {
syncSelectInputUntilChanged, syncSelectInputUntilChanged,
getEl, getEl,
conditionalElementHandler, conditionalElementHandler,
disableElementsWhenValueNotEqual, disableElementsWhenFalse,
disableElementsWhenTrue, disableElementsWhenTrue,
getValueFromProperty, getValueFromProperty,
}; };

View File

@ -16,7 +16,7 @@
{% endif %} {% endif %}
{% if field.name == "timestamp_start" or field.name == "timestamp_end" %} {% if field.name == "timestamp_start" or field.name == "timestamp_end" %}
<td> <td>
<div class="basic-button-container" hx-boost="false"> <div class="basic-button-container">
<button class="basic-button" data-target="{{ field.name }}" data-type="now">Set to now</button> <button class="basic-button" data-target="{{ field.name }}" data-type="now">Set to now</button>
<button class="basic-button" <button class="basic-button"
data-target="{{ field.name }}" data-target="{{ field.name }}"

View File

@ -14,23 +14,16 @@
<script src="{% static 'js/htmx.min.js' %}"></script> <script src="{% static 'js/htmx.min.js' %}"></script>
<link rel="stylesheet" href="{% static 'base.css' %}" /> <link rel="stylesheet" href="{% static 'base.css' %}" />
</head> </head>
<body class="dark" hx-indicator="#indicator"> <body class="dark" hx-indicator="#indicator" hx-boost="true">
<img id="indicator" <img id="indicator"
src="{% static 'icons/loading.png' %}" src="{% static 'icons/loading.png' %}"
class="absolute right-3 top-3 animate-spin htmx-indicator" class="absolute right-3 top-3 animate-spin htmx-indicator" />
height="24"
width="24"
alt="loading indicator" />
<div class="dark:bg-gray-800 min-h-screen"> <div class="dark:bg-gray-800 min-h-screen">
<nav class="mb-4 bg-white dark:bg-gray-900 border-gray-200 rounded"> <nav class="mb-4 bg-white dark:bg-gray-900 border-gray-200 rounded">
<div class="container flex flex-wrap items-center justify-between mx-auto"> <div class="container flex flex-wrap items-center justify-between mx-auto">
<a href="{% url 'list_sessions_recent' %}" class="flex items-center"> <a href="{% url 'list_sessions_recent' %}" class="flex items-center">
<span class="text-4xl"> <span class="text-4xl">
<img src="{% static 'icons/schedule.png' %}" <img src="{% static 'icons/schedule.png' %}" width="48" class="mr-4" />
height="48"
width="48"
alt="Timetracker Logo"
class="mr-4" />
</span> </span>
<span class="self-center text-xl font-semibold whitespace-nowrap text-white">Timetracker</span> <span class="self-center text-xl font-semibold whitespace-nowrap text-white">Timetracker</span>
</a> </a>

View File

@ -18,82 +18,74 @@
</select> </select>
</form> </form>
</div> </div>
<h1 class="text-5xl text-center my-6">Playtime</h1> <div class="flex flex-column flex-wrap justify-center">
<table class="responsive-table"> <div class="md:w-1/2">
<tbody> <h1 class="text-5xl text-center my-6">Playtime</h1>
<tr> <table class="responsive-table">
<td class="px-2 sm:px-4 md:px-6 md:py-2">Hours</td> <tbody>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_hours }}</td> <tr>
</tr> <td class="px-2 sm:px-4 md:px-6 md:py-2">Hours</td>
<tr> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_hours }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Sessions</td> </tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_sessions }}</td> <tr>
</tr> <td class="px-2 sm:px-4 md:px-6 md:py-2">Sessions</td>
<tr> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_sessions }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Days</td> </tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ unique_days }} ({{ unique_days_percent }}%)</td> <tr>
</tr> <td class="px-2 sm:px-4 md:px-6 md:py-2">Days</td>
<tr> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ unique_days }} ({{ unique_days_percent }}%)</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Games</td> </tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_games }}</td> <tr>
</tr> <td class="px-2 sm:px-4 md:px-6 md:py-2">Games</td>
<tr> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_games }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Games ({{ year }})</td> </tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_2023_games }}</td> <tr>
</tr> <td class="px-2 sm:px-4 md:px-6 md:py-2">Games ({{ year }})</td>
<tr> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_2023_games }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Finished</td> </tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ all_finished_this_year.count }}</td> <tr>
</tr> <td class="px-2 sm:px-4 md:px-6 md:py-2">Finished</td>
<tr> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ all_finished_this_year.count }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Finished ({{ year }})</td> </tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ this_year_finished_this_year.count }}</td> <tr>
</tr> <td class="px-2 sm:px-4 md:px-6 md:py-2">Finished ({{ year }})</td>
<tr> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ this_year_finished_this_year.count }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Longest session</td> </tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ longest_session_time }} ({{ longest_session_game }})</td> </tbody>
</tr> </table>
<tr> </div>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Most sessions</td> <div class="md:w-1/2">
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ highest_session_count }} ({{ highest_session_count_game }})</td> <h1 class="text-5xl text-center my-6">Purchases</h1>
</tr> <table class="responsive-table">
<tr> <tbody>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Highest session average</td> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono"> <td class="px-2 sm:px-4 md:px-6 md:py-2">Total</td>
{{ highest_session_average }} ({{ highest_session_average_game }}) <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ all_purchased_this_year.count }}</td>
</td> </tr>
</tr> <tr>
</tbody> <td class="px-2 sm:px-4 md:px-6 md:py-2">Refunded</td>
</table> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
<h1 class="text-5xl text-center my-6">Purchases</h1> {{ all_purchased_refunded_this_year.count }} ({{ refunded_percent }}%)
<table class="responsive-table"> </td>
<tbody> </tr>
<tr> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Total</td> <td class="px-2 sm:px-4 md:px-6 md:py-2">Unfinished</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ all_purchased_this_year.count }}</td> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
</tr> {{ purchased_unfinished.count }} ({{ unfinished_purchases_percent }}%)
<tr> </td>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Refunded</td> </tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono"> <tr>
{{ all_purchased_refunded_this_year.count }} ({{ refunded_percent }}%) <td class="px-2 sm:px-4 md:px-6 md:py-2">Backlog Decrease</td>
</td> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ backlog_decrease_count }}</td>
</tr> </tr>
<tr> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Unfinished</td> <td class="px-2 sm:px-4 md:px-6 md:py-2">Spendings ({{ total_spent_currency }})</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono"> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_spent }} ({{ spent_per_game }}/game)</td>
{{ purchased_unfinished.count }} ({{ unfinished_purchases_percent }}%) </tr>
</td> </tbody>
</tr> </table>
<tr> </div>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Backlog Decrease</td> </div>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ backlog_decrease_count }}</td>
</tr>
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Spendings ({{ total_spent_currency }})</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_spent }} ({{ spent_per_game }}/game)</td>
</tr>
</tbody>
</table>
<h1 class="text-5xl text-center my-6">Top games by playtime</h1> <h1 class="text-5xl text-center my-6">Top games by playtime</h1>
<table class="responsive-table"> <table class="responsive-table">
<thead> <thead>
@ -137,7 +129,6 @@
<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>
@ -145,16 +136,9 @@
<tr> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono"> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
<a class="underline decoration-slate-500 sm:decoration-2" <a class="underline decoration-slate-500 sm:decoration-2"
href="{% url 'edit_purchase' purchase.id %}"> href="{% url 'edit_purchase' purchase.id %}">{{ purchase.edition.name }}</a>
{% if purchase.type == 'dlc' %}
{{ purchase.name }} ({{ purchase.edition.name }} DLC)
{% else %}
{{ purchase.edition.name }}
{% endif %}
</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>

View File

@ -61,6 +61,7 @@
{% url 'start_game_session' game.id as add_session_link %} {% url 'start_game_session' game.id as add_session_link %}
{% include 'components/button.html' with title="Start new session" text="New" link=add_session_link %} {% include 'components/button.html' with title="Start new session" text="New" link=add_session_link %}
and Notes <span class="dark:text-slate-500">({{ sessions_with_notes_count }})</span> and Notes <span class="dark:text-slate-500">({{ sessions_with_notes_count }})</span>
</h1> </h1>
<ul> <ul>
{% for session in sessions %} {% for session in sessions %}

View File

@ -2,8 +2,8 @@ from datetime import datetime, timedelta
from typing import Any, Callable from typing import Any, Callable
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Avg, Count, ExpressionWrapper, F, Prefetch, Q, Sum, fields from django.db.models import Count, F, Prefetch, Sum
from django.db.models.functions import Extract, TruncDate from django.db.models.functions import TruncDate
from django.http import ( from django.http import (
HttpRequest, HttpRequest,
HttpResponse, HttpResponse,
@ -30,11 +30,11 @@ from .models import Edition, Game, Platform, Purchase, Session
def model_counts(request): def model_counts(request):
return { return {
"game_available": Game.objects.exists(), "game_available": Game.objects.count() != 0,
"edition_available": Edition.objects.exists(), "edition_available": Edition.objects.count() != 0,
"platform_available": Platform.objects.exists(), "platform_available": Platform.objects.count() != 0,
"purchase_available": Purchase.objects.exists(), "purchase_available": Purchase.objects.count() != 0,
"session_count": Session.objects.exists(), "session_count": Session.objects.count(),
} }
@ -321,22 +321,6 @@ def stats(request, year: int = 0):
if year == 0: if year == 0:
year = timezone.now().year year = timezone.now().year
this_year_sessions = Session.objects.filter(timestamp_start__year=year) this_year_sessions = Session.objects.filter(timestamp_start__year=year)
this_year_sessions_with_durations = this_year_sessions.annotate(
duration=ExpressionWrapper(
F("timestamp_end") - F("timestamp_start"),
output_field=fields.DurationField(),
)
)
longest_session = this_year_sessions_with_durations.order_by("-duration").first()
this_year_games_with_session_counts = Game.objects.annotate(
session_count=Count(
"edition__purchase__session",
filter=Q(edition__purchase__session__timestamp_start__year=year),
)
)
game_highest_session_count = this_year_games_with_session_counts.order_by(
"-session_count"
).first()
selected_currency = "CZK" selected_currency = "CZK"
unique_days = ( unique_days = (
this_year_sessions.annotate(date=TruncDate("timestamp_start")) this_year_sessions.annotate(date=TruncDate("timestamp_start"))
@ -360,8 +344,8 @@ def stats(request, year: int = 0):
this_year_purchases_unfinished = this_year_purchases_without_refunded.filter( this_year_purchases_unfinished = this_year_purchases_without_refunded.filter(
date_finished__isnull=True date_finished__isnull=True
).filter( ).filter(
Q(type=Purchase.GAME) | Q(type=Purchase.DLC) type=Purchase.GAME
) # do not count battle passes etc. ) # do not count DLC etc.
this_year_purchases_unfinished_percent = int( this_year_purchases_unfinished_percent = int(
safe_division( safe_division(
@ -371,16 +355,6 @@ 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"
@ -407,14 +381,6 @@ def stats(request, year: int = 0):
) )
.values("id", "name", "total_playtime") .values("id", "name", "total_playtime")
) )
highest_session_average_game = (
Game.objects.filter(edition__purchase__session__in=this_year_sessions)
.annotate(
session_average=Avg("edition__purchase__session__duration_calculated")
)
.order_by("-session_average")
.first()
)
top_10_games_by_playtime = games_with_playtime.order_by("-total_playtime")[:10] top_10_games_by_playtime = games_with_playtime.order_by("-total_playtime")[:10]
for game in top_10_games_by_playtime: for game in top_10_games_by_playtime:
game["formatted_playtime"] = format_duration(game["total_playtime"], "%2.0H") game["formatted_playtime"] = format_duration(game["total_playtime"], "%2.0H")
@ -452,7 +418,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_with_playtime.order_by( "all_finished_this_year": purchases_finished_this_year.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(
@ -478,18 +444,6 @@ def stats(request, year: int = 0):
"date_purchased" "date_purchased"
), ),
"backlog_decrease_count": backlog_decrease_count, "backlog_decrease_count": backlog_decrease_count,
"longest_session_time": format_duration(
longest_session.duration if longest_session else timedelta(0),
"%2.0Hh %2.0mm",
),
"longest_session_game": longest_session.purchase.edition.name,
"highest_session_count": game_highest_session_count.session_count,
"highest_session_count_game": game_highest_session_count.name,
"highest_session_average": format_duration(
highest_session_average_game.session_average, "%2.0Hh %2.0mm"
),
"highest_session_average_game": highest_session_average_game,
"title": f"{year} Stats",
} }
request.session["return_path"] = request.path request.session["return_path"] = request.path

41
poetry.lock generated
View File

@ -1,4 +1,4 @@
# 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.0 and should not be changed by hand.
[[package]] [[package]]
name = "aniso8601" name = "aniso8601"
@ -143,21 +143,6 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""}
argon2 = ["argon2-cffi (>=19.1.0)"] argon2 = ["argon2-cffi (>=19.1.0)"]
bcrypt = ["bcrypt"] bcrypt = ["bcrypt"]
[[package]]
name = "django-debug-toolbar"
version = "4.2.0"
description = "A configurable set of panels that display various debug information about the current request/response."
optional = false
python-versions = ">=3.8"
files = [
{file = "django_debug_toolbar-4.2.0-py3-none-any.whl", hash = "sha256:af99128c06e8e794479e65ab62cc6c7d1e74e1c19beb44dcbf9bad7a9c017327"},
{file = "django_debug_toolbar-4.2.0.tar.gz", hash = "sha256:bc7fdaafafcdedefcc67a4a5ad9dac96efd6e41db15bc74d402a54a2ba4854dc"},
]
[package.dependencies]
django = ">=3.2.4"
sqlparse = ">=0.2"
[[package]] [[package]]
name = "django-extensions" name = "django-extensions"
version = "3.2.3" version = "3.2.3"
@ -595,13 +580,13 @@ files = [
[[package]] [[package]]
name = "platformdirs" name = "platformdirs"
version = "4.0.0" version = "3.11.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"},
{file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"},
] ]
[package.extras] [package.extras]
@ -835,17 +820,17 @@ files = [
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "69.0.2" version = "68.2.2"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"},
{file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"},
] ]
[package.extras] [package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
@ -949,19 +934,19 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)",
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
version = "20.24.7" version = "20.24.6"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "virtualenv-20.24.7-py3-none-any.whl", hash = "sha256:a18b3fd0314ca59a2e9f4b556819ed07183b3e9a3702ecfe213f593d44f7b3fd"}, {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"},
{file = "virtualenv-20.24.7.tar.gz", hash = "sha256:69050ffb42419c91f6c1284a7b24e0475d793447e35929b488bf6a0aade39353"}, {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"},
] ]
[package.dependencies] [package.dependencies]
distlib = ">=0.3.7,<1" distlib = ">=0.3.7,<1"
filelock = ">=3.12.2,<4" filelock = ">=3.12.2,<4"
platformdirs = ">=3.9.1,<5" platformdirs = ">=3.9.1,<4"
[package.extras] [package.extras]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
@ -987,4 +972,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 = "e864dc8abf6c84e5bb16ac2aa937c2a70561d15f3e8a1459866b9d6507e8773e" content-hash = "49b33333953d875c6c2a26ffd1a1a2d21f75e06fe59e6619ba2900e39d2cf1bf"

View File

@ -25,7 +25,6 @@ djhtml = "^1.5.2"
djlint = "^1.19.11" djlint = "^1.19.11"
isort = "^5.11.4" isort = "^5.11.4"
pre-commit = "^3.5.0" pre-commit = "^3.5.0"
django-debug-toolbar = "^4.2.0"
[tool.isort] [tool.isort]
profile = "black" profile = "black"

View File

@ -46,7 +46,6 @@ 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")
INSTALLED_APPS.append("debug_toolbar")
MIDDLEWARE = [ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
@ -58,11 +57,6 @@ MIDDLEWARE = [
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
] ]
if DEBUG:
MIDDLEWARE.append("debug_toolbar.middleware.DebugToolbarMiddleware")
INTERNAL_IPS = ["127.0.0.1"]
DEBUG_TOOLBAR_CONFIG = {"ROOT_TAG_EXTRA_ATTRS": "hx-preserve"}
ROOT_URLCONF = "timetracker.urls" ROOT_URLCONF = "timetracker.urls"
TEMPLATES = [ TEMPLATES = [

View File

@ -28,4 +28,3 @@ urlpatterns = [
if settings.DEBUG: if settings.DEBUG:
urlpatterns.append(path("admin/", admin.site.urls)) urlpatterns.append(path("admin/", admin.site.urls))
urlpatterns.append(path("__debug__/", include("debug_toolbar.urls")))