From 0e4091d132bdd78c52ef16def1815e9f41315663 Mon Sep 17 00:00:00 2001 From: Joshua Laymon Date: Sat, 16 Aug 2025 22:30:05 +0000 Subject: [PATCH] Update web/core/views.py --- web/core/views.py | 166 +++++++++------------------------------------- 1 file changed, 31 insertions(+), 135 deletions(-) diff --git a/web/core/views.py b/web/core/views.py index e2296e2..97bd0c5 100644 --- a/web/core/views.py +++ b/web/core/views.py @@ -1,21 +1,22 @@ -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 -from django.contrib import messages -from django.db.models import Q -from django.views.decorators.http import require_http_methods from datetime import date, timedelta import csv import re -from .models import Entry +from django.contrib import messages +from django.contrib.auth import authenticate, login +from django.contrib.auth.decorators import login_required, user_passes_test +from django.db.models import Q +from django.http import HttpResponse, JsonResponse +from django.shortcuts import render, redirect, get_object_or_404 +from django.views.decorators.http import require_http_methods + from .forms import ImportForm, EntryForm +from .models import Entry +from .scripture_normalizer import normalize_scripture_field # NEW +from .source_normalizer import normalize_source_field # NEW +from .subject_normalizer import normalize_subject_field # NEW from .utils import terms, has_wildcards, wildcard_to_regex, import_csv_bytes -from .scripture_normalizer import normalize_scripture_field # <-- NEW -from .source_normalizer import normalize_source_field # NEW -from .subject_normalizer import normalize_subject_field # NEW -from django.http import JsonResponse + # Order + labels used in the Search UI FIELD_ORDER = [ @@ -390,7 +391,7 @@ def stats_page(request): ) -# ========= NEW: Scripture Normalizer endpoint ========= +# ========= Scripture Normalizer ========= @login_required @user_passes_test(is_admin) @@ -413,7 +414,6 @@ def normalize_scripture(request): preview = [] if apply: - # write in batches to keep transactions short from django.db import transaction batch, pending = 500, [] for e in qs.iterator(): @@ -444,11 +444,11 @@ def normalize_scripture(request): changed += 1 preview.append((e.id, original, normalized)) - preview = preview[:100] # keep the table reasonable + preview = preview[:100] messages.info( request, - f"{'Applied' if apply else 'Dry‑run'}: {changed} entries " + f"{'Applied' if apply else 'Dry-run'}: {changed} entries " f"{'changed' if apply else 'would change'}; {warnings_total} warnings." ) return render( @@ -463,8 +463,8 @@ def normalize_scripture(request): }, ) - from django.views.decorators.http import require_http_methods -from django.contrib.auth.decorators import login_required, user_passes_test + +# ========= Source Normalizer ========= @login_required @user_passes_test(is_admin) @@ -521,7 +521,7 @@ def normalize_source(request): messages.info( request, - f"{'Applied' if apply else 'Dry‑run'}: {changed} entries " + f"{'Applied' if apply else 'Dry-run'}: {changed} entries " f"{'changed' if apply else 'would change'}; {warnings_total} warnings." ) return render( @@ -535,112 +535,10 @@ def normalize_source(request): "limit": limit, }, ) - -@login_required -def stats_page(request): - from collections import Counter - total = Entry.objects.count() - today = date.today() - last30 = Entry.objects.filter(date_added__gte=today - timedelta(days=30)).count() - last365 = Entry.objects.filter(date_added__gte=today - timedelta(days=365)).count() - # ---- Adds per month (existing logic) ---- - months = [] - y = today.year - m = today.month - for i in range(12): - mm = m - i - yy = y - while mm <= 0: - mm += 12 - yy -= 1 - from datetime import date as _d - start = _d(yy, mm, 1) - end = _d(yy + 1, 1, 1) if mm == 12 else _d(yy, mm + 1, 1) - label = f"{yy}-{mm:02d}" - months.append((label, start, end)) - months = list(reversed(months)) - series = [ - (label, Entry.objects.filter(date_added__gte=start, date_added__lt=end).count()) - for label, start, end in months - ] - peak = max((v for _, v in series), default=1) - heights = [(label, value, 8 + int((value / peak) * 100) if peak else 8) - for label, value in series] +# ========= Subject Normalizer ========= - # ---- Top subjects (existing logic) ---- - counts = Counter() - for subj in Entry.objects.exclude(subject="").values_list("subject", flat=True): - for tag in [t.strip() for t in subj.split(",") if t.strip()]: - counts[tag.lower()] += 1 - top_subjects = [{"name": n.title(), "count": c} for n, c in counts.most_common(10)] - - # ---- Scripture analytics (NEW) ---- - # Expect canonical like: "Matt. 5:14; Ps. 1:1,2; 1 Cor. 13:4-7" - # Split on semicolons; capture book and chap/verses if present. - BOOK_RE = re.compile( - r"^\s*(?P(?:[1-3]\s+)?[A-Za-z\.]+(?:\s+[A-Za-z\.]+){0,2})" - r"(?:\s+(?P\d+(?::[\d,\-\u2013\u2014]+)?))?\s*$" - ) - - books_counter = Counter() - refs_counter = Counter() - ref_per_entry_counts = [] - entries_with_scripture = 0 - - scriptures = Entry.objects.exclude(scripture_raw="") \ - .values_list("scripture_raw", flat=True) - - for raw in scriptures: - pieces = [p for p in re.split(r"\s*;\s*", raw or "") if p.strip()] - if not pieces: - continue - entries_with_scripture += 1 - refs_this_entry = 0 - - for p in pieces: - m = BOOK_RE.match(p) - if not m: - continue - book = (m.group("book") or "").strip() - cv = (m.group("cv") or "").strip() - if not book: - continue - books_counter[book] += 1 - if cv: - refs_counter[f"{book} {cv}"] += 1 - refs_this_entry += 1 - - ref_per_entry_counts.append(refs_this_entry) - - top_books = books_counter.most_common(10) - top_refs = refs_counter.most_common(10) - avg_refs_per_entry = round( - (sum(ref_per_entry_counts) / len(ref_per_entry_counts)) - if ref_per_entry_counts else 0.0, 2 - ) - book_distribution = books_counter.most_common(30) # handy for future charts - - return render( - request, - "stats.html", - { - "total": total, - "last30": last30, - "last365": last365, - "series": series, - "heights": heights, - "top_subjects": top_subjects, - - # NEW context for the template - "entries_with_scripture": entries_with_scripture, - "avg_refs_per_entry": avg_refs_per_entry, - "top_books": top_books, - "top_refs": top_refs, - "book_distribution": book_distribution, - }, - ) @login_required @user_passes_test(is_admin) @require_http_methods(["GET", "POST"]) @@ -662,7 +560,6 @@ def normalize_subjects(request): preview = [] if apply: - # write in batches to keep transactions short from django.db import transaction batch, pending = 500, [] for e in qs.iterator(): @@ -680,7 +577,6 @@ def normalize_subjects(request): obj.save(update_fields=["subject"]) pending.clear() if pending: - from django.db import transaction with transaction.atomic(): for obj in pending: obj.save(update_fields=["subject"]) @@ -694,7 +590,7 @@ def normalize_subjects(request): changed += 1 preview.append((e.id, original, normalized)) - preview = preview[:100] # keep table reasonable + preview = preview[:100] messages.info( request, @@ -712,11 +608,9 @@ def normalize_subjects(request): "limit": limit, }, ) - from django.http import JsonResponse -# If not already imported above: -# from django.contrib.auth.decorators import login_required -# from django.utils import timezone # only if you need now() -# import re # already in your file + + +# ========= API: Recently Viewed (for 20-word snippet + correct link) ========= @login_required def api_get_recent_views(request): @@ -725,12 +619,14 @@ def api_get_recent_views(request): including the illustration text so the UI can show a 20-word snippet. """ # Model: RecentView with fields: user (FK), entry (FK Entry), viewed_at (DateTime) - from .models import RecentView # inline to avoid breaking if you split files + from .models import RecentView # local import to avoid issues in old migrations - recents = (RecentView.objects - .filter(user=request.user) - .select_related('entry') - .order_by('-viewed_at')[:50]) + recents = ( + RecentView.objects + .filter(user=request.user) + .select_related("entry") + .order_by("-viewed_at")[:50] + ) items = [] for rv in recents: