4 Commits

Author SHA1 Message Date
d9d4a3eaa1 Add more sample data
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-19 20:27:45 +01:00
8a1f66bfaf Fix entrypoint.sh 2023-01-19 20:20:50 +01:00
0224afcad9 Don't freak out if there are no sessions 2023-01-19 19:51:24 +01:00
a694406e99 Clean up naming 2023-01-19 19:46:43 +01:00
48 changed files with 67 additions and 99 deletions

2
.gitignore vendored
View File

@ -5,4 +5,4 @@ __pycache__
node_modules
package-lock.json
db.sqlite3
static
src/timetracker/static

View File

@ -1,11 +1,3 @@
## 1.0.0 / 2023-01-20 19:54+01:00
* Breaking
* Due to major re-arranging and re-naming of the folder structure, tables also had to be renamed.
* Fixed
* Sort form fields alphabetically (https://git.kucharczyk.xyz/lukas/timetracker/issues/39, https://git.kucharczyk.xyz/lukas/timetracker/issues/40)
* Start session button starts different game than it says (#44)
## 0.2.5 / 2023-01-18 17:01+01:00
* New

View File

@ -5,10 +5,10 @@
:8000 {
handle_path /static/* {
root * /usr/share/caddy
root * src/timetracker/static/
file_server
}
handle {
reverse_proxy backend:8001
reverse_proxy :8001
}
}

View File

@ -2,11 +2,11 @@ FROM node as css
WORKDIR /app
COPY . /app
RUN npm install && \
npx tailwindcss -i ./common/input.css -o ./static/base.css --minify
npx tailwindcss -i ./src/input.css -o ./src/timetracker/games/static/base.css --minify
FROM python:3.10.9-slim-bullseye
ENV VERSION_NUMBER 1.0.0
ENV VERSION_NUMBER 0.2.5
ENV PROD 1
ENV PYTHONUNBUFFERED=1
@ -15,13 +15,18 @@ RUN apt update && \
bash \
vim \
curl && \
apt install -y debian-keyring debian-archive-keyring apt-transport-https && \
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg && \
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list && \
apt update && \
apt install caddy && \
rm -rf /var/lib/apt/lists/*
RUN useradd -m --uid 1000 timetracker
WORKDIR /home/timetracker/app
COPY . /home/timetracker/app/
RUN chown -R timetracker:timetracker /home/timetracker/app
COPY --from=css ./app/static/base.css /home/timetracker/app/static/base.css
COPY --from=css /app/src/timetracker/games/static/base.css /home/timetracker/app/src/timetracker/games/static/base.css
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
@ -31,4 +36,4 @@ RUN pip install --no-cache-dir poetry
RUN poetry install --without dev
EXPOSE 8000
CMD [ "/entrypoint.sh" ]
ENTRYPOINT [ "/entrypoint.sh" ]

View File

@ -2,52 +2,49 @@ all: css migrate
initialize: npm css migrate sethookdir loadplatforms
HTMLFILES := $(shell find games/templates -type f)
HTMLFILES := $(shell find src/timetracker/games/templates -type f)
npm:
npm install
css: input.css
npx tailwindcss -i ./input.css -o ./games/static/base.css
css: src/input.css
npx tailwindcss -i ./src/input.css -o ./src/timetracker/games/static/base.css
css-dev: css
npx tailwindcss -i ./input.css -o ./games/static/base.css --watch
npx tailwindcss -i ./src/input.css -o ./src/timetracker/games/static/base.css --watch
makemigrations:
poetry run python manage.py makemigrations
poetry run python src/timetracker/manage.py makemigrations
migrate: makemigrations
poetry run python manage.py migrate
poetry run python src/timetracker/manage.py migrate
dev: migrate
poetry run python manage.py runserver
poetry run python src/timetracker/manage.py runserver
caddy:
caddy run --watch
dev-prod: migrate collectstatic
PROD=1 poetry run python -m gunicorn --bind 0.0.0.0:8001 timetracker.asgi:application -k uvicorn.workers.UvicornWorker
cd src/timetracker/; PROD=1 poetry run python -m gunicorn --bind 0.0.0.0:8001 timetracker.asgi:application -k uvicorn.workers.UvicornWorker
dumpgames:
poetry run python manage.py dumpdata --format yaml games --output tracker_fixture.yaml
poetry run python src/timetracker/manage.py dumpdata --format yaml games --output tracker_fixture.yaml
loadplatforms:
poetry run python manage.py loaddata platforms.yaml
loadall:
poetry run python manage.py loaddata data.yaml
poetry run python src/timetracker/manage.py loaddata platforms.yaml
loadsample:
poetry run python manage.py loaddata sample.yaml
poetry run python src/timetracker/manage.py loaddata sample.yaml
createsuperuser:
poetry run python manage.py createsuperuser
poetry run python src/timetracker/manage.py createsuperuser
shell:
poetry run python manage.py shell
poetry run python src/timetracker/manage.py shell
collectstatic:
poetry run python manage.py collectstatic --clear --no-input
poetry run python src/timetracker/manage.py collectstatic --clear --no-input
poetry.lock: pyproject.toml
poetry install
@ -59,6 +56,6 @@ date:
poetry run python -c 'import datetime; from zoneinfo import ZoneInfo; print(datetime.datetime.isoformat(datetime.datetime.now(ZoneInfo("Europe/Prague")), timespec="minutes", sep=" "))'
cleanstatic:
rm -r static/*
rm -r src/timetracker/static/*
clean: cleanstatic

View File

@ -1,17 +0,0 @@
---
services:
timetracker:
image: registry.kucharczyk.xyz/timetracker
build:
context: .
dockerfile: Dockerfile
container_name: timetracker
environment:
- TZ=Europe/Prague
- CSRF_TRUSTED_ORIGINS="https://tracker.kucharczyk.xyz"
user: "1000"
# volumes:
# - "db:/home/timetracker/app/src/timetracker/db.sqlite3"
ports:
- "8000:8000"
restart: unless-stopped

View File

@ -1,28 +1,17 @@
---
services:
backend:
timetracker:
image: registry.kucharczyk.xyz/timetracker
build:
context: .
dockerfile: Dockerfile
container_name: timetracker
environment:
- TZ=Europe/Prague
- CSRF_TRUSTED_ORIGINS="https://tracker.kucharczyk.xyz"
user: "1000"
volumes:
- "static-files:/home/timetracker/app/static"
restart: unless-stopped
frontend:
image: caddy
volumes:
- "static-files:/usr/share/caddy"
- "$PWD/Caddyfile:/etc/caddy/Caddyfile"
# volumes:
# - "db:/home/timetracker/app/src/timetracker/db.sqlite3"
ports:
- "8000:8000"
depends_on:
- backend
volumes:
static-files:
restart: unless-stopped

View File

@ -2,10 +2,12 @@
# Apply database migrations
set -euo pipefail
echo "Apply database migrations"
poetry run python manage.py migrate
poetry run python src/timetracker/manage.py migrate
echo "Collect static files"
poetry run python manage.py collectstatic --clear --no-input
poetry run python src/timetracker/manage.py collectstatic --clear --no-input
echo "Starting app"
poetry run python -m gunicorn --bind 0.0.0.0:8001 timetracker.asgi:application -k uvicorn.workers.UvicornWorker --access-logfile - --error-logfile -
echo "Starting server"
caddy start
cd src/timetracker || exit
poetry run python -m gunicorn --bind 0.0.0.0:8001 root.asgi:application -k uvicorn.workers.UvicornWorker --access-logfile - --error-logfile -

View File

@ -1,11 +1,11 @@
[tool.poetry]
name = "timetracker"
version = "1.0.0"
version = "0.2.5"
description = "A simple time tracker."
authors = ["Lukáš Kucharczyk <lukas@kucharczyk.xyz>"]
license = "GPL"
readme = "README.md"
packages = [{include = "timetracker"}]
packages = [{include = "timetracker", from = "src"}]
[tool.poetry.dependencies]
python = "^3.10"
@ -30,5 +30,10 @@ isort = "^5.11.4"
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.pytest.ini_options]
pythonpath = [
"src"
]
[tool.poetry.scripts]
timetracker-import = "common.import_data:import_from_file"
timetracker-import = "timetracker.common.util.data_import:import_from_file"

View File

@ -1,8 +1,7 @@
from timetracker.games.models import Session, Game, Purchase, Platform
import csv
from typing import TypeAlias
from games.models import Game
DataList: TypeAlias = list[dict[str, str]] | None

View File

@ -1,6 +1,6 @@
from django.contrib import admin
from games.models import Game, Platform, Purchase, Session
from .models import Game, Platform, Purchase, Session
# Register your models here.
admin.site.register(Game)

View File

@ -1,11 +1,9 @@
from django import forms
from games.models import Game, Platform, Purchase, Session
from .models import Game, Platform, Purchase, Session
class SessionForm(forms.ModelForm):
purchase = forms.ModelChoiceField(queryset=Purchase.objects.order_by("game__name"))
class Meta:
model = Session
fields = [
@ -18,9 +16,6 @@ class SessionForm(forms.ModelForm):
class PurchaseForm(forms.ModelForm):
game = forms.ModelChoiceField(queryset=Game.objects.order_by("name"))
platform = forms.ModelChoiceField(queryset=Platform.objects.order_by("name"))
class Meta:
model = Purchase
fields = ["game", "platform", "date_purchased", "date_refunded"]

View File

@ -2,7 +2,7 @@ from datetime import datetime, timedelta
from typing import Any
from zoneinfo import ZoneInfo
from common.time import format_duration
from timetracker.common.util.time import format_duration
from django.conf import settings
from django.db import models
from django.db.models import F, Manager, Sum

View File

@ -18,7 +18,7 @@
{% if purchase %}<a class="dark:text-white hover:underline block" href="{% url 'list_sessions_by_game' purchase.game.id %}">See all platforms</a>{% endif %}
{% endif %}
{% if dataset.count >= 1 %}
<a class="clear-both" href="{% url 'start_session' last.purchase.id %}">
<a class="clear-both" href="{% url 'start_session' dataset.last.purchase.id %}">
<button type="button" title="Track last tracked" class="mt-10 py-1 px-2 bg-green-600 hover:bg-green-700 focus:ring-green-500 focus:ring-offset-blue-200 text-white transition ease-in duration-200 text-center text-base font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-lg ">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="self-center w-6 h-6 inline">
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z" />

View File

@ -13,7 +13,9 @@ def version_date():
"%d-%b-%Y %H:%m",
time.gmtime(
os.path.getmtime(
os.path.abspath(os.path.join(settings.BASE_DIR, "pyproject.toml"))
os.path.abspath(
os.path.join(settings.BASE_DIR, "..", "..", "pyproject.toml")
)
)
),
)

View File

@ -1,6 +1,6 @@
from django.urls import path
from games import views
from . import views
urlpatterns = [
path("", views.index, name="index"),

View File

@ -1,8 +1,8 @@
from datetime import datetime
from zoneinfo import ZoneInfo
from common.plots import playtime_over_time_chart
from common.time import now as now_with_tz
from common.util.plots import playtime_over_time_chart
from common.util.time import now as now_with_tz
from django.conf import settings
from django.shortcuts import redirect, render
@ -85,8 +85,7 @@ def list_sessions(request, filter="", purchase_id="", platform_id="", game_id=""
# cannot use dataset[0] here because that might be only partial QuerySet
context["last"] = Session.objects.all().order_by("timestamp_start").last()
# charts are always oldest->newest
if Session.objects.count() >= 2:
context["chart"] = playtime_over_time_chart(dataset.order_by("timestamp_start"))
context["chart"] = playtime_over_time_chart(dataset.order_by("timestamp_start"))
return render(request, "list_sessions.html", context)

2
manage.py → src/timetracker/manage.py Executable file → Normal file
View File

@ -6,7 +6,7 @@ import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "timetracker.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "root.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:

View File

@ -1,5 +1,5 @@
"""
ASGI config for timetracker project.
ASGI config for root project.
It exposes the ASGI callable as a module-level variable named ``application``.
@ -11,6 +11,6 @@ import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "timetracker.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "root.settings")
application = get_asgi_application()

View File

@ -54,7 +54,7 @@ MIDDLEWARE = [
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "timetracker.urls"
ROOT_URLCONF = "root.urls"
TEMPLATES = [
{
@ -73,7 +73,7 @@ TEMPLATES = [
},
]
WSGI_APPLICATION = "timetracker.wsgi.application"
WSGI_APPLICATION = "root.wsgi.application"
# Database

View File

@ -11,6 +11,6 @@ import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "timetracker.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "root.settings")
application = get_wsgi_application()

View File

@ -1,6 +1,6 @@
module.exports = {
darkMode: 'class',
content: ["./**/*.{html,js}"],
content: ["./src/**/*.{html,js}"],
theme: {
fontFamily: {
sans: ['Inter', 'sans-serif'],

View File

@ -1,7 +1,7 @@
import unittest
from datetime import timedelta
from common.time import format_duration
from timetracker.common.util.time import format_duration
class FormatDurationTest(unittest.TestCase):