Initial import

This commit is contained in:
Joshua Laymon
2025-08-12 20:50:46 -05:00
commit e6cb7e91aa
29 changed files with 886 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% block title %}Illustrations DB{% endblock %}</title>
<style>
:root {
--blue:#1f6cd8;
--light:#f6f8fb;
--panel:#ffffff;
--line:#e5e9f2;
--text:#1a1a1a;
--muted:#6b7280;
}
body { margin:0; font-family: system-ui, -apple-system, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica, Arial, sans-serif; background:var(--light); color:var(--text); }
.topbar { display:flex; align-items:center; justify-content:space-between; padding:12px 16px; background:#fff; border-bottom:1px solid var(--line); position:sticky; top:0; z-index:10;}
.brand { font-weight:700; color:var(--blue); letter-spacing:.2px; }
.menu a { margin-left:14px; text-decoration:none; color:var(--blue); }
.container { max-width: 1100px; margin: 24px auto; padding: 0 16px; }
.panel { background:var(--panel); border:1px solid var(--line); border-radius:12px; box-shadow: 0 8px 20px rgba(0,0,0,0.04); padding: 18px; }
.btn { display:inline-block; padding:10px 14px; border-radius:10px; border:1px solid var(--line); background:#fff; color:#0d1b2a; text-decoration:none; cursor:pointer; }
.btn.primary { background:var(--blue); color:#fff; border-color:var(--blue); }
.btn.danger { background:#d61f1f; color:#fff; border-color:#d61f1f; }
.flash { margin: 16px 0; padding: 12px; border-radius:10px; background:#eaf2ff; color:#0b3d91; }
input[type=text], input[type=password], input[type=date], textarea { width:100%; padding:10px; border:1px solid var(--line); border-radius:10px; background:#fff; }
label { font-size: 13px; color:#333; display:block; margin-bottom:6px; }
.row { display:grid; grid-template-columns: 1fr 1fr; gap:16px; }
.row3 { display:grid; grid-template-columns: 1fr 1fr 1fr; gap:16px; }
.muted { color: var(--muted); }
@media (max-width: 900px){ .row, .row3{ grid-template-columns: 1fr; } }
.stats { display:flex; gap:12px; align-items:center; font-size:14px; color:var(--muted); }
.nav { display:flex; gap:8px; align-items:center; }
</style>
</head>
<body>
{% if request.user.is_authenticated %}
<div class="topbar">
<div class="brand">Illustrations Database</div>
<div class="menu">
<a href="/search/">Search</a>
{% if request.user.is_staff %}
<a href="/import/">Import Data</a>
<a href="/export/csv/">Download Backup</a>
{% endif %}
<a href="/logout/">Logout</a>
</div>
</div>
{% endif %}
<div class="container">
{% for message in messages %}<div class="flash">{{ message }}</div>{% endfor %}
{% block content %}{% endblock %}
</div>
</body>
</html>
+24
View File
@@ -0,0 +1,24 @@
{% extends "base.html" %}
{% block title %}Import Result - Illustrations DB{% endblock %}
{% block content %}
<div class="panel">
<h2 style="margin-top:0;">Import {{ "Preview" if dry_run else "Result" }}</h2>
<ul>
<li>Total rows: <strong>{{ report.rows }}</strong></li>
<li>Inserted: <strong>{{ report.inserted }}</strong></li>
<li>Updated: <strong>{{ report.updated }}</strong></li>
<li>Skipped: <strong>{{ report.skipped }}</strong></li>
<li>Scripture parsed: <strong>{{ report.scripture_parsed }}</strong></li>
<li>Scripture failed: <strong>{{ report.scripture_failed }}</strong></li>
</ul>
{% if report.errors and report.errors|length %}
<h3>Errors</h3>
<pre style="white-space:pre-wrap;">{{ report.errors|join("\n") }}</pre>
{% endif %}
<div style="margin-top:16px;">
<a class="btn" href="/import/">Run again</a>
<a class="btn" href="/search/">Done</a>
</div>
</div>
{% endblock %}
+25
View File
@@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block title %}Import Data - Illustrations DB{% endblock %}
{% block content %}
<div class="panel">
<h2 style="margin-top:0;">Import Data (CSV)</h2>
<p>Expected headers (any order, case-insensitive): <code>Subject, Illustration, Application, Scripture, Source, Talk Title, Talk Number, Code, Date, Date Edited</code></p>
<form method="post" enctype="multipart/form-data">{% csrf_token %}
<div class="row">
<div>
<label>CSV file</label>
{{ form.file }}
</div>
<div>
<label>{{ form.dry_run.label }}</label>
{{ form.dry_run }} <small>{{ form.dry_run.help_text }}</small>
</div>
</div>
<div style="margin-top:16px; display:flex; gap:10px; justify-content:flex-end;">
<a class="btn" href="/search/">Cancel</a>
<button class="btn primary" type="submit">Process</button>
</div>
</form>
</div>
{% endblock %}
+19
View File
@@ -0,0 +1,19 @@
{% extends "base.html" %}
{% block title %}Sign in - Illustrations DB{% endblock %}
{% block content %}
<div class="panel" style="max-width:420px; margin: 80px auto;">
<h2 style="margin-top:0; color: var(--blue);">Sign in</h2>
{% if error %}<div class="flash">{{ error }}</div>{% endif %}
<form method="post">{% csrf_token %}
<label>Username</label>
<input type="text" name="username" required />
<label style="margin-top:12px;">Password</label>
<input type="password" name="password" required />
<div style="margin-top:16px; display:flex; gap:10px; justify-content:flex-end;">
<a class="btn" href="#">Cancel</a>
<button class="btn primary" type="submit">Sign in</button>
</div>
</form>
</div>
{% endblock %}
+101
View File
@@ -0,0 +1,101 @@
{% extends "base.html" %}
{% block title %}Record - Illustrations DB{% endblock %}
{% block content %}
<div class="stats" style="margin-bottom:8px;">
<div>Total: <strong>{{ total }}</strong></div>
<div>Results: <strong>{{ results_count }}</strong></div>
<div>Viewing: <strong>{{ position }}</strong> of <strong>{{ results_count|default:1 }}</strong></div>
</div>
<div class="panel">
<div style="display:flex; justify-content:space-between; align-items:center;">
<div class="nav">
<a class="btn" href="/nav/prev/">&larr; Prev</a>
<a class="btn" href="/nav/next/">Next &rarr;</a>
</div>
<div>
<button class="btn" id="unlockBtn">Unlock to Edit</button>
<form method="post" action="/record/{{ entry.id }}/delete/" style="display:inline;" onsubmit="return confirm('Are you sure you want to permanently delete this entry?');">
{% csrf_token %}
<button class="btn danger">Delete</button>
</form>
</div>
</div>
<form id="entryForm" method="post" action="/record/{{ entry.id }}/save/" style="margin-top:14px;">
{% csrf_token %}
<div class="row">
<div>
<label>Subject</label>
<input type="text" name="subject" value="{{ entry.subject|default:'' }}" readonly />
</div>
<div>
<label>Scripture</label>
<input type="text" name="scripture_raw" value="{{ entry.scripture_raw|default:'' }}" readonly />
</div>
</div>
<div style="margin-top:12px;">
<label>Illustration</label>
<textarea name="illustration" rows="5" readonly>{{ entry.illustration|default:'' }}</textarea>
</div>
<div style="margin-top:12px;">
<label>Application</label>
<textarea name="application" rows="5" readonly>{{ entry.application|default:'' }}</textarea>
</div>
<div class="row3" style="margin-top:12px;">
<div>
<label>Source</label>
<input type="text" name="source" value="{{ entry.source|default:'' }}" readonly />
</div>
<div>
<label>Talk Number</label>
<input type="text" name="talk_number" value="{{ entry.talk_number|default:'' }}" readonly />
</div>
<div>
<label>Code</label>
<input type="text" name="entry_code" value="{{ entry.entry_code|default:'' }}" readonly />
</div>
</div>
<div class="row" style="margin-top:12px;">
<div>
<label>Talk Title</label>
<input type="text" name="talk_title" value="{{ entry.talk_title|default:'' }}" readonly />
</div>
<div class="row" style="grid-template-columns: 1fr 1fr; gap:12px;">
<div>
<label>Date</label>
<input type="text" name="date_added" value="{{ entry.date_added|default:'' }}" readonly />
</div>
<div>
<label>Date Edited</label>
<input type="text" name="date_edited" value="{{ entry.date_edited|default:'' }}" readonly />
</div>
</div>
</div>
<div style="margin-top:16px; display:flex; gap:10px; justify-content:flex-end;">
<a class="btn" href="/search/">Back to Search</a>
<button class="btn primary" id="saveBtn" type="submit" disabled>Save Changes</button>
</div>
</form>
</div>
<script>
const unlockBtn = document.getElementById('unlockBtn');
const form = document.getElementById('entryForm');
const saveBtn = document.getElementById('saveBtn');
unlockBtn.addEventListener('click', function(){
form.querySelectorAll('input, textarea').forEach(el=>{
if(el.name !== 'csrfmiddlewaretoken'){ el.removeAttribute('readonly'); }
});
saveBtn.disabled = false;
unlockBtn.disabled = true;
unlockBtn.textContent = 'Unlocked';
});
</script>
{% endblock %}
+36
View File
@@ -0,0 +1,36 @@
{% extends "base.html" %}
{% block title %}Search - Illustrations DB{% endblock %}
{% block content %}
<div class="panel">
<form method="get" action="/search/">
<div class="row">
<div>
<label>Search (supports * and ?)</label>
<input type="text" name="q" value="{{ q|default:'' }}" placeholder="e.g., Default, Organization or Matt 12:30 or *loyal*" />
</div>
<div>
<label>Fields to search</label>
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:6px; padding-top:6px;">
{% for f in fields %}
<label><input type="checkbox" name="fields" value="{{ f }}" {% if f in selected %}checked{% endif %}> {{ f }}</label>
{% endfor %}
</div>
</div>
</div>
<div style="margin-top:12px; display:flex; gap:10px; justify-content:flex-end;">
<a class="btn" href="/search/">Clear</a>
<button class="btn primary" type="submit">Search</button>
</div>
</form>
</div>
<div class="stats" style="margin-top:12px;">
<div>Total entries: <strong>{{ total }}</strong></div>
{% if results_count %}<div>Results: <strong>{{ results_count }}</strong></div>{% endif %}
</div>
{% if results_count %}
<div class="panel" style="margin-top:12px;">
<p>Opening first result…</p>
</div>
{% endif %}
{% endblock %}