7 Commits

Author SHA1 Message Date
0cf3411f63 Make ending session from session list faster
All checks were successful
Django CI/CD / test (push) Successful in 1m16s
Django CI/CD / build-and-push (push) Successful in 1m40s
2024-01-10 15:12:45 +01:00
aa669710e1 Change update_session to template partial
Some checks failed
Django CI/CD / test (push) Failing after 1m3s
Django CI/CD / build-and-push (push) Has been skipped
2024-01-10 14:10:13 +01:00
242833f886 Make it possible to drop purchases, or consider them infinite
Some checks failed
Django CI/CD / build-and-push (push) Blocked by required conditions
Django CI/CD / test (push) Has been cancelled
2024-01-03 22:35:39 +01:00
0cdfd3c298 Stats: optimize
All checks were successful
Django CI/CD / test (push) Successful in 56s
Django CI/CD / build-and-push (push) Successful in 1m33s
2024-01-03 21:35:47 +01:00
a98b4839dd Fix wrong unfinished purchases calculation
All checks were successful
Django CI/CD / test (push) Successful in 1m10s
Django CI/CD / build-and-push (push) Successful in 1m42s
2024-01-02 20:03:59 +01:00
1999f13cf2 stats: add first and last play
All checks were successful
Django CI/CD / test (push) Successful in 1m12s
Django CI/CD / build-and-push (push) Successful in 1m36s
2024-01-01 18:42:14 +01:00
8466f67c86 Fix errors caused by empty values
All checks were successful
Django CI/CD / test (push) Successful in 1m10s
Django CI/CD / build-and-push (push) Successful in 1m41s
2024-01-01 18:21:50 +01:00
11 changed files with 232 additions and 76 deletions

View File

@ -3,6 +3,7 @@
## Improved ## Improved
* game overview: improve how editions and purchases are displayed * game overview: improve how editions and purchases are displayed
* add purchase: only allow choosing purchases of selected edition * add purchase: only allow choosing purchases of selected edition
* session list: clicking the "End now?" link is not much faster
## 1.5.1 / 2023-11-14 21:10+01:00 ## 1.5.1 / 2023-11-14 21:10+01:00

View File

