Compare commits

..

24 Commits

Author SHA1 Message Date
Lukáš Kucharczyk 00b1d4ee8d
Organize better
Django CI/CD / test (push) Failing after 1m19s Details
Django CI/CD / build-and-push (push) Has been skipped Details
2023-11-29 22:03:07 +01:00
Lukáš Kucharczyk 8bd7134d2a
Revert "Move GraphQL to separata app"
This reverts commit 6ac4209492.
2023-11-29 22:03:07 +01:00
Lukáš Kucharczyk ee7bda1455
Revert "Add UpdateGameMutation"
This reverts commit e9e61403a9.
2023-11-29 22:03:07 +01:00
Lukáš Kucharczyk 34a3cd48c2
Add UpdateGameMutation 2023-11-29 22:03:07 +01:00
Lukáš Kucharczyk 9b75d7926f
Move GraphQL to separata app 2023-11-29 22:03:07 +01:00
Lukáš Kucharczyk a3b2d47031
Allow DLC to have date_finished set 2023-11-29 22:03:07 +01:00
Lukáš Kucharczyk d15b809b52
Initial working API 2023-11-29 22:03:07 +01:00
Lukáš Kucharczyk a245b6ff0f
Fix longest session formatting
Django CI/CD / test (push) Successful in 1m1s Details
Django CI/CD / build-and-push (push) Successful in 1m24s Details
Put space between hours and minutes
2023-11-29 09:08:10 +01:00
Lukáš Kucharczyk 6329d380b7
Editions are unique if name, platform OR year is different
Django CI/CD / test (push) Successful in 1m25s Details
Django CI/CD / build-and-push (push) Successful in 1m20s Details
2023-11-28 14:44:11 +01:00
Lukáš Kucharczyk 76fbc39fed
Disable hx-boost
Django CI/CD / test (push) Successful in 1m1s Details
Django CI/CD / build-and-push (push) Successful in 1m17s Details
2023-11-28 14:29:56 +01:00
Lukáš Kucharczyk 4b6734c173
Add width, height, alt to images 2023-11-28 14:29:11 +01:00
Lukáš Kucharczyk b505b5b430
Stats: add highest session average
Django CI/CD / test (push) Successful in 1m2s Details
Django CI/CD / build-and-push (push) Successful in 1m23s Details
2023-11-21 21:57:17 +01:00
Lukáš Kucharczyk 87553ebdc5
Add djlint pre-commit hook
Django CI/CD / test (push) Successful in 1m13s Details
Django CI/CD / build-and-push (push) Successful in 1m15s Details
2023-11-21 18:19:25 +01:00
Lukáš Kucharczyk ba4fc0cac5
Do not trigger hx-boost for non-submit buttons
Django CI/CD / test (push) Successful in 1m7s Details
Django CI/CD / build-and-push (push) Successful in 1m20s Details
2023-11-21 18:12:58 +01:00
Lukáš Kucharczyk 8cb0276215
Use better way to find out if model record exists 2023-11-21 18:03:01 +01:00
Lukáš Kucharczyk f9a51ee83d
Remove experimental layout 2023-11-21 18:03:01 +01:00
Lukáš Kucharczyk c9deba7d65
Add stats for most sessions, longest session 2023-11-21 17:02:44 +01:00
Lukáš Kucharczyk c55fbe86b5
Support HTMX with Django Debug Toolbar 2023-11-21 16:59:21 +01:00
Lukáš Kucharczyk 0e93993498
Add django-debug-toolbar 2023-11-21 14:42:37 +01:00
Lukáš Kucharczyk 9fccdfbff0
Make links colorful 2023-11-20 23:07:11 +01:00
Lukáš Kucharczyk d78139a5b3
Display finished DLCs in stats better
Django CI/CD / test (push) Successful in 1m6s Details
Django CI/CD / build-and-push (push) Successful in 1m26s Details
2023-11-20 21:56:16 +01:00
Lukáš Kucharczyk 7dc43fbf77
Fix wrong export name 2023-11-20 21:54:51 +01:00
Lukáš Kucharczyk 5442926457
Allow DLC to have date_finished set
Django CI/CD / test (push) Successful in 1m6s Details
Django CI/CD / build-and-push (push) Successful in 1m35s Details
2023-11-20 21:42:23 +01:00
Lukáš Kucharczyk db4c635260
Remote JavaScript files 2023-11-20 21:25:21 +01:00
15 changed files with 242 additions and 90 deletions

