from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth import authenticate, login from django.contrib.auth.decorators import login_required, user_passes_test from django.http import HttpResponse, HttpResponseForbidden from django.contrib import messages from django.db.models import Q import csv from django.utils.timezone import now from .forms import ImportForm from .models import Entry from .utils import import_csv, SEARCHABLE_FIELDS, build_query def is_admin(user): return user.is_superuser or user.is_staff def login_view(request): if request.user.is_authenticated: return redirect("search") ctx = {} if request.method == "POST": username = request.POST.get("username") password = request.POST.get("password") user = authenticate(request, username=username, password=password) if user: login(request, user) return redirect("search") ctx["error"] = "Invalid credentials" return render(request, "login.html", ctx) @login_required def redirect_to_search(request): return redirect("search") @login_required def search_view(request): total = Entry.objects.count() query = request.GET.get("q", "").strip() selected = request.GET.getlist("fields") or list(SEARCHABLE_FIELDS.keys()) entries = [] results_count = 0 current_id = None if query: q = build_query(selected, query) entries = list(Entry.objects.filter(q).order_by("-date_added","-id").values_list("id", flat=True)) results_count = len(entries) request.session["search_ids"] = entries request.session["search_index"] = 0 if entries: current_id = entries[0] return redirect("record_view", entry_id=current_id) return render(request, "search.html", { "total": total, "q": query, "selected": selected, "fields": list(SEARCHABLE_FIELDS.keys()), "results_count": results_count, }) @login_required def record_view(request, entry_id): ids = request.session.get("search_ids", []) if entry_id in ids: request.session["search_index"] = ids.index(entry_id) idx = request.session.get("search_index", 0) total = Entry.objects.count() results_count = len(ids) pos = (idx+1) if ids else 1 entry = get_object_or_404(Entry, id=entry_id) return render(request, "record.html", { "entry": entry, "locked": True, "total": total, "results_count": results_count, "position": pos, }) @login_required def nav_prev(request): ids = request.session.get("search_ids", []) idx = request.session.get("search_index", 0) if ids: idx = max(0, idx-1) request.session["search_index"] = idx return redirect("record_view", entry_id=ids[idx]) messages.info(request, "No search results loaded.") return redirect("search") @login_required def nav_next(request): ids = request.session.get("search_ids", []) idx = request.session.get("search_index", 0) if ids: idx = min(len(ids)-1, idx+1) request.session["search_index"] = idx return redirect("record_view", entry_id=ids[idx]) messages.info(request, "No search results loaded.") return redirect("search") @login_required def record_save(request, entry_id): if request.method != "POST": return redirect("record_view", entry_id=entry_id) e = get_object_or_404(Entry, id=entry_id) # Save edited fields e.subject = request.POST.get("subject","") e.illustration = request.POST.get("illustration","") e.application = request.POST.get("application","") e.scripture_raw = request.POST.get("scripture_raw","") e.source = request.POST.get("source","") e.talk_title = request.POST.get("talk_title","") tn = request.POST.get("talk_number","").strip() e.talk_number = int(tn) if tn.isdigit() else None e.entry_code = request.POST.get("entry_code","") e.date_added = request.POST.get("date_added") or None e.date_edited = request.POST.get("date_edited") or None e.save() messages.success(request, "Saved changes.") return redirect("record_view", entry_id=entry_id) @login_required def record_delete(request, entry_id): if request.method == "POST": e = get_object_or_404(Entry, id=entry_id) e.delete() messages.success(request, "Entry deleted.") # After delete, move to previous or search page ids = request.session.get("search_ids", []) idx = request.session.get("search_index", 0) if ids: ids = [i for i in ids if i != entry_id] request.session["search_ids"] = ids if not ids: return redirect("search") idx = max(0, min(idx, len(ids)-1)) request.session["search_index"] = idx return redirect("record_view", entry_id=ids[idx]) return redirect("search") return HttpResponseForbidden("Use POST to delete.") @login_required @user_passes_test(is_admin) def import_wizard(request): from .forms import ImportForm if request.method == "POST": form = ImportForm(request.POST, request.FILES) if form.is_valid(): fbytes = form.cleaned_data["file"].read() dry = form.cleaned_data["dry_run"] try: report = import_csv(fbytes, dry_run=dry) return render(request, "import_result.html", {"report": report, "dry_run": dry}) except Exception as e: messages.error(request, f"Import failed: {e}") else: form = ImportForm() return render(request, "import_wizard.html", {"form": form}) @login_required @user_passes_test(is_admin) def export_csv(request): response = HttpResponse(content_type='text/csv') ts = now().strftime("%Y-%m-%d_%H-%M-%S") response['Content-Disposition'] = f'attachment; filename="illustrations_backup_{ts}.csv"' writer = csv.writer(response) writer.writerow(["Subject","Illustration","Application","Scripture","Source","Talk Title","Talk Number","Code","Date","Date Edited"]) for e in Entry.objects.all().order_by("id"): writer.writerow([ e.subject, e.illustration, e.application, e.scripture_raw, e.source, e.talk_title, e.talk_number if e.talk_number is not None else "", e.entry_code, e.date_added.isoformat() if e.date_added else "", e.date_edited.isoformat() if e.date_edited else "", ]) return response