@ -83,6 +83,7 @@ class PurchaseForm(forms.ModelForm):
"date_purchased": custom_date_widget, "date_purchased": custom_date_widget,
"date_refunded": custom_date_widget, "date_refunded": custom_date_widget,
"date_finished": custom_date_widget, "date_finished": custom_date_widget,
"date_dropped": custom_date_widget,
} }
model = Purchase model = Purchase
fields = [ fields = [
@ -91,6 +92,8 @@ class PurchaseForm(forms.ModelForm):
"date_purchased", "date_purchased",
"date_refunded", "date_refunded",
"date_finished", "date_finished",
"date_dropped",
"infinite",
"price", "price",
"price_currency", "price_currency",
"ownership_type", "ownership_type",

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.7 on 2024-01-03 21:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("games", "0033_alter_edition_unique_together"),
]
operations = [
migrations.AddField(
model_name="purchase",
name="date_dropped",
field=models.DateField(blank=True, null=True),
),
migrations.AddField(
model_name="purchase",
name="infinite",
field=models.BooleanField(default=False),
),
]

View File

@ -116,6 +116,8 @@ class Purchase(models.Model):
date_purchased = models.DateField() date_purchased = models.DateField()
date_refunded = models.DateField(blank=True, null=True) date_refunded = models.DateField(blank=True, null=True)
date_finished = models.DateField(blank=True, null=True) date_finished = models.DateField(blank=True, null=True)
date_dropped = models.DateField(blank=True, null=True)
infinite = models.BooleanField(default=False)
price = models.IntegerField(default=0) price = models.IntegerField(default=0)
price_currency = models.CharField(max_length=3, default="USD") price_currency = models.CharField(max_length=3, default="USD")
ownership_type = models.CharField( ownership_type = models.CharField(

View File

@ -1,3 +1,4 @@
{% load django_htmx %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
{% load static %} {% load static %}
@ -12,6 +13,7 @@
{% endblock title %} {% endblock title %}
</title> </title>
<script src="{% static 'js/htmx.min.js' %}"></script> <script src="{% static 'js/htmx.min.js' %}"></script>
{% django_htmx_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">

View File

@ -7,13 +7,13 @@
{% if dataset.count >= 1 %} {% if dataset.count >= 1 %}
<div class="mx-auto text-center my-4"> <div class="mx-auto text-center my-4">
<a id="last-session-start" <a id="last-session-start"
href="{% url 'start_session_same_as_last' last.id %}" href="{% url 'start_session_same_as_last' last.id %}"
hx-get="{% url 'start_session_same_as_last' last.id %}" hx-get="{% url 'start_session_same_as_last' last.id %}"
hx-swap="afterbegin" hx-swap="afterbegin"
hx-target=".responsive-table tbody" hx-target=".responsive-table tbody"
hx-select=".responsive-table tbody tr:first-child" hx-select=".responsive-table tbody tr:first-child"
onClick="document.querySelector('#last-session-start').classList.add('invisible')" onClick="document.querySelector('#last-session-start').classList.add('invisible')"
class="{% if last.timestamp_end == null %}invisible{% endif %}"> class="{% if last.timestamp_end == null %}invisible{% endif %}">
{% include 'components/button_start.html' with text=last.purchase title="Start session of last played game" only %} {% include 'components/button_start.html' with text=last.purchase title="Start session of last played game" only %}
</a> </a>
</div> </div>
@ -29,36 +29,37 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for data in dataset %} {% for session in dataset %}
<tr> {% partialdef session-row inline=True %}
<td class="px-2 sm:px-4 md:px-6 md:py-2 purchase-name truncate max-w-20char md:max-w-40char"> <tr>
<a class="underline decoration-slate-500 sm:decoration-2" <td class="px-2 sm:px-4 md:px-6 md:py-2 purchase-name truncate max-w-20char md:max-w-40char">
href="{% url 'view_game' data.purchase.edition.game.id %}"> <a class="underline decoration-slate-500 sm:decoration-2"
{{ data.purchase.edition }} href="{% url 'view_game' session.purchase.edition.game.id %}">
</a> {{ session.purchase.edition }}
</td> </a>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono hidden sm:table-cell"> </td>
{{ data.timestamp_start | date:"d/m/Y H:i" }} <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono hidden sm:table-cell">
</td> {{ session.timestamp_start | date:"d/m/Y H:i" }}
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono hidden lg:table-cell"> </td>
{% if data.unfinished %} <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono hidden lg:table-cell">
<a href="{% url 'update_session' data.id %}" {% if session.unfinished %}
hx-get="{% url 'update_session' data.id %}" <a href="{% url 'update_session' session.id %}"
hx-swap="outerHTML" hx-get="{% url 'update_session' session.id %}"
hx-target=".responsive-table tbody tr:first-child" hx-target="closest tr"
hx-select=".responsive-table tbody tr:first-child" hx-swap="outerHTML"
hx-indicator="#indicator" hx-indicator="#indicator"
onClick="document.querySelector('#last-session-start').classList.remove('invisible')"> onClick="document.querySelector('#last-session-start').classList.remove('invisible')">
<span class="text-yellow-300">Finish now?</span> <span class="text-yellow-300">Finish now?</span>
</a> </a>
{% elif data.duration_manual %} {% elif session.duration_manual %}
-- --
{% else %} {% else %}
{{ data.timestamp_end | date:"d/m/Y H:i" }} {{ session.timestamp_end | date:"d/m/Y H:i" }}
{% endif %} {% endif %}
</td> </td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ data.duration_formatted }}</td> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ session.duration_formatted }}</td>
</tr> </tr>
{% endpartialdef %}
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>

View File

@ -43,11 +43,11 @@
</tr> </tr>
<tr> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Finished</td> <td class="px-2 sm:px-4 md:px-6 md:py-2">Finished</td>
<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 font-mono">{{ all_finished_this_year_count }}</td>
</tr> </tr>
<tr> <tr>
<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">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> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ this_year_finished_this_year_count }}</td>
</tr> </tr>
<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">Longest session</td>
@ -63,6 +63,14 @@
{{ highest_session_average }} ({{ highest_session_average_game }}) {{ highest_session_average }} ({{ highest_session_average_game }})
</td> </td>
</tr> </tr>
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">First play</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ first_play_name }} ({{ first_play_date }})</td>
</tr>
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Last play</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ last_play_name }} ({{ last_play_date }})</td>
</tr>
</tbody> </tbody>
</table> </table>
<h1 class="text-5xl text-center my-6">Purchases</h1> <h1 class="text-5xl text-center my-6">Purchases</h1>
@ -70,18 +78,18 @@
<tbody> <tbody>
<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">Total</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">{{ all_purchased_this_year_count }}</td>
</tr> </tr>
<tr> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Refunded</td> <td class="px-2 sm:px-4 md:px-6 md:py-2">Refunded</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_refunded_this_year.count }} ({{ refunded_percent }}%) {{ all_purchased_refunded_this_year_count }} ({{ refunded_percent }}%)
</td> </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">Unfinished</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">
{{ purchased_unfinished.count }} ({{ unfinished_purchases_percent }}%) {{ purchased_unfinished_count }} ({{ unfinished_purchases_percent }}%)
</td> </td>
</tr> </tr>
<tr> <tr>
@ -197,6 +205,33 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<h1 class="text-5xl text-center my-6">Unfinished Purchases</h1>
<table class="responsive-table">
<thead>
<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">Price ({{ total_spent_currency }})</th>
<th class="px-2 sm:px-4 md:px-6 md:py-2">Date</th>
</tr>
</thead>
<tbody>
{% for purchase in purchased_unfinished %}
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
<a class="underline decoration-slate-500 sm:decoration-2"
href="{% url 'edit_purchase' purchase.id %}">
{{ purchase.edition.name }}
{% if purchase.type == "dlc" %}({{ purchase.name }}, {{ purchase.get_type_display }}){% endif %}
</a>
</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.price }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_purchased | date:"d/m/Y" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<h1 class="text-5xl text-center my-6">All Purchases</h1> <h1 class="text-5xl text-center my-6">All Purchases</h1>
<table class="responsive-table"> <table class="responsive-table">
<thead> <thead>

