This commit is contained in:
Joshua Laymon 2025-08-12 22:29:18 -05:00
parent 3458501272
commit 6426211800
7 changed files with 23 additions and 16 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1 +1 @@
Place illustrations_seed.csv here.
Place your seed file as illustrations_seed.csv here if you want auto-import on first boot.

View File

@ -1,6 +1,6 @@
import csv, io, re
from dateutil import parser as dateparser
from datetime import date, timedelta
from datetime import date
from .models import Entry, ScriptureRef
SCR_REF_RE = re.compile(r"""^\s*([1-3]?\s*[A-Za-z\.]+)\s+(\d+)(?::(\d+))?(?:\s*[-–—]\s*(\d+)(?::(\d+))?)?\s*$""", re.VERBOSE)
@ -11,7 +11,7 @@ def normalize_book(s):
return BOOK_ALIASES.get(b, s.strip())
def parse_scripture(s):
items=[];
items=[]
for p in [x.strip() for x in (s or '').split(';') if x.strip()]:
m = SCR_REF_RE.match(p)
if not m: items.append(None); continue
@ -69,7 +69,6 @@ def import_csv_bytes(b: bytes, dry_run=True):
obj.save(); obj.scripture_refs.all().delete(); report["updated"]+=1
else:
obj=Entry.objects.create(**data); report["inserted"]+=1
from .models import ScriptureRef
for it in parsed:
if it: ScriptureRef.objects.create(entry=obj, **it)
except Exception as e:

View File

@ -43,7 +43,6 @@ def search_page(request):
field_options=[{"name":k,"label":label,"checked":bool(selected.get(k))} for k,label in FIELD_ORDER]
q=request.GET.get("q","").strip()
if q:
# AND across terms, OR across selected fields per term
term_list=terms(q)
qs=Entry.objects.all()
fields=[f for f,sel in selected.items() if sel]
@ -56,7 +55,9 @@ def search_page(request):
request.session["result_ids"]=ids
if ids:
entry=Entry.objects.get(pk=ids[0])
return render(request,"entry_view.html",{"entry":entry,"locked":True,"position":1,"count":len(ids),"from_search":True})
subject_list=[t.strip() for t in (entry.subject or "").split(",") if t.strip()]
scripture_list=[t.strip() for t in (entry.scripture_raw or "").split(";") if t.strip()]
return render(request,"entry_view.html",{"entry":entry,"subject_list":subject_list,"scripture_list":scripture_list,"locked":True,"position":1,"count":len(ids),"from_search":True})
total=Entry.objects.count()
return render(request,"search.html",{"q":q,"selected":selected,"field_options":field_options,"total":total})
return render(request,"search.html",{"selected":default_fields,"field_options":[{"name":k,"label":lbl,"checked":default_fields.get(k,False)} for k,lbl in FIELD_ORDER]})
@ -67,7 +68,9 @@ def nav_next(request):
if not ids: return redirect("search")
idx=int(request.GET.get("i","0")); idx=min(idx+1, len(ids)-1)
entry=get_object_or_404(Entry, pk=ids[idx])
return render(request,"entry_view.html",{"entry":entry,"locked":True,"position":idx+1,"count":len(ids)})
subject_list=[t.strip() for t in (entry.subject or "").split(",") if t.strip()]
scripture_list=[t.strip() for t in (entry.scripture_raw or "").split(";") if t.strip()]
return render(request,"entry_view.html",{"entry":entry,"subject_list":subject_list,"scripture_list":scripture_list,"locked":True,"position":idx+1,"count":len(ids)})
@login_required
def nav_prev(request):
@ -75,14 +78,18 @@ def nav_prev(request):
if not ids: return redirect("search")
idx=int(request.GET.get("i","0")); idx=max(idx-1, 0)
entry=get_object_or_404(Entry, pk=ids[idx])
return render(request,"entry_view.html",{"entry":entry,"locked":True,"position":idx+1,"count":len(ids)})
subject_list=[t.strip() for t in (entry.subject or "").split(",") if t.strip()]
scripture_list=[t.strip() for t in (entry.scripture_raw or "").split(";") if t.strip()]
return render(request,"entry_view.html",{"entry":entry,"subject_list":subject_list,"scripture_list":scripture_list,"locked":True,"position":idx+1,"count":len(ids)})
@login_required
def entry_view(request, entry_id):
entry=get_object_or_404(Entry, pk=entry_id)
ids=request.session.get("result_ids",[]); count=len(ids)
position=ids.index(entry.id)+1 if entry.id in ids else 1
return render(request,"entry_view.html",{"entry":entry,"locked":True,"position":position,"count":count})
subject_list=[t.strip() for t in (entry.subject or "").split(",") if t.strip()]
scripture_list=[t.strip() for t in (entry.scripture_raw or "").split(";") if t.strip()]
return render(request,"entry_view.html",{"entry":entry,"subject_list":subject_list,"scripture_list":scripture_list,"locked":True,"position":position,"count":count})
@login_required
def entry_edit(request, entry_id):
@ -141,10 +148,12 @@ def stats_page(request):
last365=Entry.objects.filter(date_added__gte=today - timedelta(days=365)).count()
buckets=month_buckets_last_12(today)
series=[(label, Entry.objects.filter(date_added__gte=start, date_added__lt=end).count()) for label,start,end in buckets]
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]
from collections import Counter
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)]
return render(request,"stats.html",{"total":total,"last30":last30,"last365":last365,"series":series,"top_subjects":top_subjects})
return render(request,"stats.html",{"total":total,"last30":last30,"last365":last365,"series":series,"heights":heights,"top_subjects":top_subjects})

