From 1732458546626b843e352f64444ad58c0944306d Mon Sep 17 00:00:00 2001 From: Joshua Laymon Date: Sat, 16 Aug 2025 20:33:52 +0000 Subject: [PATCH] Add web/core/views_user_features.py --- web/core/views_user_features.py | 112 ++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 web/core/views_user_features.py diff --git a/web/core/views_user_features.py b/web/core/views_user_features.py new file mode 100644 index 0000000..825fecf --- /dev/null +++ b/web/core/views_user_features.py @@ -0,0 +1,112 @@ +# core/views_user_features.py +from django.contrib.auth.decorators import login_required +from django.http import JsonResponse, HttpResponseBadRequest +from django.views.decorators.http import require_POST, require_GET +from django.views.decorators.csrf import csrf_exempt +from django.utils import timezone +from django.forms.models import model_to_dict + +from .models import Entry # existing +from .models_user import UserPrefs, SearchHistory, ViewedIllustration + + +# ---------- Font size prefs ---------- +@login_required +@require_POST +def api_set_font_size(request): + size = (request.POST.get("size") or "").lower() + if size not in ("small", "default", "large", "xlarge"): + return JsonResponse({"ok": False, "error": "bad_size"}, status=400) + prefs, _ = UserPrefs.objects.get_or_create(user=request.user) + prefs.font_size = size + prefs.save(update_fields=["font_size"]) + return JsonResponse({"ok": True}) + + +@login_required +@require_GET +def api_get_prefs(request): + prefs, _ = UserPrefs.objects.get_or_create(user=request.user) + return JsonResponse({"ok": True, "font_size": prefs.font_size}) + + +# ---------- Search history ---------- +# We use Beacon here, so CSRF headers aren’t available -> mark this one csrf_exempt +@login_required +@csrf_exempt +@require_POST +def api_log_search(request): + q = (request.POST.get("q") or "").strip() + # Expect JSON-like dict from the client for fields, but we’ll accept a flat form too + selected = {} + for k in request.POST: + if k.startswith("sel[") and k.endswith("]"): # sel[subject]=on + name = k[4:-1] + selected[name] = request.POST.get(k) in ("1", "true", "on", "yes") + + # consecutive de-duplication + last = SearchHistory.objects.filter(user=request.user).order_by("-created_at").first() + if not last or last.q != q or last.selected != selected: + SearchHistory.objects.create(user=request.user, q=q, selected=selected) + + # trim to last 10 + ids = list( + SearchHistory.objects.filter(user=request.user) + .order_by("-created_at") + .values_list("id", flat=True)[:10] + ) + SearchHistory.objects.filter(user=request.user).exclude(id__in=ids).delete() + return JsonResponse({"ok": True}) + + +@login_required +@require_GET +def api_get_search_history(request): + items = ( + SearchHistory.objects.filter(user=request.user) + .order_by("-created_at") + .values("q", "selected", "created_at") + ) + # JSON-safe + data = [{"q": i["q"], "selected": i["selected"], "ts": i["created_at"].isoformat()} for i in items] + return JsonResponse({"ok": True, "items": data}) + + +# ---------- Recently viewed ---------- +@login_required +@require_POST +def api_log_view(request, entry_id): + try: + e = Entry.objects.get(pk=entry_id) + except Entry.DoesNotExist: + return JsonResponse({"ok": False, "error": "notfound"}, status=404) + + ViewedIllustration.objects.create(user=request.user, entry=e) + + # trim to 50 + ids = list( + ViewedIllustration.objects.filter(user=request.user) + .order_by("-viewed_at") + .values_list("id", flat=True)[:50] + ) + ViewedIllustration.objects.filter(user=request.user).exclude(id__in=ids).delete() + return JsonResponse({"ok": True}) + + +@login_required +@require_GET +def api_get_recent_views(request): + rows = ( + ViewedIllustration.objects.filter(user=request.user) + .select_related("entry") + .order_by("-viewed_at")[:10] # show 10 (we store 50) + ) + data = [ + { + "entry_id": r.entry_id, + "viewed_at": r.viewed_at.isoformat(), + "subject": (r.entry.subject or ""), + } + for r in rows + ] + return JsonResponse({"ok": True, "items": data}) \ No newline at end of file