View File

@ -13,6 +13,7 @@ from django.http import (
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.shortcuts import get_object_or_404
from common.time import format_duration from common.time import format_duration
from common.utils import safe_division from common.utils import safe_division
@ -74,9 +75,12 @@ def add_session(request, purchase_id=None):
def update_session(request, session_id=None): def update_session(request, session_id=None):
session = Session.objects.get(id=session_id) session = get_object_or_404(Session, id=session_id)
session.finish_now() session.finish_now()
session.save() session.save()
if request.htmx:
context = {"session": session}
return render(request, "list_sessions.html#session-row", context)
return redirect("list_sessions") return redirect("list_sessions")
@ -320,7 +324,9 @@ def stats(request, year: int = 0):
return HttpResponseRedirect(reverse("stats_by_year", args=[selected_year])) return HttpResponseRedirect(reverse("stats_by_year", args=[selected_year]))
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
).select_related("purchase__edition")
this_year_sessions_with_durations = this_year_sessions.annotate( this_year_sessions_with_durations = this_year_sessions.annotate(
duration=ExpressionWrapper( duration=ExpressionWrapper(
F("timestamp_end") - F("timestamp_start"), F("timestamp_end") - F("timestamp_start"),
@ -328,7 +334,10 @@ def stats(request, year: int = 0):
) )
) )
longest_session = this_year_sessions_with_durations.order_by("-duration").first() longest_session = this_year_sessions_with_durations.order_by("-duration").first()
this_year_games_with_session_counts = Game.objects.annotate( this_year_games = Game.objects.filter(
edition__purchase__session__in=this_year_sessions
).distinct()
this_year_games_with_session_counts = this_year_games.annotate(
session_count=Count( session_count=Count(
"edition__purchase__session", "edition__purchase__session",
filter=Q(edition__purchase__session__timestamp_start__year=year), filter=Q(edition__purchase__session__timestamp_start__year=year),
@ -349,23 +358,29 @@ def stats(request, year: int = 0):
).distinct() ).distinct()
this_year_purchases = Purchase.objects.filter(date_purchased__year=year) this_year_purchases = Purchase.objects.filter(date_purchased__year=year)
this_year_purchases_with_currency = this_year_purchases.filter( this_year_purchases_with_currency = this_year_purchases.select_related(
price_currency__exact=selected_currency "edition"
) ).filter(price_currency__exact=selected_currency)
this_year_purchases_without_refunded = this_year_purchases_with_currency.filter( this_year_purchases_without_refunded = this_year_purchases_with_currency.filter(
date_refunded=None date_refunded=None
) )
this_year_purchases_refunded = this_year_purchases_with_currency.refunded() this_year_purchases_refunded = this_year_purchases_with_currency.refunded()
this_year_purchases_unfinished = this_year_purchases_without_refunded.filter( this_year_purchases_unfinished = (
date_finished__isnull=True this_year_purchases_without_refunded.filter(date_finished__isnull=True)
).filter( .filter(date_dropped__isnull=True)
Q(type=Purchase.GAME) | Q(type=Purchase.DLC) .filter(infinite=False)
.filter(Q(type=Purchase.GAME) | Q(type=Purchase.DLC))
) # do not count battle passes etc. ) # do not count battle passes etc.
this_year_purchases_without_refunded_count = (
this_year_purchases_without_refunded.count()
)
this_year_purchases_unfinished_count = this_year_purchases_unfinished.count()
this_year_purchases_unfinished_percent = int( this_year_purchases_unfinished_percent = int(
safe_division( safe_division(
this_year_purchases_unfinished.count(), this_year_purchases_refunded.count() this_year_purchases_unfinished_count,
this_year_purchases_without_refunded_count,
) )
* 100 * 100
) )
@ -377,10 +392,8 @@ def stats(request, year: int = 0):
) )
) )
purchased_this_year_finished_this_year = ( purchased_this_year_finished_this_year = (
this_year_purchases_without_refunded.intersection( this_year_purchases_without_refunded.filter(date_finished__year=year)
purchases_finished_this_year ).order_by("date_finished")
).order_by("date_finished")
)
this_year_spendings = this_year_purchases_without_refunded.aggregate( this_year_spendings = this_year_purchases_without_refunded.aggregate(
total_spent=Sum(F("price")) total_spent=Sum(F("price"))
@ -425,6 +438,20 @@ def stats(request, year: int = 0):
.count() .count()
) )
first_play_name = "N/A"
first_play_date = "N/A"
last_play_name = "N/A"
last_play_date = "N/A"
if this_year_sessions:
first_session = this_year_sessions.earliest()
first_play_name = first_session.purchase.edition.name
first_play_date = first_session.timestamp_start.strftime("%x")
last_session = this_year_sessions.latest()
last_play_name = last_session.purchase.edition.name
last_play_date = last_session.timestamp_start.strftime("%x")
all_purchased_this_year_count = this_year_purchases_with_currency.count()
all_purchased_refunded_this_year_count = this_year_purchases_refunded.count()
context = { context = {
"total_hours": format_duration( "total_hours": format_duration(
this_year_sessions.total_duration_unformatted(), "%2.0H" this_year_sessions.total_duration_unformatted(), "%2.0H"
@ -440,45 +467,67 @@ def stats(request, year: int = 0):
"total_spent_currency": selected_currency, "total_spent_currency": selected_currency,
"all_purchased_this_year": this_year_purchases_without_refunded, "all_purchased_this_year": this_year_purchases_without_refunded,
"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.select_related(
"edition"
).order_by("date_finished"),
"all_finished_this_year_count": purchases_finished_this_year.count(),
"this_year_finished_this_year": purchases_finished_this_year_released_this_year.select_related(
"edition"
).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_count": purchases_finished_this_year_released_this_year.count(),
"date_finished" "purchased_this_year_finished_this_year": purchased_this_year_finished_this_year.select_related(
), "edition"
"purchased_this_year_finished_this_year": purchased_this_year_finished_this_year.order_by( ).order_by(
"date_finished" "date_finished"
), ),
"total_sessions": this_year_sessions.count(), "total_sessions": this_year_sessions.count(),
"unique_days": unique_days["dates"], "unique_days": unique_days["dates"],
"unique_days_percent": int(unique_days["dates"] / 365 * 100), "unique_days_percent": int(unique_days["dates"] / 365 * 100),
"purchased_unfinished": this_year_purchases_unfinished, "purchased_unfinished": this_year_purchases_unfinished,
"purchased_unfinished_count": this_year_purchases_unfinished_count,
"unfinished_purchases_percent": this_year_purchases_unfinished_percent, "unfinished_purchases_percent": this_year_purchases_unfinished_percent,
"refunded_percent": int( "refunded_percent": int(
safe_division( safe_division(
this_year_purchases_refunded.count(), all_purchased_refunded_this_year_count,
this_year_purchases_with_currency.count(), all_purchased_this_year_count,
) )
* 100 * 100
), ),
"all_purchased_refunded_this_year": this_year_purchases_refunded, "all_purchased_refunded_this_year": this_year_purchases_refunded,
"all_purchased_refunded_this_year_count": all_purchased_refunded_this_year_count,
"all_purchased_this_year": this_year_purchases_with_currency.order_by( "all_purchased_this_year": this_year_purchases_with_currency.order_by(
"date_purchased" "date_purchased"
), ),
"all_purchased_this_year_count": all_purchased_this_year_count,
"backlog_decrease_count": backlog_decrease_count, "backlog_decrease_count": backlog_decrease_count,
"longest_session_time": format_duration( "longest_session_time": format_duration(
longest_session.duration if longest_session else timedelta(0), longest_session.duration, "%2.0Hh %2.0mm"
"%2.0Hh %2.0mm", )
), if longest_session
"longest_session_game": longest_session.purchase.edition.name, else 0,
"highest_session_count": game_highest_session_count.session_count, "longest_session_game": longest_session.purchase.edition.name
"highest_session_count_game": game_highest_session_count.name, if longest_session
else "N/A",
"highest_session_count": game_highest_session_count.session_count
if game_highest_session_count
else 0,
"highest_session_count_game": game_highest_session_count.name
if game_highest_session_count
else "N/A",
"highest_session_average": format_duration( "highest_session_average": format_duration(
highest_session_average_game.session_average, "%2.0Hh %2.0mm" highest_session_average_game.session_average, "%2.0Hh %2.0mm"
), )
if highest_session_average_game
else 0,
"highest_session_average_game": highest_session_average_game, "highest_session_average_game": highest_session_average_game,
"first_play_name": first_play_name,
"first_play_date": first_play_date,
"last_play_name": last_play_name,
"last_play_date": last_play_date,
"title": f"{year} Stats", "title": f"{year} Stats",
} }

