{% extends "base.html" %} {% block title %}History: {{ verbose_name }} #{{ pk }}{% endblock %} {% block breadcrumb %} Home › {{ verbose_name }}s › #{{ pk }} › History {% endblock %} {% block extra_head %} {% endblock %} {% block content %}
| Field | Previous Value | New Value | ||||
|---|---|---|---|---|---|---|
| {{ field_name }} | {% if change is mapping %}{{ change.old|default('(empty)') if change.old is not none else '(empty)' }} | → | {{ change.new|default('(empty)') if change.new is not none else '(empty)' }} | {% else %}{{ change }} | {% endif %}||
A new record was created. No field-level diff is available for creation events.
This record was permanently deleted from the database.
No changes have been recorded for this record yet. Actions like creating, editing, or deleting will appear here automatically.
Back to {{ verbose_name }}Download the full audit trail for {{ verbose_name }} #{{ pk }}.
| Field | Entry A | Entry B |
|---|---|---|
| ' + f + ' | '; html += '' + (diff ? '' : '') + aVal + (diff ? '' : '') + ' | '; html += '' + (diff ? '' : '') + bVal + (diff ? '' : '') + ' | '; html += '
No field changes to compare between these entries.
'; } if (compareContent) compareContent.innerHTML = html; if (compareModal) compareModal.classList.add('show'); }); } window.closeCompareModal = function() { if (compareModal) compareModal.classList.remove('show'); }; /* ── Export ─────────────────────────────────────────── */ var exportModal = document.getElementById('exportModal'); var exportBtn = document.getElementById('exportBtn'); if (exportBtn) { exportBtn.addEventListener('click', function() { if (exportModal) exportModal.classList.add('show'); }); } window.closeExportModal = function() { if (exportModal) exportModal.classList.remove('show'); }; if (exportModal) { exportModal.addEventListener('click', function(e) { if (e.target === exportModal) closeExportModal(); }); } if (compareModal) { compareModal.addEventListener('click', function(e) { if (e.target === compareModal) closeCompareModal(); }); } window.exportHistory = function(fmt) { var data = entries; var blob, filename; if (fmt === 'json') { blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); filename = modelName + '_' + pk + '_history.json'; } else { /* CSV export with all fields */ var cols = ['timestamp', 'username', 'role', 'action', 'ip_address', 'success', 'fields_changed', 'changes']; var lines = [cols.join(',')]; data.forEach(function(e) { lines.push(cols.map(function(c) { var v = c === 'changes' ? JSON.stringify(e[c] || {}) : String(e[c] != null ? e[c] : ''); return '"' + v.replace(/"/g, '""') + '"'; }).join(',')); }); blob = new Blob([lines.join('\n')], { type: 'text/csv' }); filename = modelName + '_' + pk + '_history.csv'; } var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); closeExportModal(); if (typeof showToast === 'function') { showToast('Exported ' + data.length + ' entries as ' + fmt.toUpperCase(), 'success'); } }; /* ── Keyboard shortcuts ────────────────────────────── */ document.addEventListener('keydown', function(e) { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; if (e.key === 'Escape') { closeExportModal(); closeCompareModal(); if (compareMode) exitCompareMode(); return; } if (e.key === 'e' || e.key === 'E') { if (expandBtn) expandBtn.click(); return; } if ((e.key === 'f' || e.key === 'F') && !e.ctrlKey && !e.metaKey) { e.preventDefault(); if (searchInput) searchInput.focus(); return; } if (e.key === 'c' || e.key === 'C') { if (compareBtn && !compareMode) compareBtn.click(); return; } }); /* ── Auto-expand first entry with changes ──────────── */ var autoExpanded = false; for (var i = 0; i < entries.length; i++) { if (entries[i].changes && Object.keys(entries[i].changes).length > 0) { var firstCard = document.getElementById('entry-' + i); if (firstCard) { firstCard.classList.add('expanded'); autoExpanded = true; } break; } } /* If no entry has changes, expand the first one anyway */ if (!autoExpanded && entries.length > 0) { var firstCard = document.getElementById('entry-0'); if (firstCard) firstCard.classList.add('expanded'); } /* ── Live time updates (every 30s) ─────────────────── */ setInterval(function() { document.querySelectorAll('.entry-time').forEach(function(el) { var ts = el.getAttribute('data-ts'); var textEl = el.querySelector('.time-text'); if (textEl) textEl.textContent = relativeTime(ts); }); if (lastEl && entries.length > 0) { lastEl.textContent = relativeTime(entries[0].timestamp || ''); } }, 30000); })(); {% endblock %}