Compare commits

...

6 Commits

Author SHA1 Message Date
Lukáš Kucharczyk f9b53da19c
Use better way to find out if model record exists
Django CI/CD / test (push) Successful in 1m1s Details
Django CI/CD / build-and-push (push) Successful in 1m22s Details
2023-11-21 17:42:57 +01:00
Lukáš Kucharczyk 9a0e505aae
Remove experimental layout 2023-11-21 17:42:41 +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
8 changed files with 119 additions and 17 deletions

View File

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

View File

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

View File

@ -18,8 +18,6 @@
</select>
</form>
</div>
<div class="flex flex-column flex-wrap justify-center">
<div class="md:w-1/2">
<h1 class="text-5xl text-center my-6">Playtime</h1>
<table class="responsive-table">
<tbody>
@ -51,10 +49,16 @@
<td class="px-2 sm:px-4 md:px-6 md:py-2">Finished ({{ year }})</td>
<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>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ longest_session_time }} ({{ longest_session_game }})</td>
</tr>
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Most sessions</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ highest_session_count }} ({{ highest_session_count_game }})</td>
</tr>
</tbody>
</table>
</div>
<div class="md:w-1/2">
<h1 class="text-5xl text-center my-6">Purchases</h1>
<table class="responsive-table">
<tbody>

View File

@ -2,8 +2,8 @@ from datetime import datetime, timedelta
from typing import Any, Callable
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Count, F, Prefetch, Q, Sum
from django.db.models.functions import TruncDate
from django.db.models import Count, ExpressionWrapper, F, Prefetch, Q, Sum, fields
from django.db.models.functions import Extract, TruncDate
from django.http import (
HttpRequest,
HttpResponse,
@ -30,11 +30,11 @@ from .models import Edition, Game, Platform, Purchase, Session
def model_counts(request):
return {
"game_available": Game.objects.count() != 0,
"edition_available": Edition.objects.count() != 0,
"platform_available": Platform.objects.count() != 0,
"purchase_available": Purchase.objects.count() != 0,
"session_count": Session.objects.count(),
"game_available": Game.objects.exists(),
"edition_available": Edition.objects.exists(),
"platform_available": Platform.objects.exists(),
"purchase_available": Purchase.objects.exists(),
"session_count": Session.objects.exists(),
}
@ -321,6 +321,22 @@ def stats(request, year: int = 0):
if year == 0:
year = timezone.now().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"
unique_days = (
this_year_sessions.annotate(date=TruncDate("timestamp_start"))
@ -444,6 +460,13 @@ def stats(request, year: int = 0):
"date_purchased"
),
"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,
}
request.session["return_path"] = request.path

63
poetry.lock generated
View File