39
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"
@ -172,6 +172,39 @@ files = [
[package.dependencies] [package.dependencies]
Django = ">=3.2" Django = ">=3.2"
[[package]]
name = "django-htmx"
version = "1.17.2"
description = "Extensions for using Django with htmx."
optional = false
python-versions = ">=3.8"
files = [
{file = "django-htmx-1.17.2.tar.gz", hash = "sha256:4089f2ed38727e9846c2f4cd1daddf6b010c7be8d834cfbcffc8c5ecf445c04e"},
{file = "django_htmx-1.17.2-py3-none-any.whl", hash = "sha256:f4971432d2ca45dbb31d9b58add1c50ae54354afe4bf59cafd591b1711b502c0"},
]
[package.dependencies]
asgiref = ">=3.6"
Django = ">=3.2"
[[package]]
name = "django-template-partials"
version = "23.4"
description = "django-template-partials"
optional = false
python-versions = "*"
files = [
{file = "django-template-partials-23.4.tar.gz", hash = "sha256:f762b0b7b2222462df0845f0556792640b769eb832eae218a0e7dadd4e5606cc"},
{file = "django_template_partials-23.4-py2.py3-none-any.whl", hash = "sha256:d83d9c2d2836be769919e9aaf394d5feb1ac86e1187083030398308070122fca"},
]
[package.dependencies]
Django = "*"
[package.extras]
docs = ["Sphinx"]
tests = ["coverage", "django_coverage_plugin"]
[[package]] [[package]]
name = "djhtml" name = "djhtml"
version = "1.5.2" version = "1.5.2"
@ -986,5 +1019,5 @@ watchdog = ["watchdog (>=2.3)"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.12" python-versions = "^3.11"
content-hash = "e864dc8abf6c84e5bb16ac2aa937c2a70561d15f3e8a1459866b9d6507e8773e" content-hash = "4662e73ad621b11cbe5b517ca08aae4cbeb350bfcc855a6c067861942e232d2a"

View File

@ -8,11 +8,13 @@ readme = "README.md"
packages = [{include = "timetracker"}] packages = [{include = "timetracker"}]
[tool.poetry.group.main.dependencies] [tool.poetry.group.main.dependencies]
python = "^3.12" python = "^3.11"
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" graphene-django = "^3.1.5"
django-htmx = "^1.17.2"
django-template-partials = "^23.4"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
black = "^22.12.0" black = "^22.12.0"
@ -27,6 +29,7 @@ isort = "^5.11.4"
pre-commit = "^3.5.0" pre-commit = "^3.5.0"
django-debug-toolbar = "^4.2.0" django-debug-toolbar = "^4.2.0"
[tool.isort] [tool.isort]
profile = "black" profile = "black"

View File

@ -38,7 +38,9 @@ INSTALLED_APPS = [
"django.contrib.sessions", "django.contrib.sessions",
"django.contrib.messages", "django.contrib.messages",
"django.contrib.staticfiles", "django.contrib.staticfiles",
"template_partials",
"graphene_django", "graphene_django",
"django_htmx",
] ]
GRAPHENE = {"SCHEMA": "games.schema.schema"} GRAPHENE = {"SCHEMA": "games.schema.schema"}
@ -56,6 +58,7 @@ MIDDLEWARE = [
"django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
"django_htmx.middleware.HtmxMiddleware",
] ]
if DEBUG: if DEBUG:
@ -79,6 +82,7 @@ TEMPLATES = [
"games.views.model_counts", "games.views.model_counts",
"games.views.stats_dropdown_year_range", "games.views.stats_dropdown_year_range",
], ],
"builtins": ["template_partials.templatetags.partials"],
}, },
}, },
] ]