You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
115 lines
3.6 KiB
115 lines
3.6 KiB
import json
|
|
|
|
from django.contrib.admin.views.decorators import staff_member_required
|
|
from django.db.models import Count
|
|
from django.http import JsonResponse
|
|
from django.shortcuts import render, HttpResponse, redirect
|
|
from django.utils import timezone
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
from django.views.decorators.http import require_POST
|
|
|
|
from datetime import timedelta
|
|
|
|
from .models import UsageEvent
|
|
from .utils import parse_user_agent
|
|
|
|
# Create your views here.
|
|
def index(request):
|
|
return render(request, "app/base.html")
|
|
|
|
def install(request):
|
|
return render(request, "app/install.html")
|
|
|
|
def home(request):
|
|
return redirect("/app/v262")
|
|
|
|
def login(request):
|
|
return render(request, "app/login.html")
|
|
|
|
def v1321(request):
|
|
return redirect("/app/v262")
|
|
|
|
def v1322(request):
|
|
return redirect("/app/v262")
|
|
|
|
def v133(request):
|
|
return redirect("/app/v262")
|
|
|
|
def v1332(request):
|
|
return redirect("/app/v262")
|
|
|
|
def v1333(request):
|
|
return redirect("/app/v262")
|
|
|
|
def v1334(request):
|
|
return redirect("/app/v262")
|
|
|
|
def v1335(request):
|
|
return redirect("/app/v262")
|
|
|
|
def v1340(request):
|
|
return redirect("/app/v262")
|
|
|
|
def v260(request):
|
|
return redirect("/app/v262")
|
|
|
|
def v261(request):
|
|
return redirect("/app/v262")
|
|
|
|
def v262(request):
|
|
return render(request, "app/home.html")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Anonyme Nutzungsstatistik fürs Admin-Dashboard
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@csrf_exempt
|
|
@require_POST
|
|
def track_usage(request):
|
|
"""Nimmt ein anonymes Nutzungssignal entgegen (ein Aufruf pro Gerät und Tag,
|
|
siehe home01.js: pingUsageIfNeeded()). Kein CSRF-Schutz nötig, da rein additiv
|
|
und ohne jeden Personenbezug - device_id kommt aus dem localStorage des Geräts."""
|
|
try:
|
|
data = json.loads(request.body or "{}")
|
|
except json.JSONDecodeError:
|
|
data = {}
|
|
|
|
device_id = str(data.get("device_id", "")).strip()[:64]
|
|
if not device_id:
|
|
return JsonResponse({"ok": False, "error": "device_id fehlt"}, status=400)
|
|
|
|
user_agent = request.META.get("HTTP_USER_AGENT", "")
|
|
platform, browser = parse_user_agent(user_agent)
|
|
|
|
UsageEvent.objects.create(
|
|
device_id=device_id,
|
|
app_version=str(data.get("app_version", ""))[:20],
|
|
user_agent=user_agent[:1000],
|
|
platform=platform,
|
|
browser=browser,
|
|
is_standalone=bool(data.get("standalone", False)),
|
|
)
|
|
return JsonResponse({"ok": True})
|
|
|
|
|
|
@staff_member_required
|
|
def usage_dashboard(request):
|
|
"""Nutzungs-Dashboard für Admins - nur für angemeldete Staff-Nutzer (Django-Admin-Login)."""
|
|
now = timezone.now()
|
|
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
events = UsageEvent.objects.all()
|
|
|
|
context = {
|
|
"unique_devices_total": events.values("device_id").distinct().count(),
|
|
"devices_today": events.filter(created_at__gte=today_start).values("device_id").distinct().count(),
|
|
"devices_7d": events.filter(created_at__gte=now - timedelta(days=7)).values("device_id").distinct().count(),
|
|
"devices_30d": events.filter(created_at__gte=now - timedelta(days=30)).values("device_id").distinct().count(),
|
|
"standalone_count": events.filter(is_standalone=True).values("device_id").distinct().count(),
|
|
"by_platform": events.values("platform").annotate(count=Count("device_id", distinct=True)).order_by("-count"),
|
|
"by_browser": events.values("browser").annotate(count=Count("device_id", distinct=True)).order_by("-count"),
|
|
"recent_events": events[:30],
|
|
}
|
|
return render(request, "app/dashboard.html", context)
|