View File

@ -4,7 +4,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.getenv("DJANGO_SECRET_KEY","dev-insecure")
DEBUG = os.getenv("DJANGO_DEBUG","False") == "True"
ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS","*").split(",")
ALLOWED_HOSTS = [h.strip() for h in os.getenv("DJANGO_ALLOWED_HOSTS","*").split(",") if h.strip()]
CSRF_TRUSTED_ORIGINS = [x.strip() for x in os.getenv("CSRF_TRUSTED_ORIGINS","").split(",") if x.strip()]
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

View File

@ -18,11 +18,11 @@
<div class="row">
<div>
<label>Subject</label>
<div class="chips">{% for t in entry.subject.split(',') %}{% if t.strip %}<span class="chip">{{ t.strip }}</span>{% endif %}{% endfor %}</div>
<div class="chips">{% for t in subject_list %}<span class="chip">{{ t }}</span>{% endfor %}</div>
</div>
<div>
<label>Scripture</label>
<div class="chips">{% for t in entry.scripture_raw.split(';') %}{% if t.strip %}<span class="chip" style="background:#eef4ff;">{{ t.strip }}</span>{% endif %}{% endfor %}</div>
<div class="chips">{% for t in scripture_list %}<span class="chip" style="background:#eef4ff;">{{ t }}</span>{% endfor %}</div>
</div>
</div>
<div class="spacer"></div>

View File

@ -13,9 +13,8 @@
<div class="card">
<div class="small">Entries per month (by Date Added)</div>
<div style="display:flex; gap:6px; align-items:flex-end; height:120px; margin-top:8px;">
{% with peak=series|map:'1' %}{% endwith %}
{% for label, value in series %}
<div title="{{ label }}: {{ value }}" style="width:24px; background:#dbe7ff; border:1px solid #c8d6ff; height: {{ value|add:5 }}px;"></div>
{% for label, value, height in heights %}
<div title="{{ label }}: {{ value }}" style="width:24px; background:#dbe7ff; border:1px solid #c8d6ff; height: {{ height }}px;"></div>
{% endfor %}
</div>
<div class="small" style="display:flex; gap:8px; flex-wrap:wrap; margin-top:6px;">