Initial commit

This commit is contained in:
Felix Baumann
2026-06-27 17:07:34 +00:00
commit fb47c53d52
256 changed files with 21270 additions and 0 deletions
+263
View File
@@ -0,0 +1,263 @@
from django.contrib.auth.decorators import login_required
from accounts.decorators import permission_required
from django.shortcuts import render, get_object_or_404, redirect
from django.utils import timezone
from django.utils.dateparse import parse_datetime
from datetime import datetime
from projects.models import ProjectMembership
from .models import TimeEntry
from django_tables2 import RequestConfig
from .tables import TimeEntryTable
@login_required
@permission_required("projects_time_tracking_view")
def time_tracking_overview(request, project_id):
membership = get_object_or_404(
ProjectMembership,
project_id=project_id,
user=request.user
)
project = membership.project
# ==========================
# POST ACTIONS
# ==========================
if request.method == "POST":
action = request.POST.get("action")
# --------------------------
# START TIMER
# --------------------------
if action == "start":
open_entry = TimeEntry.objects.filter(
project=project,
user=request.user,
end__isnull=True
).first()
if not open_entry:
TimeEntry.objects.create(
project=project,
user=request.user,
entry_type=TimeEntry.EntryType.WORK,
activity="",
start=timezone.now(),
break_minutes=0,
)
return redirect(
"time_tracking:overview",
project.id
)
# --------------------------
# STOP TIMER
# --------------------------
if action == "stop":
open_entry = TimeEntry.objects.filter(
project=project,
user=request.user,
end__isnull=True
).order_by("-start").first()
if open_entry:
open_entry.end = timezone.now()
open_entry.save(
update_fields=[
"end"
]
)
return redirect(
"time_tracking:overview",
project.id
)
# --------------------------
# ADD ENTRY
# --------------------------
if action == "add":
start_str = request.POST.get("start", "")
end_str = request.POST.get("end", "")
start_dt = None
end_dt = None
try:
start_dt = datetime.strptime(start_str, "%Y-%m-%dT%H:%M")
start_dt = timezone.make_aware(start_dt)
except (ValueError, TypeError):
pass
if end_str:
try:
end_dt = datetime.strptime(end_str, "%Y-%m-%dT%H:%M")
end_dt = timezone.make_aware(end_dt)
except (ValueError, TypeError):
pass
TimeEntry.objects.create(
project=project,
user=request.user,
entry_type=request.POST.get("entry_type"),
activity=request.POST.get("activity"),
start=start_dt or timezone.now(),
end=end_dt,
break_minutes=request.POST.get("break_minutes") or 0,
note=request.POST.get("note", "")
)
return redirect(
"time_tracking:overview",
project.id
)
# ==========================
# TABLE
# ==========================
entries = project.time_entries.select_related(
"user"
).order_by(
"-start"
)
table = TimeEntryTable(
entries,
request=request,
)
RequestConfig(
request,
paginate={
"per_page": 25
}
).configure(table)
# ==========================
# RESPONSE
# ==========================
return render(
request,
"time_tracking/overview.html",
{
"project": project,
"table": table,
"types": TimeEntry.EntryType.choices,
}
)
@login_required
@permission_required("projects_time_tracking_edit")
def time_tracking_edit(request, project_id, entry_id):
membership = get_object_or_404(
ProjectMembership,
project_id=project_id,
user=request.user
)
project = membership.project
if request.method != "POST":
return redirect(
"time_tracking:overview",
project.id
)
entry = get_object_or_404(
TimeEntry,
id=entry_id,
project=project
)
# Parse datetime fields
start_str = request.POST.get("start", "")
end_str = request.POST.get("end", "")
try:
# Format: "Y-m-dTH:i" (e.g., "2026-06-27T13:36")
entry.start = datetime.strptime(start_str, "%Y-%m-%dT%H:%M")
entry.start = timezone.make_aware(entry.start)
except (ValueError, TypeError):
pass
if end_str:
try:
entry.end = datetime.strptime(end_str, "%Y-%m-%dT%H:%M")
entry.end = timezone.make_aware(entry.end)
except (ValueError, TypeError):
entry.end = None
else:
entry.end = None
entry.entry_type = request.POST.get("entry_type")
entry.activity = request.POST.get("activity")
entry.break_minutes = request.POST.get("break_minutes") or 0
entry.note = request.POST.get("note", "")
entry.save()
return redirect(
"time_tracking:overview",
project.id
)
@login_required
@permission_required("projects_time_tracking_delete")
def time_tracking_delete(request, project_id, entry_id):
membership = get_object_or_404(
ProjectMembership,
project_id=project_id,
user=request.user
)
project = membership.project
if request.method == "POST":
TimeEntry.objects.filter(
id=entry_id,
project=project
).delete()
return redirect(
"time_tracking:overview",
project.id
)