Compare commits
23 Commits
63504a5768
...
0.1.0
Author | SHA1 | Date | |
---|---|---|---|
250f841e00 | |||
89adf479f6 | |||
5f9ca5781f | |||
1a2f0b974d | |||
c6bb60bbbb | |||
126e758172 | |||
6e4db38ee4 | |||
aae05f23e7 | |||
cd35af471a | |||
d896a37779 | |||
eec8f1b9f5
|
|||
b695a35fb1 | |||
76bd923a00 | |||
811e7771cc | |||
7d31f6f416 | |||
c03b9fe8e1 | |||
ec4a095425 | |||
f2076a6cd0 | |||
4d52fd21f8 | |||
ab9d8d1ae3 | |||
9d142126b1 | |||
0f2f0d281e | |||
a115adae28 |
@ -8,7 +8,11 @@ steps:
|
|||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
settings:
|
settings:
|
||||||
repo: registry.kucharczyk.xyz/timetracker
|
repo: registry.kucharczyk.xyz/timetracker
|
||||||
tags: latest
|
environment:
|
||||||
|
VERSION_NUMBER: $(git describe --tags --abbrev=0)
|
||||||
|
tags:
|
||||||
|
- latest
|
||||||
|
- $VERSION_NUMBER
|
||||||
trigger:
|
trigger:
|
||||||
event:
|
event:
|
||||||
- push
|
- push
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
.venv
|
.venv
|
||||||
node_modules
|
node_modules
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
db.sqlite3
|
16
Dockerfile
16
Dockerfile
@ -1,17 +1,15 @@
|
|||||||
FROM python:3.10-slim-bullseye
|
FROM python:3.10-slim-bullseye
|
||||||
RUN apt update \
|
|
||||||
&& apt install --no-install-recommends --yes \
|
|
||||||
git \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
ENV VIRTUAL_ENV=/opt/venv
|
ENV VIRTUAL_ENV=/opt/venv
|
||||||
RUN python3 -m venv pip $VIRTUAL_ENV
|
RUN python3 -m venv pip $VIRTUAL_ENV
|
||||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||||
RUN pip install --no-cache-dir poetry
|
RUN pip install --no-cache-dir poetry
|
||||||
RUN useradd --create-home --uid 1000 timetracker
|
RUN useradd --create-home --uid 1000 timetracker
|
||||||
RUN git clone https://git.kucharczyk.xyz/lukas/timetracker.git /home/timetracker/app
|
|
||||||
WORKDIR /home/timetracker/app
|
WORKDIR /home/timetracker/app
|
||||||
RUN chown -R timetracker /home/timetracker/app
|
COPY . /home/timetracker/app/
|
||||||
RUN poetry install
|
RUN chown -R timetracker:timetracker /home/timetracker/app
|
||||||
EXPOSE 8000
|
RUN poetry install --without dev
|
||||||
|
COPY entrypoint.sh /
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
USER timetracker
|
USER timetracker
|
||||||
CMD [ "python3", "src/web/manage.py", "runserver", "0.0.0.0:8000" ]
|
EXPOSE 8000
|
||||||
|
ENTRYPOINT [ "/entrypoint.sh" ]
|
37
Makefile
Normal file
37
Makefile
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
.PHONY: createsuperuser
|
||||||
|
|
||||||
|
all: css migrate
|
||||||
|
|
||||||
|
initialize: npm css migrate loadplatforms
|
||||||
|
|
||||||
|
HTMLFILES := $(shell find src/web/tracker/templates -type f)
|
||||||
|
|
||||||
|
npm:
|
||||||
|
npm install
|
||||||
|
|
||||||
|
css: src/input.css
|
||||||
|
npx tailwindcss -i ./src/input.css -o ./src/web/tracker/static/base.css
|
||||||
|
|
||||||
|
css-dev: css
|
||||||
|
npx tailwindcss -i ./src/input.css -o ./src/web/tracker/static/base.css --watch
|
||||||
|
|
||||||
|
makemigrations:
|
||||||
|
python src/web/manage.py makemigrations
|
||||||
|
|
||||||
|
migrate: makemigrations
|
||||||
|
python src/web/manage.py migrate
|
||||||
|
|
||||||
|
dev: migrate
|
||||||
|
python src/web/manage.py runserver
|
||||||
|
|
||||||
|
dumptracker:
|
||||||
|
python src/web/manage.py dumpdata --format yaml tracker --output tracker_fixture.yaml
|
||||||
|
|
||||||
|
loadplatforms:
|
||||||
|
python src/web/manage.py loaddata platforms.yaml
|
||||||
|
|
||||||
|
loadsample:
|
||||||
|
python src/web/manage.py loaddata sample.yaml
|
||||||
|
|
||||||
|
createsuperuser:
|
||||||
|
python src/web/manage.py createsuperuser
|
8
entrypoint.sh
Normal file
8
entrypoint.sh
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Apply database migrations
|
||||||
|
echo "Apply database migrations"
|
||||||
|
python src/web/manage.py migrate
|
||||||
|
|
||||||
|
# Start server
|
||||||
|
echo "Starting server"
|
||||||
|
python src/web/manage.py runserver 0.0.0.0:8000
|
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "timelogger"
|
name = "timetracker"
|
||||||
version = "0.1.0"
|
version = "0.0.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"
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
form label {
|
||||||
|
@apply dark:text-slate-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
form input,select,textarea {
|
||||||
|
@apply dark:bg-slate-500 dark:border dark:border-slate-900 dark:text-slate-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
form input[type=submit] {
|
||||||
|
@apply p-2 bg-purple-900;
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 4.1.4 on 2023-01-02 18:55
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("tracker", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="session",
|
||||||
|
name="duration_manual",
|
||||||
|
field=models.DurationField(
|
||||||
|
blank=True, default=datetime.timedelta(0), null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 4.1.4 on 2023-01-02 23:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("tracker", "0002_alter_session_duration_manual"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="session",
|
||||||
|
name="duration_manual",
|
||||||
|
field=models.DurationField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="session",
|
||||||
|
name="timestamp_end",
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -31,8 +31,8 @@ class Platform(models.Model):
|
|||||||
class Session(models.Model):
|
class Session(models.Model):
|
||||||
purchase = models.ForeignKey("Purchase", on_delete=models.CASCADE)
|
purchase = models.ForeignKey("Purchase", on_delete=models.CASCADE)
|
||||||
timestamp_start = models.DateTimeField()
|
timestamp_start = models.DateTimeField()
|
||||||
timestamp_end = models.DateTimeField()
|
timestamp_end = models.DateTimeField(blank=True, null=True)
|
||||||
duration_manual = models.DurationField(blank=True, null=True, default=timedelta(0))
|
duration_manual = models.DurationField(blank=True, null=True)
|
||||||
duration_calculated = models.DurationField(blank=True, null=True)
|
duration_calculated = models.DurationField(blank=True, null=True)
|
||||||
note = models.TextField(blank=True, null=True)
|
note = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
@ -41,7 +41,10 @@ class Session(models.Model):
|
|||||||
return f"{str(self.purchase)} {str(self.timestamp_start.date())} ({self.total_duration()}{mark})"
|
return f"{str(self.purchase)} {str(self.timestamp_start.date())} ({self.total_duration()}{mark})"
|
||||||
|
|
||||||
def calculated_duration(self):
|
def calculated_duration(self):
|
||||||
return self.timestamp_end - self.timestamp_start
|
if self.timestamp_end == None or self.timestamp_start == None:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return self.timestamp_end - self.timestamp_start
|
||||||
|
|
||||||
def total_duration(self):
|
def total_duration(self):
|
||||||
return (
|
return (
|
||||||
|
@ -717,6 +717,26 @@ select {
|
|||||||
position: static;
|
position: static;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.absolute {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-0 {
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-0 {
|
||||||
|
bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-1 {
|
||||||
|
left: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-1 {
|
||||||
|
bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mx-auto {
|
.mx-auto {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
@ -742,6 +762,10 @@ select {
|
|||||||
display: grid;
|
display: grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-screen {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
.w-full {
|
.w-full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@ -825,6 +849,10 @@ select {
|
|||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.text-4xl {
|
.text-4xl {
|
||||||
font-size: 2.25rem;
|
font-size: 2.25rem;
|
||||||
line-height: 2.5rem;
|
line-height: 2.5rem;
|
||||||
@ -840,6 +868,16 @@ select {
|
|||||||
line-height: 1.75rem;
|
line-height: 1.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-sm {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-xs {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.font-semibold {
|
.font-semibold {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@ -849,12 +887,63 @@ select {
|
|||||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-slate-800 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(30 41 59 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-slate-700 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(51 65 85 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-slate-600 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(71 85 105 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-slate-500 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(100 116 139 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-slate-400 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(148 163 184 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-slate-300 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(203 213 225 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.shadow {
|
.shadow {
|
||||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||||
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
|
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
|
||||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark form label {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(148 163 184 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark form input,.dark select,.dark textarea {
|
||||||
|
border-width: 1px;
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(15 23 42 / var(--tw-border-opacity));
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(100 116 139 / var(--tw-bg-opacity));
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(241 245 249 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
form input[type=submit] {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(88 28 135 / var(--tw-bg-opacity));
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:underline:hover {
|
.hover\:underline:hover {
|
||||||
text-decoration-line: underline;
|
text-decoration-line: underline;
|
||||||
}
|
}
|
||||||
@ -864,6 +953,11 @@ select {
|
|||||||
border-color: rgb(255 255 255 / var(--tw-border-opacity));
|
border-color: rgb(255 255 255 / var(--tw-border-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .dark\:bg-gray-800 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.dark .dark\:bg-gray-900 {
|
.dark .dark\:bg-gray-900 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
|
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
|
||||||
@ -874,9 +968,14 @@ select {
|
|||||||
background-color: rgb(51 65 85 / var(--tw-bg-opacity));
|
background-color: rgb(51 65 85 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .dark\:text-slate-300 {
|
.dark .dark\:bg-slate-400 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(148 163 184 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .dark\:text-white {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(203 213 225 / var(--tw-text-opacity));
|
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .dark\:text-slate-400 {
|
.dark .dark\:text-slate-400 {
|
||||||
@ -884,6 +983,16 @@ select {
|
|||||||
color: rgb(148 163 184 / var(--tw-text-opacity));
|
color: rgb(148 163 184 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .dark\:text-slate-300 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(203 213 225 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .dark\:text-slate-600 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(71 85 105 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.md\:block {
|
.md\:block {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -6,30 +6,34 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{% block title %}No Title{% endblock title %}</title>
|
<title>Timetracker - {% block title %}Untitled{% endblock title %}</title>
|
||||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||||
<link rel="stylesheet" href="{% static 'base.css' %}" />
|
<link rel="stylesheet" href="{% static 'base.css' %}" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="dark">
|
<body class="dark">
|
||||||
<nav class="mb-4 bg-white dark:bg-gray-900 border-gray-200 rounded">
|
<div class="dark:bg-gray-800 h-screen">
|
||||||
<div class="container flex flex-wrap items-center justify-between mx-auto">
|
<nav class="mb-4 bg-white dark:bg-gray-900 border-gray-200 rounded">
|
||||||
<a href="#" class="flex items-center">
|
<div class="container flex flex-wrap items-center justify-between mx-auto">
|
||||||
<span class="text-4xl">⌚</span>
|
<a href="#" class="flex items-center">
|
||||||
<span class="self-center text-xl font-semibold whitespace-nowrap text-white">Timetracker</span>
|
<span class="text-4xl">⌚</span>
|
||||||
</a>
|
<span class="self-center text-xl font-semibold whitespace-nowrap text-white">Timetracker</span>
|
||||||
<div class="w-full md:block md:w-auto">
|
</a>
|
||||||
<ul
|
<div class="w-full md:block md:w-auto">
|
||||||
class="flex flex-col md:flex-row p-4 mt-4">
|
<ul
|
||||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_game' %}">New Game</a></li>
|
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_purchase' %}">New Purchase</a></li>
|
<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_session' %}">New Session</a></li>
|
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_purchase' %}">New Purchase</a></li>
|
||||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'list_sessions' %}">All Sessions</a></li>
|
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_session' %}">New Session</a></li>
|
||||||
</ul>
|
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'list_sessions' %}">All Sessions</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
</nav>
|
{% block content %}No content here.{% endblock %}
|
||||||
{% block content %}No content here.{% endblock %}
|
</div>
|
||||||
|
{% load version %}
|
||||||
|
<span id="version-info" class="absolute left-1 bottom-1 text-xs text-slate-300 dark:text-slate-600">{% version %} ({% version_date %})</span>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -1,17 +1,23 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}Tracker Entry List{% endblock title %}
|
{% block title %}Sessions{% endblock title %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% if purchase %}
|
||||||
|
<div class="text-center text-xl mb-4 dark:text-slate-400">
|
||||||
|
<h1>Listing sessions only for purchase "{{ purchase }}"</h1>
|
||||||
|
<a class="dark:text-white hover:underline" href="{% url 'list_sessions' %}">View all sessions</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="grid grid-cols-4 gap-4 shadow rounded-xl max-w-screen-lg mx-auto dark:bg-slate-700 p-2 justify-center">
|
<div class="grid grid-cols-4 gap-4 shadow rounded-xl max-w-screen-lg mx-auto dark:bg-slate-700 p-2 justify-center">
|
||||||
<div class="dark:border-white dark:text-slate-300 text-lg">Name</div>
|
<div class="dark:border-white dark:text-slate-300 text-lg">Name</div>
|
||||||
<div class="dark:border-white dark:text-slate-300 text-lg">Start</div>
|
<div class="dark:border-white dark:text-slate-300 text-lg">Start</div>
|
||||||
<div class="dark:border-white dark:text-slate-300 text-lg">End</div>
|
<div class="dark:border-white dark:text-slate-300 text-lg">End</div>
|
||||||
<div class="dark:border-white dark:text-slate-300 text-lg">Duration</div>
|
<div class="dark:border-white dark:text-slate-300 text-lg">Duration</div>
|
||||||
{% for data in dataset %}
|
{% for data in dataset %}
|
||||||
<div class="dark:text-slate-400">{{ data.purchase }}</div>
|
<div class=""><a class="dark:text-white hover:underline" href="{% url 'list_sessions' data.purchase.id %}">{{ data.purchase }}</a></div>
|
||||||
<div class="dark:text-slate-400">{{ data.timestamp_start }}</div>
|
<div class="dark:text-slate-400">{{ data.timestamp_start | date:"d/m/Y H:i" }}</div>
|
||||||
<div class="dark:text-slate-400">{{ data.timestamp_end }}</div>
|
<div class="dark:text-slate-400">{{ data.timestamp_end | date:"d/m/Y H:i" }}</div>
|
||||||
<div class="dark:text-slate-400">{{ data.time_delta }}</div>
|
<div class="dark:text-slate-400">{{ data.time_delta }}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
18
src/web/tracker/templatetags/version.py
Normal file
18
src/web/tracker/templatetags/version.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from django import template
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def version_date():
|
||||||
|
return time.strftime(
|
||||||
|
"%d-%b-%Y %H:%m",
|
||||||
|
time.gmtime(os.path.getmtime(os.path.abspath(os.path.join(".git")))),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def version():
|
||||||
|
return os.environ.get("VERSION_NUMBER", "UNKNOWN VERSION")
|
@ -7,4 +7,9 @@ urlpatterns = [
|
|||||||
path("add-session/", views.add_session, name="add_session"),
|
path("add-session/", views.add_session, name="add_session"),
|
||||||
path("add-purchase/", views.add_purchase, name="add_purchase"),
|
path("add-purchase/", views.add_purchase, name="add_purchase"),
|
||||||
path("list-sessions/", views.list_sessions, name="list_sessions"),
|
path("list-sessions/", views.list_sessions, name="list_sessions"),
|
||||||
|
path(
|
||||||
|
"list-sessions/by-purchase/<int:purchase_id>",
|
||||||
|
views.list_sessions,
|
||||||
|
name="list_sessions",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -4,6 +4,7 @@ from .models import Game, Platform, Purchase, Session
|
|||||||
from .forms import SessionForm, PurchaseForm, GameForm
|
from .forms import SessionForm, PurchaseForm, GameForm
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from django.db.models import ExpressionWrapper, F, DurationField
|
from django.db.models import ExpressionWrapper, F, DurationField
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
def add_session(request):
|
def add_session(request):
|
||||||
@ -18,9 +19,16 @@ def add_session(request):
|
|||||||
return render(request, "add_session.html", context)
|
return render(request, "add_session.html", context)
|
||||||
|
|
||||||
|
|
||||||
def list_sessions(request):
|
def list_sessions(request, purchase_id=None):
|
||||||
context = {}
|
context = {}
|
||||||
dataset = Session.objects.annotate(
|
|
||||||
|
if purchase_id != None:
|
||||||
|
dataset = Session.objects.filter(purchase=purchase_id)
|
||||||
|
context["purchase"] = Purchase.objects.get(id=purchase_id)
|
||||||
|
else:
|
||||||
|
dataset = Session.objects.all()
|
||||||
|
|
||||||
|
dataset = dataset.annotate(
|
||||||
time_delta=ExpressionWrapper(
|
time_delta=ExpressionWrapper(
|
||||||
F("timestamp_end") - F("timestamp_start"), output_field=DurationField()
|
F("timestamp_end") - F("timestamp_start"), output_field=DurationField()
|
||||||
)
|
)
|
||||||
|
@ -11,6 +11,8 @@ https://docs.djangoproject.com/en/4.1/ref/settings/
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
@ -106,7 +108,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||||||
|
|
||||||
LANGUAGE_CODE = "en-us"
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
TIME_ZONE = "UTC"
|
TIME_ZONE = os.environ.get("TZ", "UTC")
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
@ -122,3 +124,13 @@ STATIC_URL = "static/"
|
|||||||
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
|
# https://docs.djangoproject.com/en/4.1/topics/logging/
|
||||||
|
LOGGING = {
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": False,
|
||||||
|
"handlers": {"console": {"class": "logging.StreamHandler"}},
|
||||||
|
"root": {"handlers": ["console"], "level": "WARNING"},
|
||||||
|
}
|
||||||
|
|
||||||
|
CSRF_TRUSTED_ORIGINS = ["https://tracker.kucharczyk.xyz"]
|
||||||
|
Reference in New Issue
Block a user