Compare commits
11 Commits
1.4.0
...
d211326c3f
Author | SHA1 | Date | |
---|---|---|---|
d211326c3f | |||
270a291f05 | |||
13b750ca92 | |||
015b6db2f7 | |||
667b161fff | |||
5958cbf4a6 | |||
3b37f2c3f0 | |||
4517ff2b5a | |||
884ce13e26 | |||
dd219bae9d | |||
60d29090a1 |
@ -1,4 +1,9 @@
|
|||||||
## Unreleased
|
### Unreleased
|
||||||
|
|
||||||
|
## New
|
||||||
|
* Add stat for finished this year's games
|
||||||
|
|
||||||
|
## 1.4.0 / 2023-11-09 21:01+01:00
|
||||||
|
|
||||||
### New
|
### New
|
||||||
* More fields are now optional. This is to make it easier to add new items in bulk.
|
* More fields are now optional. This is to make it easier to add new items in bulk.
|
||||||
@ -23,6 +28,8 @@
|
|||||||
* Unfinished (count)
|
* Unfinished (count)
|
||||||
* Refunded (count)
|
* Refunded (count)
|
||||||
* Backlog Decrease (count)
|
* Backlog Decrease (count)
|
||||||
|
* New workflow:
|
||||||
|
* Adding Game, Edition, Purchase, and Session in a row is now much faster
|
||||||
|
|
||||||
### Improved
|
### Improved
|
||||||
* game overview: simplify playtime range display
|
* game overview: simplify playtime range display
|
||||||
|
@ -6,7 +6,7 @@ RUN npm install && \
|
|||||||
|
|
||||||
FROM python:3.10.9-slim-bullseye
|
FROM python:3.10.9-slim-bullseye
|
||||||
|
|
||||||
ENV VERSION_NUMBER 1.3.0
|
ENV VERSION_NUMBER 1.4.0
|
||||||
ENV PROD 1
|
ENV PROD 1
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ def format_duration(
|
|||||||
# timestamps where end is before start
|
# timestamps where end is before start
|
||||||
if seconds_total < 0:
|
if seconds_total < 0:
|
||||||
seconds_total = 0
|
seconds_total = 0
|
||||||
days = hours = minutes = seconds = 0
|
days = hours = hours_float = minutes = seconds = 0
|
||||||
remainder = seconds = seconds_total
|
remainder = seconds = seconds_total
|
||||||
if "%d" in format_string:
|
if "%d" in format_string:
|
||||||
days, remainder = divmod(seconds_total, day_seconds)
|
days, remainder = divmod(seconds_total, day_seconds)
|
||||||
@ -55,7 +55,7 @@ def format_duration(
|
|||||||
minutes, seconds = divmod(remainder, minute_seconds)
|
minutes, seconds = divmod(remainder, minute_seconds)
|
||||||
literals = {
|
literals = {
|
||||||
"d": str(days),
|
"d": str(days),
|
||||||
"H": str(hours),
|
"H": str(hours) if "m" not in format_string else str(hours_float),
|
||||||
"m": str(minutes),
|
"m": str(minutes),
|
||||||
"s": str(seconds),
|
"s": str(seconds),
|
||||||
"r": str(seconds_total),
|
"r": str(seconds_total),
|
||||||
|
@ -151,7 +151,7 @@ class Session(models.Model):
|
|||||||
objects = SessionQuerySet.as_manager()
|
objects = SessionQuerySet.as_manager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
mark = ", manual" if self.duration_manual != None else ""
|
mark = ", manual" if self.is_manual() else ""
|
||||||
return f"{str(self.purchase)} {str(self.timestamp_start.date())} ({self.duration_formatted()}{mark})"
|
return f"{str(self.purchase)} {str(self.timestamp_start.date())} ({self.duration_formatted()}{mark})"
|
||||||
|
|
||||||
def finish_now(self):
|
def finish_now(self):
|
||||||
@ -163,7 +163,7 @@ class Session(models.Model):
|
|||||||
def duration_seconds(self) -> timedelta:
|
def duration_seconds(self) -> timedelta:
|
||||||
manual = timedelta(0)
|
manual = timedelta(0)
|
||||||
calculated = timedelta(0)
|
calculated = timedelta(0)
|
||||||
if not self.duration_manual in (None, 0, timedelta(0)):
|
if self.is_manual():
|
||||||
manual = self.duration_manual
|
manual = self.duration_manual
|
||||||
if self.timestamp_end != None and self.timestamp_start != None:
|
if self.timestamp_end != None and self.timestamp_start != None:
|
||||||
calculated = self.timestamp_end - self.timestamp_start
|
calculated = self.timestamp_end - self.timestamp_start
|
||||||
@ -173,6 +173,9 @@ class Session(models.Model):
|
|||||||
result = format_duration(self.duration_seconds(), "%02.0H:%02.0m")
|
result = format_duration(self.duration_seconds(), "%02.0H:%02.0m")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def is_manual(self) -> bool:
|
||||||
|
return not self.duration_manual == timedelta(0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def duration_sum(self) -> str:
|
def duration_sum(self) -> str:
|
||||||
return Session.objects.all().total_duration_formatted()
|
return Session.objects.all().total_duration_formatted()
|
||||||
|
29
games/templates/add_edition.html
Normal file
29
games/templates/add_edition.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<table class="mx-auto">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{{ form.as_table }}
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td><input type="submit" name="submit" value="Submit"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td><input type="submit" name="submit_and_redirect" value="Submit & Create Purchase"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% if script_name %}
|
||||||
|
<script type="module" src="{% static 'js/'|add:script_name %}"></script>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock scripts %}
|
||||||
|
|
29
games/templates/add_game.html
Normal file
29
games/templates/add_game.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<table class="mx-auto">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{{ form.as_table }}
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td><input type="submit" name="submit" value="Submit"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td><input type="submit" name="submit_and_redirect" value="Submit & Create Edition"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% if script_name %}
|
||||||
|
<script type="module" src="{% static 'js/'|add:script_name %}"></script>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock scripts %}
|
||||||
|
|
29
games/templates/add_purchase.html
Normal file
29
games/templates/add_purchase.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<table class="mx-auto">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{{ form.as_table }}
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td><input type="submit" name="submit" value="Submit"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td><input type="submit" name="submit_and_redirect" value="Submit & Create Session"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% if script_name %}
|
||||||
|
<script type="module" src="{% static 'js/'|add:script_name %}"></script>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock scripts %}
|
||||||
|
|
@ -45,6 +45,10 @@
|
|||||||
<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>
|
||||||
|
<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>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,6 +13,11 @@ urlpatterns = [
|
|||||||
path("add-game/", views.add_game, name="add_game"),
|
path("add-game/", views.add_game, name="add_game"),
|
||||||
path("add-platform/", views.add_platform, name="add_platform"),
|
path("add-platform/", views.add_platform, name="add_platform"),
|
||||||
path("add-session/", views.add_session, name="add_session"),
|
path("add-session/", views.add_session, name="add_session"),
|
||||||
|
path(
|
||||||
|
"add-session-for-purchase/<int:purchase_id>",
|
||||||
|
views.add_session,
|
||||||
|
name="add_session_for_purchase",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"update-session/by-session/<int:session_id>",
|
"update-session/by-session/<int:session_id>",
|
||||||
views.update_session,
|
views.update_session,
|
||||||
@ -34,7 +39,17 @@ urlpatterns = [
|
|||||||
# name="delete_session",
|
# name="delete_session",
|
||||||
# ),
|
# ),
|
||||||
path("add-purchase/", views.add_purchase, name="add_purchase"),
|
path("add-purchase/", views.add_purchase, name="add_purchase"),
|
||||||
|
path(
|
||||||
|
"add-purchase-for-edition/<int:edition_id>",
|
||||||
|
views.add_purchase,
|
||||||
|
name="add_purchase_for_edition",
|
||||||
|
),
|
||||||
path("add-edition/", views.add_edition, name="add_edition"),
|
path("add-edition/", views.add_edition, name="add_edition"),
|
||||||
|
path(
|
||||||
|
"add-edition-for-game/<int:game_id>",
|
||||||
|
views.add_edition,
|
||||||
|
name="add_edition_for_game",
|
||||||
|
),
|
||||||
path("edit-edition/<int:edition_id>", views.edit_edition, name="edit_edition"),
|
path("edit-edition/<int:edition_id>", views.edit_edition, name="edit_edition"),
|
||||||
path("game/<int:game_id>/view", views.view_game, name="view_game"),
|
path("game/<int:game_id>/view", views.view_game, name="view_game"),
|
||||||
path("game/<int:game_id>/edit", views.edit_game, name="edit_game"),
|
path("game/<int:game_id>/edit", views.edit_game, name="edit_game"),
|
||||||
|
128
games/views.py
128
games/views.py
@ -32,24 +32,38 @@ def model_counts(request):
|
|||||||
|
|
||||||
|
|
||||||
def stats_dropdown_year_range(request):
|
def stats_dropdown_year_range(request):
|
||||||
return {"stats_dropdown_year_range": range(2018, 2024)}
|
result = {
|
||||||
|
"stats_dropdown_year_range": range(
|
||||||
|
datetime.now(ZoneInfo(settings.TIME_ZONE)).year, 1999, -1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def add_session(request):
|
def add_session(request, purchase_id=None):
|
||||||
context = {}
|
context = {}
|
||||||
initial = {}
|
initial = {"timestamp_start": now_with_tz()}
|
||||||
|
|
||||||
now = now_with_tz()
|
|
||||||
initial["timestamp_start"] = now
|
|
||||||
|
|
||||||
last = Session.objects.all().last()
|
last = Session.objects.all().last()
|
||||||
if last != None:
|
if last != None:
|
||||||
initial["purchase"] = last.purchase
|
initial["purchase"] = last.purchase
|
||||||
|
|
||||||
form = SessionForm(request.POST or None, initial=initial)
|
if request.method == "POST":
|
||||||
if form.is_valid():
|
form = SessionForm(request.POST or None, initial=initial)
|
||||||
form.save()
|
if form.is_valid():
|
||||||
return redirect("list_sessions")
|
form.save()
|
||||||
|
return redirect("list_sessions")
|
||||||
|
else:
|
||||||
|
if purchase_id:
|
||||||
|
purchase = Purchase.objects.get(id=purchase_id)
|
||||||
|
form = SessionForm(
|
||||||
|
initial={
|
||||||
|
**initial,
|
||||||
|
"purchase": purchase,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
form = SessionForm(initial=initial)
|
||||||
|
|
||||||
context["title"] = "Add New Session"
|
context["title"] = "Add New Session"
|
||||||
context["form"] = form
|
context["form"] = form
|
||||||
@ -322,7 +336,7 @@ def stats(request, year: int = 0):
|
|||||||
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"))
|
||||||
)
|
)
|
||||||
total_spent = this_year_spendings["total_spent"]
|
total_spent = this_year_spendings["total_spent"] or 0
|
||||||
|
|
||||||
games_with_playtime = (
|
games_with_playtime = (
|
||||||
Game.objects.filter(edition__purchase__session__in=this_year_sessions)
|
Game.objects.filter(edition__purchase__session__in=this_year_sessions)
|
||||||
@ -371,9 +385,15 @@ def stats(request, year: int = 0):
|
|||||||
"spent_per_game": int(
|
"spent_per_game": int(
|
||||||
safe_division(total_spent, this_year_purchases_without_refunded.count())
|
safe_division(total_spent, this_year_purchases_without_refunded.count())
|
||||||
),
|
),
|
||||||
"all_finished_this_year": purchases_finished_this_year,
|
"all_finished_this_year": purchases_finished_this_year.order_by(
|
||||||
"this_year_finished_this_year": purchases_finished_this_year_released_this_year,
|
"date_finished"
|
||||||
"purchased_this_year_finished_this_year": purchased_this_year_finished_this_year,
|
),
|
||||||
|
"this_year_finished_this_year": purchases_finished_this_year_released_this_year.order_by(
|
||||||
|
"date_finished"
|
||||||
|
),
|
||||||
|
"purchased_this_year_finished_this_year": purchased_this_year_finished_this_year.order_by(
|
||||||
|
"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),
|
||||||
@ -397,45 +417,91 @@ def stats(request, year: int = 0):
|
|||||||
return render(request, "stats.html", context)
|
return render(request, "stats.html", context)
|
||||||
|
|
||||||
|
|
||||||
def add_purchase(request):
|
def add_purchase(request, edition_id=None):
|
||||||
context = {}
|
context = {}
|
||||||
now = datetime.now()
|
initial = {"date_purchased": now_with_tz()}
|
||||||
initial = {"date_purchased": now}
|
|
||||||
form = PurchaseForm(request.POST or None, initial=initial)
|
if request.method == "POST":
|
||||||
if form.is_valid():
|
form = PurchaseForm(request.POST or None, initial=initial)
|
||||||
form.save()
|
if form.is_valid():
|
||||||
return redirect("index")
|
purchase = form.save()
|
||||||
|
if "submit_and_redirect" in request.POST:
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
reverse(
|
||||||
|
"add_session_for_purchase", kwargs={"purchase_id": purchase.id}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return redirect("index")
|
||||||
|
else:
|
||||||
|
if edition_id:
|
||||||
|
edition = Edition.objects.get(id=edition_id)
|
||||||
|
form = PurchaseForm(
|
||||||
|
initial={
|
||||||
|
**initial,
|
||||||
|
"edition": edition,
|
||||||
|
"platform": edition.platform,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
form = PurchaseForm(initial=initial)
|
||||||
|
|
||||||
context["form"] = form
|
context["form"] = form
|
||||||
context["title"] = "Add New Purchase"
|
context["title"] = "Add New Purchase"
|
||||||
context["script_name"] = "add_purchase.js"
|
context["script_name"] = "add_purchase.js"
|
||||||
return render(request, "add.html", context)
|
return render(request, "add_purchase.html", context)
|
||||||
|
|
||||||
|
|
||||||
def add_game(request):
|
def add_game(request):
|
||||||
context = {}
|
context = {}
|
||||||
form = GameForm(request.POST or None)
|
form = GameForm(request.POST or None)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
game = form.save()
|
||||||
return redirect("index")
|
if "submit_and_redirect" in request.POST:
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
reverse("add_edition_for_game", kwargs={"game_id": game.id})
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return redirect("index")
|
||||||
|
|
||||||
context["form"] = form
|
context["form"] = form
|
||||||
context["title"] = "Add New Game"
|
context["title"] = "Add New Game"
|
||||||
context["script_name"] = "add_game.js"
|
context["script_name"] = "add_game.js"
|
||||||
return render(request, "add.html", context)
|
return render(request, "add_game.html", context)
|
||||||
|
|
||||||
|
|
||||||
def add_edition(request):
|
def add_edition(request, game_id=None):
|
||||||
context = {}
|
context = {}
|
||||||
form = EditionForm(request.POST or None)
|
if request.method == "POST":
|
||||||
if form.is_valid():
|
form = EditionForm(request.POST or None)
|
||||||
form.save()
|
if form.is_valid():
|
||||||
return redirect("index")
|
edition = form.save()
|
||||||
|
if "submit_and_redirect" in request.POST:
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
reverse(
|
||||||
|
"add_purchase_for_edition", kwargs={"edition_id": edition.id}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return redirect("index")
|
||||||
|
else:
|
||||||
|
if game_id:
|
||||||
|
game = Game.objects.get(id=game_id)
|
||||||
|
form = EditionForm(
|
||||||
|
initial={
|
||||||
|
"game": game,
|
||||||
|
"name": game.name,
|
||||||
|
"sort_name": game.sort_name,
|
||||||
|
"year_released": game.year_released,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
form = EditionForm()
|
||||||
|
|
||||||
context["form"] = form
|
context["form"] = form
|
||||||
context["title"] = "Add New Edition"
|
context["title"] = "Add New Edition"
|
||||||
context["script_name"] = "add_edition.js"
|
context["script_name"] = "add_edition.js"
|
||||||
return render(request, "add.html", context)
|
return render(request, "add_edition.html", context)
|
||||||
|
|
||||||
|
|
||||||
def add_platform(request):
|
def add_platform(request):
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "timetracker"
|
name = "timetracker"
|
||||||
version = "1.3.0"
|
version = "1.4.0"
|
||||||
description = "A simple time tracker."
|
description = "A simple time tracker."
|
||||||
authors = ["Lukáš Kucharczyk <lukas@kucharczyk.xyz>"]
|
authors = ["Lukáš Kucharczyk <lukas@kucharczyk.xyz>"]
|
||||||
license = "GPL"
|
license = "GPL"
|
||||||
|
89
tests/test_paths_return_200.py
Normal file
89
tests/test_paths_return_200.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import django
|
||||||
|
import os
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
from datetime import datetime
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "timetracker.settings")
|
||||||
|
django.setup()
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from games.models import Game, Edition, Purchase, Session, Platform
|
||||||
|
|
||||||
|
ZONEINFO = ZoneInfo(settings.TIME_ZONE)
|
||||||
|
|
||||||
|
|
||||||
|
class PathWorksTest(TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
pl = Platform(name="Test Platform")
|
||||||
|
pl.save()
|
||||||
|
g = Game(name="The Test Game")
|
||||||
|
g.save()
|
||||||
|
e = Edition(game=g, name="The Test Game Edition", platform=pl)
|
||||||
|
e.save()
|
||||||
|
p = Purchase(
|
||||||
|
edition=e,
|
||||||
|
platform=pl,
|
||||||
|
date_purchased=datetime(2022, 9, 26, 14, 58, tzinfo=ZONEINFO),
|
||||||
|
)
|
||||||
|
p.save()
|
||||||
|
s = Session(
|
||||||
|
purchase=p,
|
||||||
|
timestamp_start=datetime(2022, 9, 26, 14, 58, tzinfo=ZONEINFO),
|
||||||
|
timestamp_end=datetime(2022, 9, 26, 17, 38, tzinfo=ZONEINFO),
|
||||||
|
)
|
||||||
|
s.save()
|
||||||
|
self.testSession = s
|
||||||
|
return super().setUp()
|
||||||
|
|
||||||
|
def test_add_device_returns_200(self):
|
||||||
|
url = reverse("add_device")
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_add_platform_returns_200(self):
|
||||||
|
url = reverse("add_platform")
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_add_game_returns_200(self):
|
||||||
|
url = reverse("add_game")
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_add_edition_returns_200(self):
|
||||||
|
url = reverse("add_edition")
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_add_purchase_returns_200(self):
|
||||||
|
url = reverse("add_purchase")
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_add_session_returns_200(self):
|
||||||
|
url = reverse("add_session")
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_edit_session_returns_200(self):
|
||||||
|
id = self.testSession.id
|
||||||
|
url = reverse("edit_session", args=[id])
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_view_game_returns_200(self):
|
||||||
|
url = reverse("view_game", args=[1])
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_edit_game_returns_200(self):
|
||||||
|
url = reverse("edit_game", args=[1])
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_list_sessions_returns_200(self):
|
||||||
|
url = reverse("list_sessions")
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
38
tests/test_session_formatting.py
Normal file
38
tests/test_session_formatting.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import django
|
||||||
|
import os
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.db import models
|
||||||
|
from datetime import datetime
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "timetracker.settings")
|
||||||
|
django.setup()
|
||||||
|
from django.conf import settings
|
||||||
|
from games.models import Game, Edition, Purchase, Session
|
||||||
|
|
||||||
|
ZONEINFO = ZoneInfo(settings.TIME_ZONE)
|
||||||
|
|
||||||
|
|
||||||
|
class FormatDurationTest(TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
return super().setUp()
|
||||||
|
|
||||||
|
def test_duration_format(self):
|
||||||
|
g = Game(name="The Test Game")
|
||||||
|
g.save()
|
||||||
|
e = Edition(game=g, name="The Test Game Edition")
|
||||||
|
e.save()
|
||||||
|
p = Purchase(
|
||||||
|
edition=e, date_purchased=datetime(2022, 9, 26, 14, 58, tzinfo=ZONEINFO)
|
||||||
|
)
|
||||||
|
p.save()
|
||||||
|
s = Session(
|
||||||
|
purchase=p,
|
||||||
|
timestamp_start=datetime(2022, 9, 26, 14, 58, tzinfo=ZONEINFO),
|
||||||
|
timestamp_end=datetime(2022, 9, 26, 17, 38, tzinfo=ZONEINFO),
|
||||||
|
)
|
||||||
|
s.save()
|
||||||
|
self.assertEqual(
|
||||||
|
s.duration_formatted(),
|
||||||
|
"02:40",
|
||||||
|
)
|
@ -83,6 +83,16 @@ class FormatDurationTest(unittest.TestCase):
|
|||||||
result = format_duration(delta, "%r seconds")
|
result = format_duration(delta, "%r seconds")
|
||||||
self.assertEqual(result, "0 seconds")
|
self.assertEqual(result, "0 seconds")
|
||||||
|
|
||||||
|
def test_specific(self):
|
||||||
|
delta = timedelta(hours=2, minutes=40)
|
||||||
|
result = format_duration(delta, "%H:%m")
|
||||||
|
self.assertEqual(result, "2:40")
|
||||||
|
|
||||||
|
def test_specific_precise_if_unncessary(self):
|
||||||
|
delta = timedelta(hours=2, minutes=40)
|
||||||
|
result = format_duration(delta, "%02.0H:%02.0m")
|
||||||
|
self.assertEqual(result, "02:40")
|
||||||
|
|
||||||
def test_all_at_once(self):
|
def test_all_at_once(self):
|
||||||
delta = timedelta(days=50, hours=10, minutes=34, seconds=24)
|
delta = timedelta(days=50, hours=10, minutes=34, seconds=24)
|
||||||
result = format_duration(
|
result = format_duration(
|
||||||
|
@ -150,5 +150,3 @@ if _csrf_trusted_origins:
|
|||||||
CSRF_TRUSTED_ORIGINS = _csrf_trusted_origins.split(",")
|
CSRF_TRUSTED_ORIGINS = _csrf_trusted_origins.split(",")
|
||||||
else:
|
else:
|
||||||
CSRF_TRUSTED_ORIGINS = []
|
CSRF_TRUSTED_ORIGINS = []
|
||||||
|
|
||||||
USE_L10N = False
|
|
||||||
|
Reference in New Issue
Block a user