Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
4552cf7616 | |||
a614b51d29 | |||
e67aa3fda1 | |||
8423fd02b4 | |||
2bd07e5f2d | |||
058b83522c | |||
f13ed8a078 | |||
02d5adcb3c | |||
d6fb16bb74 | |||
71b90b8202 | |||
3ee36932c3 | |||
391fcc79a8 | |||
57d4fd7212 |
20
CHANGELOG.md
20
CHANGELOG.md
@ -1,4 +1,22 @@
|
||||
## Unreleased
|
||||
## 1.3.0 / 2023-11-05 15:09+01:00
|
||||
|
||||
### New
|
||||
* Add Stats to the main navigation
|
||||
* Allow selecting year on the Stats page
|
||||
|
||||
### Improved
|
||||
* Make some pages redirect back instead to session list
|
||||
|
||||
### Improved
|
||||
* Make navigation more compact
|
||||
|
||||
### Fixed
|
||||
* Correctly limit sessions to a single year for stats
|
||||
|
||||
## 1.2.0 / 2023-11-01 20:18+01:00
|
||||
|
||||
### New
|
||||
* Add yearly stats page (https://git.kucharczyk.xyz/lukas/timetracker/issues/15)
|
||||
|
||||
### Enhancements
|
||||
* Add a button to start session from game overview
|
||||
|
@ -6,7 +6,7 @@ RUN npm install && \
|
||||
|
||||
FROM python:3.10.9-slim-bullseye
|
||||
|
||||
ENV VERSION_NUMBER 1.1.2
|
||||
ENV VERSION_NUMBER 1.3.0
|
||||
ENV PROD 1
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
|
@ -755,6 +755,10 @@ select {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bottom-2 {
|
||||
bottom: 0.5rem;
|
||||
}
|
||||
@ -791,10 +795,19 @@ select {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.my-6 {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.mb-1 {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.mb-10 {
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
@ -819,6 +832,10 @@ select {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: inline;
|
||||
}
|
||||
@ -863,6 +880,10 @@ select {
|
||||
width: 1.75rem;
|
||||
}
|
||||
|
||||
.w-auto {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
@ -889,10 +910,6 @@ select {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
@ -905,10 +922,6 @@ select {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.items-baseline {
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
@ -957,6 +970,11 @@ select {
|
||||
border-color: rgb(100 116 139 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.bg-gray-200 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-green-600 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(22 163 74 / var(--tw-bg-opacity));
|
||||
@ -981,6 +999,11 @@ select {
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.px-4 {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.py-1 {
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
@ -999,6 +1022,10 @@ select {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.pt-1 {
|
||||
padding-top: 0.25rem;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
@ -1017,6 +1044,11 @@ select {
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
|
||||
.text-5xl {
|
||||
font-size: 3rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.text-base {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
@ -1037,11 +1069,6 @@ select {
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.font-semibold {
|
||||
font-weight: 600;
|
||||
}
|
||||
@ -1050,6 +1077,11 @@ select {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.text-gray-700 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(55 65 81 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-slate-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(203 213 225 / var(--tw-text-opacity));
|
||||
@ -1285,6 +1317,11 @@ th label {
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.hover\:bg-gray-400:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(156 163 175 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-green-700:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(21 128 61 / var(--tw-bg-opacity));
|
||||
@ -1332,6 +1369,10 @@ th label {
|
||||
--tw-ring-offset-color: #ddd6fe;
|
||||
}
|
||||
|
||||
.group:hover .group-hover\:block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:is(.dark .dark\:bg-gray-800) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
|
||||
|
@ -25,19 +25,37 @@
|
||||
<div class="w-full md:block md:w-auto">
|
||||
<ul
|
||||
class="flex flex-col md:flex-row p-4 mt-4 dark:text-white">
|
||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_game' %}">New Game</a></li>
|
||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_platform' %}">New Platform</a></li>
|
||||
{% if game_available and platform_available %}
|
||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_edition' %}">New Edition</a></li>
|
||||
{% endif %}
|
||||
{% if edition_available %}
|
||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_purchase' %}">New Purchase</a></li>
|
||||
{% endif %}
|
||||
{% if purchase_available %}
|
||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_session' %}">New Session</a></li>
|
||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_device' %}">New Device</a></li>
|
||||
{% endif %}
|
||||
<li class="relative group">
|
||||
<a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_game' %}">New</a>
|
||||
<ul class="absolute hidden text-gray-700 pt-1 group-hover:block w-auto whitespace-nowrap">
|
||||
{% if purchase_available %}
|
||||
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_device' %}">Device</a></li>
|
||||
{% endif %}
|
||||
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_game' %}">Game</a></li>
|
||||
{% if game_available and platform_available %}
|
||||
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_edition' %}">Edition</a></li>
|
||||
{% endif %}
|
||||
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_platform' %}">Platform</a></li>
|
||||
{% if edition_available %}
|
||||
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_purchase' %}">Purchase</a></li>
|
||||
{% endif %}
|
||||
{% if purchase_available %}
|
||||
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_session' %}">Session</a></li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
{% if session_count > 0 %}
|
||||
<li class="relative group">
|
||||
<a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'stats_current_year' %}">Stats</a>
|
||||
<ul class="absolute hidden text-gray-700 pt-1 group-hover:block">
|
||||
{% for year in stats_dropdown_year_range %}
|
||||
<li>
|
||||
<a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'stats_by_year' year %}">{{ year }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'list_sessions' %}">All Sessions</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
98
games/templates/stats.html
Normal file
98
games/templates/stats.html
Normal file
@ -0,0 +1,98 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock title %}
|
||||
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="dark:text-white max-w-sm sm:max-w-xl lg:max-w-3xl mx-auto">
|
||||
<div class="flex justify-center items-center">
|
||||
<form method="get" class="text-center">
|
||||
<label class="text-5xl text-center inline-block mb-10" for="yearSelect">Stats for:</label>
|
||||
<select name="year" id="yearSelect" onchange="this.form.submit();" class="mx-2">
|
||||
{% for year_item in stats_dropdown_year_range %}
|
||||
<option value="{{ year_item }}" {% if year == year_item %}selected{% endif %}>{{ year_item }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
<table class="responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">Hours</th>
|
||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">Games</th>
|
||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">Games ({{ year }})</th>
|
||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">Purchases</th>
|
||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">Spendings ({{ total_spent_currency }})</th>
|
||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">{{ total_spent_currency }}/game</th>
|
||||
</tr>
|
||||
<tbody>
|
||||
<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 font-mono">{{ total_games }}</td>
|
||||
<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 font-mono">{{ all_purchased_this_year.count }}</td>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_spent }}</td>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ spent_per_game }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h1 class="text-5xl text-center my-6">Top games by playtime</h1>
|
||||
<table class="responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">Name</th>
|
||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">Playtime (hours)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for game in top_10_games_by_playtime %}
|
||||
<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 'view_game' game.id %}">{{ game.name }}
|
||||
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ game.formatted_playtime }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<h1 class="text-5xl text-center my-6">Platforms by playtime</h1>
|
||||
<table class="responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">Platform</th>
|
||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">Playtime (hours)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in total_playtime_per_platform %}
|
||||
<tr>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ item.platform_name }}</td>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ item.formatted_playtime }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<h1 class="text-5xl text-center my-6">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 all_purchased_this_year %}
|
||||
<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 }}</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>
|
||||
</div>
|
||||
{% endblock content %}
|
@ -73,4 +73,10 @@ urlpatterns = [
|
||||
{"filter": "ownership_type"},
|
||||
name="list_sessions_by_ownership_type",
|
||||
),
|
||||
path("stats/", views.stats, name="stats_current_year"),
|
||||
path(
|
||||
"stats/<int:year>",
|
||||
views.stats,
|
||||
name="stats_by_year",
|
||||
),
|
||||
]
|
||||
|
108
games/views.py
108
games/views.py
@ -1,10 +1,12 @@
|
||||
from common.time import format_duration, now as now_with_tz
|
||||
from datetime import datetime, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from common.time import now as now_with_tz
|
||||
from common.time import format_duration
|
||||
from django.conf import settings
|
||||
from django.db.models import Sum, F
|
||||
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import reverse
|
||||
from typing import Callable, Any
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from .forms import (
|
||||
GameForm,
|
||||
@ -27,6 +29,10 @@ def model_counts(request):
|
||||
}
|
||||
|
||||
|
||||
def stats_dropdown_year_range(request):
|
||||
return {"stats_dropdown_year_range": range(2018, 2024)}
|
||||
|
||||
|
||||
def add_session(request):
|
||||
context = {}
|
||||
initial = {}
|
||||
@ -55,6 +61,25 @@ def update_session(request, session_id=None):
|
||||
return redirect("list_sessions")
|
||||
|
||||
|
||||
def use_custom_redirect(
|
||||
func: Callable[..., HttpResponse]
|
||||
) -> Callable[..., HttpResponse]:
|
||||
"""
|
||||
Will redirect to "return_path" session variable if set.
|
||||
"""
|
||||
|
||||
def wrapper(request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
|
||||
response = func(request, *args, **kwargs)
|
||||
if isinstance(response, HttpResponseRedirect) and (
|
||||
next_url := request.session.get("return_path")
|
||||
):
|
||||
return HttpResponseRedirect(next_url)
|
||||
return response
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@use_custom_redirect
|
||||
def edit_session(request, session_id=None):
|
||||
context = {}
|
||||
session = Session.objects.get(id=session_id)
|
||||
@ -67,6 +92,7 @@ def edit_session(request, session_id=None):
|
||||
return render(request, "add_session.html", context)
|
||||
|
||||
|
||||
@use_custom_redirect
|
||||
def edit_purchase(request, purchase_id=None):
|
||||
context = {}
|
||||
purchase = Purchase.objects.get(id=purchase_id)
|
||||
@ -79,6 +105,7 @@ def edit_purchase(request, purchase_id=None):
|
||||
return render(request, "add.html", context)
|
||||
|
||||
|
||||
@use_custom_redirect
|
||||
def edit_game(request, game_id=None):
|
||||
context = {}
|
||||
purchase = Game.objects.get(id=game_id)
|
||||
@ -113,9 +140,11 @@ def view_game(request, game_id=None):
|
||||
context["last_session"] = context["sessions"].first()
|
||||
context["first_session"] = context["sessions"].last()
|
||||
context["sessions_with_notes"] = context["sessions"].exclude(note="")
|
||||
request.session["return_path"] = request.path
|
||||
return render(request, "view_game.html", context)
|
||||
|
||||
|
||||
@use_custom_redirect
|
||||
def edit_platform(request, platform_id=None):
|
||||
context = {}
|
||||
purchase = Platform.objects.get(id=platform_id)
|
||||
@ -128,6 +157,7 @@ def edit_platform(request, platform_id=None):
|
||||
return render(request, "add.html", context)
|
||||
|
||||
|
||||
@use_custom_redirect
|
||||
def edit_edition(request, edition_id=None):
|
||||
context = {}
|
||||
edition = Edition.objects.get(id=edition_id)
|
||||
@ -140,6 +170,7 @@ def edit_edition(request, edition_id=None):
|
||||
return render(request, "add.html", context)
|
||||
|
||||
|
||||
@use_custom_redirect
|
||||
def start_game_session(request, game_id: int):
|
||||
last_session = (
|
||||
Session.objects.filter(purchase__edition__game_id=game_id)
|
||||
@ -229,6 +260,75 @@ def list_sessions(
|
||||
return render(request, "list_sessions.html", context)
|
||||
|
||||
|
||||
def stats(request, year: int = 0):
|
||||
selected_year = request.GET.get("year")
|
||||
if selected_year:
|
||||
return HttpResponseRedirect(reverse("stats_by_year", args=[selected_year]))
|
||||
if year == 0:
|
||||
year = now_with_tz().year
|
||||
first_day_of_year = datetime(year, 1, 1)
|
||||
last_day_of_year = datetime(year + 1, 1, 1)
|
||||
year_sessions = Session.objects.filter(timestamp_start__year=year)
|
||||
year_played_purchases = Purchase.objects.filter(
|
||||
session__in=year_sessions
|
||||
).distinct()
|
||||
|
||||
selected_currency = "CZK"
|
||||
all_purchased_this_year = (
|
||||
Purchase.objects.filter(date_purchased__year=year)
|
||||
.filter(price_currency__exact=selected_currency)
|
||||
.filter(date_refunded__exact=None)
|
||||
.order_by("date_purchased")
|
||||
)
|
||||
|
||||
this_year_spendings = all_purchased_this_year.aggregate(total_spent=Sum(F("price")))
|
||||
total_spent = this_year_spendings["total_spent"]
|
||||
|
||||
games_with_playtime = (
|
||||
Game.objects.filter(edition__purchase__session__in=year_sessions)
|
||||
.annotate(
|
||||
total_playtime=Sum(
|
||||
F("edition__purchase__session__duration_calculated")
|
||||
+ F("edition__purchase__session__duration_manual")
|
||||
)
|
||||
)
|
||||
.values("id", "name", "total_playtime")
|
||||
)
|
||||
top_10_games_by_playtime = games_with_playtime.order_by("-total_playtime")[:10]
|
||||
for game in top_10_games_by_playtime:
|
||||
game["formatted_playtime"] = format_duration(game["total_playtime"], "%2.0H")
|
||||
|
||||
total_playtime_per_platform = (
|
||||
year_sessions.values("purchase__platform__name")
|
||||
.annotate(total_playtime=Sum(F("duration_calculated") + F("duration_manual")))
|
||||
.annotate(platform_name=F("purchase__platform__name"))
|
||||
.values("platform_name", "total_playtime")
|
||||
.order_by("-total_playtime")
|
||||
)
|
||||
for item in total_playtime_per_platform:
|
||||
item["formatted_playtime"] = format_duration(item["total_playtime"], "%2.0H")
|
||||
|
||||
context = {
|
||||
"total_hours": format_duration(
|
||||
year_sessions.total_duration_unformatted(), "%2.0H"
|
||||
),
|
||||
"total_games": year_played_purchases.count(),
|
||||
"total_2023_games": year_played_purchases.filter(
|
||||
edition__year_released=year
|
||||
).count(),
|
||||
"top_10_games_by_playtime": top_10_games_by_playtime,
|
||||
"year": year,
|
||||
"total_playtime_per_platform": total_playtime_per_platform,
|
||||
"total_spent": total_spent,
|
||||
"total_spent_currency": selected_currency,
|
||||
"all_purchased_this_year": all_purchased_this_year,
|
||||
"spent_per_game": int(total_spent / all_purchased_this_year.count()),
|
||||
}
|
||||
|
||||
request.session["return_path"] = request.path
|
||||
return render(request, "stats.html", context)
|
||||
|
||||
|
||||
def add_purchase(request):
|
||||
context = {}
|
||||
now = datetime.now()
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "timetracker"
|
||||
version = "1.1.2"
|
||||
version = "1.3.0"
|
||||
description = "A simple time tracker."
|
||||
authors = ["Lukáš Kucharczyk <lukas@kucharczyk.xyz>"]
|
||||
license = "GPL"
|
||||
|
@ -68,6 +68,7 @@ TEMPLATES = [
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"games.views.model_counts",
|
||||
"games.views.stats_dropdown_year_range",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
Reference in New Issue
Block a user