Update web/core/views.py
This commit is contained in:
parent
d70cc99e9c
commit
d4ef68ee72
@ -663,4 +663,150 @@ def api_get_recent_views(request):
|
||||
|
||||
@login_required
|
||||
def settings_home(request):
|
||||
return render(request, "settings/home.html")
|
||||
return render(request, "settings/home.html")
|
||||
|
||||
@login_required
|
||||
def stats_page(request):
|
||||
from collections import Counter, OrderedDict
|
||||
from calendar import month_abbr
|
||||
|
||||
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()
|
||||
|
||||
# ---- Sparkline (last 12 months) ----
|
||||
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"{month_abbr[mm]} {str(yy)[2:]}"
|
||||
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
|
||||
]
|
||||
|
||||
# ---- Top subjects (existing) ----
|
||||
subject_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()]:
|
||||
subject_counts[tag.lower()] += 1
|
||||
top_subjects = [{"name": n.title(), "count": c} for n, c in subject_counts.most_common(10)]
|
||||
|
||||
# ===============================
|
||||
# Scripture analytics (from scripture_raw)
|
||||
# ===============================
|
||||
# A light normalizer so common abbreviations map to canonical book names.
|
||||
BOOK_MAP = {
|
||||
# OT (examples; extend as needed)
|
||||
"gen":"Genesis","ge":"Genesis","gn":"Genesis",
|
||||
"ex":"Exodus","exo":"Exodus",
|
||||
"lev":"Leviticus","le":"Leviticus",
|
||||
"num":"Numbers","nu":"Numbers",
|
||||
"de":"Deuteronomy","deut":"Deuteronomy",
|
||||
"jos":"Joshua","josh":"Joshua",
|
||||
"jdg":"Judges","judg":"Judges",
|
||||
"ru":"Ruth","rut":"Ruth",
|
||||
"ps":"Psalms","psalm":"Psalms","psalms":"Psalms",
|
||||
"pr":"Proverbs","pro":"Proverbs",
|
||||
"ec":"Ecclesiastes","ecc":"Ecclesiastes",
|
||||
"isa":"Isaiah","is":"Isaiah",
|
||||
"jer":"Jeremiah","je":"Jeremiah",
|
||||
"eze":"Ezekiel","ez":"Ezekiel",
|
||||
"da":"Daniel","dan":"Daniel",
|
||||
"ho":"Hosea","hos":"Hosea",
|
||||
# NT (examples; extend as needed)
|
||||
"mt":"Matthew","matt":"Matthew",
|
||||
"mr":"Mark","mk":"Mark",
|
||||
"lu":"Luke","lk":"Luke",
|
||||
"joh":"John","john":"John","jn":"John",
|
||||
"ac":"Acts","acts":"Acts",
|
||||
"rom":"Romans","ro":"Romans",
|
||||
"1cor":"1 Corinthians","1 co":"1 Corinthians","1 cor":"1 Corinthians",
|
||||
"2cor":"2 Corinthians","2 co":"2 Corinthians","2 cor":"2 Corinthians",
|
||||
}
|
||||
|
||||
BOOK_RE = re.compile(r"""
|
||||
^\s*
|
||||
(?P<book>(?:[1-3]\s*)?[A-Za-z\.]+) # optional 1/2/3 prefix + word
|
||||
[\s\.]+
|
||||
(?P<ref>\d+[:\.]\d+.*)? # 3:16 or 3.16 etc (optional tail)
|
||||
""", re.X)
|
||||
|
||||
def normalize_book(raw):
|
||||
b = raw.strip().lower().replace('.', '')
|
||||
b = re.sub(r'\s+', '', b) # "1 john" -> "1john"
|
||||
return BOOK_MAP.get(b, raw.strip().title())
|
||||
|
||||
def split_refs(text):
|
||||
if not text:
|
||||
return []
|
||||
# Entries are typically separated by semicolons; allow commas too.
|
||||
parts = re.split(r'[;]+', text)
|
||||
return [p.strip() for p in parts if p.strip()]
|
||||
|
||||
def parse_piece(piece):
|
||||
m = BOOK_RE.match(piece)
|
||||
if not m:
|
||||
return None, None
|
||||
book = normalize_book(m.group('book'))
|
||||
ref = (m.group('ref') or '').strip()
|
||||
return book, (f"{book} {ref}" if ref else book)
|
||||
|
||||
book_counts = Counter()
|
||||
ref_counts = Counter()
|
||||
refs_per_entry = []
|
||||
|
||||
entries_with_script = (Entry.objects
|
||||
.exclude(scripture_raw__isnull=True)
|
||||
.exclude(scripture_raw__exact=""))
|
||||
for e in entries_with_script.iterator():
|
||||
pieces = split_refs(e.scripture_raw)
|
||||
entry_ref_count = 0
|
||||
for piece in pieces:
|
||||
book, full = parse_piece(piece)
|
||||
if not book:
|
||||
continue
|
||||
book_counts[book] += 1
|
||||
if full and full != book:
|
||||
ref_counts[full] += 1
|
||||
entry_ref_count += 1
|
||||
if entry_ref_count:
|
||||
refs_per_entry.append(entry_ref_count)
|
||||
|
||||
avg_refs_per_entry = round(sum(refs_per_entry) / len(refs_per_entry), 2) if refs_per_entry else 0
|
||||
top_books = list(book_counts.most_common(10))
|
||||
top_refs = list(ref_counts.most_common(10))
|
||||
|
||||
return render(
|
||||
request,
|
||||
"stats.html",
|
||||
{
|
||||
"total": total,
|
||||
"last30": last30,
|
||||
"last365": last365,
|
||||
"series": series,
|
||||
"heights": heights,
|
||||
"top_subjects": top_subjects,
|
||||
# NEW:
|
||||
"avg_refs_per_entry": avg_refs_per_entry,
|
||||
"top_books": top_books, # iterable of (book, count)
|
||||
"top_refs": top_refs, # iterable of (ref, count)
|
||||
},
|
||||
)
|
||||
Loading…
Reference in New Issue
Block a user