@ -1,9 +1,10 @@
# 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]]
name = "asgiref"
version = "3.7.2"
description = "ASGI specs, helper code, and adapters"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
@ -18,6 +19,7 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
name = "black"
version = "22.12.0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -51,6 +53,7 @@ uvloop = ["uvloop (>=0.15.2)"]
name = "cfgv"
version = "3.4.0"
description = "Validate configuration and produce human readable error messages."
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
@ -62,6 +65,7 @@ files = [
name = "click"
version = "8.1.7"
description = "Composable command line interface toolkit"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
@ -76,6 +80,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
@ -87,6 +92,7 @@ files = [
name = "cssbeautifier"
version = "1.14.11"
description = "CSS unobfuscator and beautifier."
category = "dev"
optional = false
python-versions = "*"
files = [
@ -102,6 +108,7 @@ six = ">=1.13.0"
name = "distlib"
version = "0.3.7"
description = "Distribution utilities"
category = "dev"
optional = false
python-versions = "*"
files = [
@ -113,6 +120,7 @@ files = [
name = "django"
version = "4.2.7"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
category = "main"
optional = false
python-versions = ">=3.8"
files = [
@ -129,10 +137,27 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""}
argon2 = ["argon2-cffi (>=19.1.0)"]
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]]
name = "django-extensions"
version = "3.2.3"
description = "Extensions for Django"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@ -147,6 +172,7 @@ Django = ">=3.2"
name = "djhtml"
version = "1.5.2"
description = "Django/Jinja template indenter"
category = "dev"
optional = false
python-versions = "*"
files = [
@ -160,6 +186,7 @@ dev = ["black", "flake8", "isort", "nox", "pre-commit"]
name = "djlint"
version = "1.34.0"
description = "HTML Template Linter and Formatter"
category = "dev"
optional = false
python-versions = ">=3.8.0,<4.0.0"
files = [
@ -184,6 +211,7 @@ tqdm = ">=4.62.2,<5.0.0"
name = "editorconfig"
version = "0.12.3"
description = "EditorConfig File Locator and Interpreter for Python"
category = "dev"
optional = false
python-versions = "*"
files = [
@ -195,6 +223,7 @@ files = [
name = "filelock"
version = "3.13.1"
description = "A platform independent file lock."
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
@ -211,6 +240,7 @@ typing = ["typing-extensions (>=4.8)"]
name = "gunicorn"
version = "20.1.0"
description = "WSGI HTTP Server for UNIX"
category = "main"
optional = false
python-versions = ">=3.5"
files = [
@ -231,6 +261,7 @@ tornado = ["tornado (>=0.2)"]
name = "h11"
version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
@ -242,6 +273,7 @@ files = [
name = "html-tag-names"
version = "0.1.2"
description = "List of known HTML tag names"
category = "dev"
optional = false
python-versions = ">=3.7,<4.0"
files = [
@ -253,6 +285,7 @@ files = [
name = "html-void-elements"
version = "0.1.0"
description = "List of HTML void tag names."
category = "dev"
optional = false
python-versions = ">=3.7,<4.0"
files = [
@ -264,6 +297,7 @@ files = [
name = "identify"
version = "2.5.31"
description = "File identification library for Python"
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
@ -278,6 +312,7 @@ license = ["ukkonen"]
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -289,6 +324,7 @@ files = [
name = "isort"
version = "5.12.0"
description = "A Python utility / library to sort Python imports."
category = "dev"
optional = false
python-versions = ">=3.8.0"
files = [
@ -306,6 +342,7 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"]
name = "jsbeautifier"
version = "1.14.11"
description = "JavaScript unobfuscator and beautifier."
category = "dev"
optional = false
python-versions = "*"
files = [
@ -320,6 +357,7 @@ six = ">=1.13.0"
name = "json5"
version = "0.9.14"
description = "A Python implementation of the JSON5 data format."
category = "dev"
optional = false
python-versions = "*"
files = [
@ -334,6 +372,7 @@ dev = ["hypothesis"]
name = "markupsafe"
version = "2.1.3"
description = "Safely add untrusted strings to HTML/XML markup."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -403,6 +442,7 @@ files = [
name = "mypy"
version = "0.991"
description = "Optional static typing for Python"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -452,6 +492,7 @@ reports = ["lxml"]
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
category = "dev"
optional = false
python-versions = ">=3.5"
files = [
@ -463,6 +504,7 @@ files = [
name = "nodeenv"
version = "1.8.0"
description = "Node.js virtual environment builder"
category = "dev"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
files = [
@ -477,6 +519,7 @@ setuptools = "*"
name = "packaging"
version = "23.2"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -488,6 +531,7 @@ files = [
name = "pathspec"
version = "0.11.2"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -499,6 +543,7 @@ files = [
name = "platformdirs"
version = "3.11.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -514,6 +559,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co
name = "pluggy"
version = "1.3.0"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
@ -529,6 +575,7 @@ testing = ["pytest", "pytest-benchmark"]
name = "pre-commit"
version = "3.5.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
@ -547,6 +594,7 @@ virtualenv = ">=20.10.0"
name = "pytest"
version = "7.4.3"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -567,6 +615,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no
name = "pyyaml"
version = "6.0.1"
description = "YAML parser and emitter for Python"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@ -626,6 +675,7 @@ files = [
name = "regex"
version = "2023.10.3"
description = "Alternative regular expression module, to replace re."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -723,6 +773,7 @@ files = [
name = "setuptools"
version = "68.2.2"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
category = "main"
optional = false
python-versions = ">=3.8"
files = [
@ -739,6 +790,7 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
@ -750,6 +802,7 @@ files = [
name = "sqlparse"
version = "0.4.4"
description = "A non-validating SQL parser."
category = "main"
optional = false
python-versions = ">=3.5"
files = [
@ -766,6 +819,7 @@ test = ["pytest", "pytest-cov"]
name = "tqdm"
version = "4.66.1"
description = "Fast, Extensible Progress Meter"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -786,6 +840,7 @@ telegram = ["requests"]
name = "typing-extensions"
version = "4.8.0"
description = "Backported and Experimental Type Hints for Python 3.8+"
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
@ -797,6 +852,7 @@ files = [
name = "tzdata"
version = "2023.3"
description = "Provider of IANA time zone data"
category = "main"
optional = false
python-versions = ">=2"
files = [
@ -808,6 +864,7 @@ files = [
name = "uvicorn"
version = "0.20.0"
description = "The lightning-fast ASGI server."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
@ -826,6 +883,7 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)",
name = "virtualenv"
version = "20.24.6"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -846,6 +904,7 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
name = "werkzeug"
version = "2.3.8"
description = "The comprehensive WSGI web application library."
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
@ -862,4 +921,4 @@ watchdog = ["watchdog (>=2.3)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "32e7c40e7148530effb10ebd5d67a4f1c8fe30794a4d3b5d213d4f30048c79ea"
content-hash = "498b3358998a9f3bbfb74fae7d6a90de7b55b9bdc76845bce52f65785afd0c1e"

View File

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

View File

@ -43,6 +43,7 @@ INSTALLED_APPS = [
if DEBUG:
INSTALLED_APPS.append("django_extensions")
INSTALLED_APPS.append("django.contrib.admin")
INSTALLED_APPS.append("debug_toolbar")
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
@ -54,6 +55,11 @@ MIDDLEWARE = [
"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"
TEMPLATES = [

View File

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