Add web/core/views_user_features.py

This commit is contained in:
Joshua Laymon 2025-08-16 20:33:52 +00:00
parent 5235ee7dbe
commit 1732458546

View File

@ -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 arent 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 well 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})