View File

@ -8,3 +8,8 @@ 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,6 +23,12 @@
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

@ -0,0 +1,17 @@
# 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

@ -34,7 +34,7 @@ class Game(models.Model):
class Edition(models.Model): class Edition(models.Model):
class Meta: class Meta:
unique_together = [["name", "platform"]] unique_together = [["name", "platform", "year_released"]]
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)

View File

@ -1173,6 +1173,12 @@ 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));
@ -1477,10 +1483,6 @@ th label {
display: block; display: block;
} }
.md\:w-1\/2 {
width: 50%;
}
.md\:w-auto { .md\:w-auto {
width: auto; width: auto;
} }

View File

@ -205,7 +205,7 @@ export {
syncSelectInputUntilChanged, syncSelectInputUntilChanged,
getEl, getEl,
conditionalElementHandler, conditionalElementHandler,
disableElementsWhenFalse, disableElementsWhenValueNotEqual,
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"> <div class="basic-button-container" hx-boost="false">
<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,16 +14,23 @@
<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" hx-boost="true"> <body class="dark" hx-indicator="#indicator">
<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' %}" width="48" class="mr-4" /> <img src="{% static 'icons/schedule.png' %}"
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,74 +18,82 @@
</select> </select>
</form> </form>
</div> </div>
<div class="flex flex-column flex-wrap justify-center"> <h1 class="text-5xl text-center my-6">Playtime</h1>
<div class="md:w-1/2"> <table class="responsive-table">
<h1 class="text-5xl text-center my-6">Playtime</h1> <tbody>
<table class="responsive-table"> <tr>
<tbody> <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">Hours</td> </tr>
<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">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">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">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">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">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</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 ({{ 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">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">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</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 ({{ 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">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">Longest session</td>
</tbody> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ longest_session_time }} ({{ longest_session_game }})</td>
</table> </tr>
</div> <tr>
<div class="md:w-1/2"> <td class="px-2 sm:px-4 md:px-6 md:py-2">Most sessions</td>
<h1 class="text-5xl text-center my-6">Purchases</h1> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ highest_session_count }} ({{ highest_session_count_game }})</td>
<table class="responsive-table"> </tr>
<tbody> <tr>
<tr> <td class="px-2 sm:px-4 md:px-6 md:py-2">Highest session average</td>
<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 font-mono">
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ all_purchased_this_year.count }}</td> {{ highest_session_average }} ({{ highest_session_average_game }})
</tr> </td>
<tr> </tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Refunded</td> </tbody>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono"> </table>
{{ all_purchased_refunded_this_year.count }} ({{ refunded_percent }}%) <h1 class="text-5xl text-center my-6">Purchases</h1>
</td> <table class="responsive-table">
</tr> <tbody>
<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">Total</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">{{ all_purchased_this_year.count }}</td>
{{ purchased_unfinished.count }} ({{ unfinished_purchases_percent }}%) </tr>
</td> <tr>
</tr> <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">
<td class="px-2 sm:px-4 md:px-6 md:py-2">Backlog Decrease</td> {{ all_purchased_refunded_this_year.count }} ({{ refunded_percent }}%)
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ backlog_decrease_count }}</td> </td>
</tr> </tr>
<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">Unfinished</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_spent }} ({{ spent_per_game }}/game)</td> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
</tr> {{ purchased_unfinished.count }} ({{ unfinished_purchases_percent }}%)
</tbody> </td>
</table> </tr>
</div> <tr>
</div> <td class="px-2 sm:px-4 md:px-6 md:py-2">Backlog Decrease</td>
<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>
@ -136,7 +144,13 @@
<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 %}">{{ purchase.edition.name }}</a> href="{% url 'edit_purchase' purchase.id %}">
{% 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>
</tr> </tr>

View File

@ -61,7 +61,6 @@
{% 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 Count, F, Prefetch, Sum from django.db.models import Avg, Count, ExpressionWrapper, F, Prefetch, Q, Sum, fields
from django.db.models.functions import TruncDate from django.db.models.functions import Extract, 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.count() != 0, "game_available": Game.objects.exists(),
"edition_available": Edition.objects.count() != 0, "edition_available": Edition.objects.exists(),
"platform_available": Platform.objects.count() != 0, "platform_available": Platform.objects.exists(),
"purchase_available": Purchase.objects.count() != 0, "purchase_available": Purchase.objects.exists(),
"session_count": Session.objects.count(), "session_count": Session.objects.exists(),
} }
@ -321,6 +321,22 @@ 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"))
@ -344,8 +360,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(
type=Purchase.GAME Q(type=Purchase.GAME) | Q(type=Purchase.DLC)
) # do not count DLC etc. ) # do not count battle passes etc.
this_year_purchases_unfinished_percent = int( this_year_purchases_unfinished_percent = int(
safe_division( safe_division(
@ -381,6 +397,14 @@ 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")
@ -444,6 +468,17 @@ 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,
} }
request.session["return_path"] = request.path request.session["return_path"] = request.path

61
poetry.lock generated
View File

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. # This file is automatically @generated by Poetry and should not be changed by hand.
[[package]] [[package]]
name = "aniso8601" name = "aniso8601"
@ -18,6 +18,7 @@ dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"]
name = "asgiref" name = "asgiref"
version = "3.7.2" version = "3.7.2"
description = "ASGI specs, helper code, and adapters" description = "ASGI specs, helper code, and adapters"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -32,6 +33,7 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
name = "black" name = "black"
version = "22.12.0" version = "22.12.0"
description = "The uncompromising code formatter." description = "The uncompromising code formatter."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -65,6 +67,7 @@ uvloop = ["uvloop (>=0.15.2)"]
name = "cfgv" name = "cfgv"
version = "3.4.0" version = "3.4.0"
description = "Validate configuration and produce human readable error messages." description = "Validate configuration and produce human readable error messages."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -76,6 +79,7 @@ files = [
name = "click" name = "click"
version = "8.1.7" version = "8.1.7"
description = "Composable command line interface toolkit" description = "Composable command line interface toolkit"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -90,6 +94,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
name = "colorama" name = "colorama"
version = "0.4.6" version = "0.4.6"
description = "Cross-platform colored terminal text." description = "Cross-platform colored terminal text."
category = "main"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [ files = [
@ -101,6 +106,7 @@ files = [
name = "cssbeautifier" name = "cssbeautifier"
version = "1.14.11" version = "1.14.11"
description = "CSS unobfuscator and beautifier." description = "CSS unobfuscator and beautifier."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -116,6 +122,7 @@ six = ">=1.13.0"
name = "distlib" name = "distlib"
version = "0.3.7" version = "0.3.7"
description = "Distribution utilities" description = "Distribution utilities"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -127,6 +134,7 @@ files = [
name = "django" name = "django"
version = "4.2.7" version = "4.2.7"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
category = "main"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -143,10 +151,27 @@ 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."
category = "dev"
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"
description = "Extensions for Django" description = "Extensions for Django"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -161,6 +186,7 @@ Django = ">=3.2"
name = "djhtml" name = "djhtml"
version = "1.5.2" version = "1.5.2"
description = "Django/Jinja template indenter" description = "Django/Jinja template indenter"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -174,6 +200,7 @@ dev = ["black", "flake8", "isort", "nox", "pre-commit"]
name = "djlint" name = "djlint"
version = "1.34.0" version = "1.34.0"
description = "HTML Template Linter and Formatter" description = "HTML Template Linter and Formatter"
category = "dev"
optional = false optional = false
python-versions = ">=3.8.0,<4.0.0" python-versions = ">=3.8.0,<4.0.0"
files = [ files = [
@ -198,6 +225,7 @@ tqdm = ">=4.62.2,<5.0.0"
name = "editorconfig" name = "editorconfig"
version = "0.12.3" version = "0.12.3"
description = "EditorConfig File Locator and Interpreter for Python" description = "EditorConfig File Locator and Interpreter for Python"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -209,6 +237,7 @@ files = [
name = "filelock" name = "filelock"
version = "3.13.1" version = "3.13.1"
description = "A platform independent file lock." description = "A platform independent file lock."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -294,6 +323,7 @@ graphql-core = ">=3.2,<3.3"
name = "gunicorn" name = "gunicorn"
version = "20.1.0" version = "20.1.0"
description = "WSGI HTTP Server for UNIX" description = "WSGI HTTP Server for UNIX"
category = "main"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -314,6 +344,7 @@ tornado = ["tornado (>=0.2)"]
name = "h11" name = "h11"
version = "0.14.0" version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -325,6 +356,7 @@ files = [
name = "html-tag-names" name = "html-tag-names"
version = "0.1.2" version = "0.1.2"
description = "List of known HTML tag names" description = "List of known HTML tag names"
category = "dev"
optional = false optional = false
python-versions = ">=3.7,<4.0" python-versions = ">=3.7,<4.0"
files = [ files = [
@ -336,6 +368,7 @@ files = [
name = "html-void-elements" name = "html-void-elements"
version = "0.1.0" version = "0.1.0"
description = "List of HTML void tag names." description = "List of HTML void tag names."
category = "dev"
optional = false optional = false
python-versions = ">=3.7,<4.0" python-versions = ">=3.7,<4.0"
files = [ files = [
@ -347,6 +380,7 @@ files = [
name = "identify" name = "identify"
version = "2.5.32" version = "2.5.32"
description = "File identification library for Python" description = "File identification library for Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -361,6 +395,7 @@ license = ["ukkonen"]
name = "iniconfig" name = "iniconfig"
version = "2.0.0" version = "2.0.0"
description = "brain-dead simple config-ini parsing" description = "brain-dead simple config-ini parsing"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -372,6 +407,7 @@ files = [
name = "isort" name = "isort"
version = "5.12.0" version = "5.12.0"
description = "A Python utility / library to sort Python imports." description = "A Python utility / library to sort Python imports."
category = "dev"
optional = false optional = false
python-versions = ">=3.8.0" python-versions = ">=3.8.0"
files = [ files = [
@ -389,6 +425,7 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"]
name = "jsbeautifier" name = "jsbeautifier"
version = "1.14.11" version = "1.14.11"
description = "JavaScript unobfuscator and beautifier." description = "JavaScript unobfuscator and beautifier."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -403,6 +440,7 @@ six = ">=1.13.0"
name = "json5" name = "json5"
version = "0.9.14" version = "0.9.14"
description = "A Python implementation of the JSON5 data format." description = "A Python implementation of the JSON5 data format."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -417,6 +455,7 @@ dev = ["hypothesis"]
name = "markupsafe" name = "markupsafe"
version = "2.1.3" version = "2.1.3"
description = "Safely add untrusted strings to HTML/XML markup." description = "Safely add untrusted strings to HTML/XML markup."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -486,6 +525,7 @@ files = [
name = "mypy" name = "mypy"
version = "0.991" version = "0.991"
description = "Optional static typing for Python" description = "Optional static typing for Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -535,6 +575,7 @@ reports = ["lxml"]
name = "mypy-extensions" name = "mypy-extensions"
version = "1.0.0" version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker." description = "Type system extensions for programs checked with the mypy type checker."
category = "dev"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -546,6 +587,7 @@ files = [
name = "nodeenv" name = "nodeenv"
version = "1.8.0" version = "1.8.0"
description = "Node.js virtual environment builder" description = "Node.js virtual environment builder"
category = "dev"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
files = [ files = [
@ -560,6 +602,7 @@ setuptools = "*"
name = "packaging" name = "packaging"
version = "23.2" version = "23.2"
description = "Core utilities for Python packages" description = "Core utilities for Python packages"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -571,6 +614,7 @@ files = [
name = "pathspec" name = "pathspec"
version = "0.11.2" version = "0.11.2"
description = "Utility library for gitignore style pattern matching of file paths." description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -582,6 +626,7 @@ files = [
name = "platformdirs" name = "platformdirs"
version = "3.11.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\"."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -597,6 +642,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co
name = "pluggy" name = "pluggy"
version = "1.3.0" version = "1.3.0"
description = "plugin and hook calling mechanisms for python" description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -612,6 +658,7 @@ testing = ["pytest", "pytest-benchmark"]
name = "pre-commit" name = "pre-commit"
version = "3.5.0" version = "3.5.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks." description = "A framework for managing and maintaining multi-language pre-commit hooks."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -646,6 +693,7 @@ test = ["coveralls", "futures", "mock", "pytest (>=2.7.3)", "pytest-benchmark",
name = "pytest" name = "pytest"
version = "7.4.3" version = "7.4.3"
description = "pytest: simple powerful testing with Python" description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -666,6 +714,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no
name = "pyyaml" name = "pyyaml"
version = "6.0.1" version = "6.0.1"
description = "YAML parser and emitter for Python" description = "YAML parser and emitter for Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -725,6 +774,7 @@ files = [
name = "regex" name = "regex"
version = "2023.10.3" version = "2023.10.3"
description = "Alternative regular expression module, to replace re." description = "Alternative regular expression module, to replace re."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -822,6 +872,7 @@ files = [
name = "setuptools" name = "setuptools"
version = "68.2.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"
category = "main"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -838,6 +889,7 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar
name = "six" name = "six"
version = "1.16.0" version = "1.16.0"
description = "Python 2 and 3 compatibility utilities" description = "Python 2 and 3 compatibility utilities"
category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
files = [ files = [
@ -849,6 +901,7 @@ files = [
name = "sqlparse" name = "sqlparse"
version = "0.4.4" version = "0.4.4"
description = "A non-validating SQL parser." description = "A non-validating SQL parser."
category = "main"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -876,6 +929,7 @@ files = [
name = "tqdm" name = "tqdm"
version = "4.66.1" version = "4.66.1"
description = "Fast, Extensible Progress Meter" description = "Fast, Extensible Progress Meter"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -896,6 +950,7 @@ telegram = ["requests"]
name = "typing-extensions" name = "typing-extensions"
version = "4.8.0" version = "4.8.0"
description = "Backported and Experimental Type Hints for Python 3.8+" description = "Backported and Experimental Type Hints for Python 3.8+"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -907,6 +962,7 @@ files = [
name = "tzdata" name = "tzdata"
version = "2023.3" version = "2023.3"
description = "Provider of IANA time zone data" description = "Provider of IANA time zone data"
category = "main"
optional = false optional = false
python-versions = ">=2" python-versions = ">=2"
files = [ files = [
@ -918,6 +974,7 @@ files = [
name = "uvicorn" name = "uvicorn"
version = "0.20.0" version = "0.20.0"
description = "The lightning-fast ASGI server." description = "The lightning-fast ASGI server."
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -936,6 +993,7 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)",
name = "virtualenv" name = "virtualenv"
version = "20.24.6" version = "20.24.6"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -956,6 +1014,7 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
name = "werkzeug" name = "werkzeug"
version = "2.3.8" version = "2.3.8"
description = "The comprehensive WSGI web application library." description = "The comprehensive WSGI web application library."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [

View File

@ -25,6 +25,7 @@ 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,6 +46,7 @@ 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",
@ -57,6 +58,11 @@ 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,3 +28,4 @@ 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")))