# 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})