feat: Make the admin interface less ugh

This commit is contained in:
Kumi 2025-06-24 16:56:35 +02:00
parent 9c754f55c6
commit 925a4158ac
Signed by: kumi
GPG key ID: ECBCC9082395383F
10 changed files with 2048 additions and 747 deletions

View file

@ -1,2 +1,3 @@
migrations/
.ruff_cache/
admin/

View file

@ -1,5 +1,4 @@
from django.contrib import admin
from django.urls import path
from django.contrib.admin import AdminSite
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.utils.translation import gettext_lazy as _
@ -7,6 +6,7 @@ from django.urls import reverse
from django.utils.html import format_html
from django.contrib.contenttypes.admin import GenericTabularInline
from django.utils import timezone
from django.contrib.admin.models import LogEntry
from datetime import timedelta
@ -47,6 +47,10 @@ class DuckpondAdminSite(AdminSite):
site_title = "Duckpond Admin"
index_title = "Dashboard"
index_template = "admin/index.html"
login_template = "admin/login.html"
app_index_template = "admin/app_index.html"
def index(self, request, extra_context=None):
# Get counts for dashboard stats
from duckpond.core.models import User, Membership
@ -55,6 +59,9 @@ class DuckpondAdminSite(AdminSite):
# Calculate date for "recent" items
recent_date = timezone.now() - timedelta(days=30)
# Get recent admin actions
recent_actions = LogEntry.objects.select_related("content_type", "user")[:10]
# Prepare stats
stats = {
"user_count": User.objects.count(),
@ -63,6 +70,7 @@ class DuckpondAdminSite(AdminSite):
"recent_payments": Payment.objects.filter(
created_at__gte=recent_date
).count(),
"recent_actions": recent_actions,
}
context = extra_context or {}
@ -114,7 +122,16 @@ class UserAdmin(BaseUserAdmin):
(None, {"fields": ("username", "password")}),
(
_("Personal info"),
{"fields": ("preferred_name", "first_name", "last_name", "company_name", "email", "address")},
{
"fields": (
"preferred_name",
"first_name",
"last_name",
"company_name",
"email",
"address",
)
},
),
(
_("Permissions"),
@ -131,7 +148,14 @@ class UserAdmin(BaseUserAdmin):
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
(
_("Membership"),
{"fields": ("is_approved", "email_verified", "gnucash_customer_id", "payment_reference")},
{
"fields": (
"is_approved",
"email_verified",
"gnucash_customer_id",
"payment_reference",
)
},
),
)
list_display = (

View file

@ -1,589 +0,0 @@
/* static/core/css/admin_override.css */
:root {
/* Admin Color Palette (Derived from your main style.css) */
--admin-primary: #4361ee; /* Main accent color */
--admin-primary-dark: #3f37c9; /* Darker accent for hover */
--admin-primary-light: #a1aff7; /* Lighter accent */
--admin-primary-fg: #ffffff; /* Text color on primary */
--admin-secondary: #4cc9f0; /* Secondary accent (less used) */
--admin-body-bg: #f8f9fa; /* Main background */
--admin-content-bg: #ffffff; /* Background for content areas */
--admin-body-fg: #212529; /* Main text color */
--admin-body-quiet-color: #6c757d; /* Lighter text */
--admin-body-loud-color: #000000; /* Used rarely */
--admin-header-bg: var(--admin-primary);
--admin-header-fg: var(--admin-primary-fg);
--admin-module-header-bg: #e9ecef; /* Lighter header for modules */
--admin-module-header-fg: var(--admin-body-fg);
--admin-module-border-color: #dee2e6;
--admin-link-fg: var(--admin-primary);
--admin-link-hover-color: var(--admin-primary-dark);
--admin-button-bg: var(--admin-primary);
--admin-button-fg: var(--admin-primary-fg);
--admin-button-hover-bg: var(--admin-primary-dark);
--admin-delete-button-bg: #dc3545; /* Danger color */
--admin-delete-button-hover-bg: #c82333;
--admin-message-success-bg: #d1e7dd;
--admin-message-success-fg: #0f5132;
--admin-message-warning-bg: #fff3cd;
--admin-message-warning-fg: #664d03;
--admin-message-error-bg: #f8d7da;
--admin-message-error-fg: #58151c;
--admin-border-radius: 0.375rem; /* Consistent border radius */
--admin-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--admin-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
}
body {
background-color: var(--admin-body-bg);
color: var(--admin-body-fg);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 0.95rem; /* Slightly smaller base font for admin */
}
a {
color: var(--admin-link-fg);
}
a:hover {
color: var(--admin-link-hover-color);
}
/* --- Header --- */
#header {
background-color: var(--admin-header-bg);
color: var(--admin-header-fg);
box-shadow: var(--admin-box-shadow-sm);
height: auto;
padding: 0.75rem 1.5rem;
}
#branding h1, #branding h1 a {
color: var(--admin-header-fg);
font-weight: 500;
font-size: 1.25rem;
}
#branding img {
vertical-align: -5px; /* Better alignment for the logo */
}
#user-tools {
color: var(--admin-header-fg);
font-size: 0.9rem;
font-weight: 500;
}
#user-tools a {
color: var(--admin-header-fg);
border-bottom: none;
padding: 0.5rem 0.75rem;
border-radius: var(--admin-border-radius);
transition: background-color 0.2s ease;
}
#user-tools a:hover {
background-color: rgba(255, 255, 255, 0.15);
color: var(--admin-header-fg);
}
/* --- Breadcrumbs --- */
div.breadcrumbs {
background: var(--admin-content-bg);
padding: 0.75rem 1.5rem;
border-bottom: 1px solid var(--admin-module-border-color);
box-shadow: var(--admin-box-shadow-sm);
color: var(--admin-body-quiet-color);
font-size: 0.85rem;
}
div.breadcrumbs a {
color: var(--admin-link-fg);
}
div.breadcrumbs a:hover {
color: var(--admin-link-hover-color);
}
/* --- Main Content Area --- */
#content {
padding: 1.5rem;
}
/* --- Modules --- */
.module {
border: none;
background: var(--admin-content-bg);
border-radius: var(--admin-border-radius);
box-shadow: var(--admin-box-shadow-sm);
margin-bottom: 1.5rem;
}
.module caption, .module h2, .inline-group h2 {
background: var(--admin-module-header-bg);
color: var(--admin-module-header-fg);
border-bottom: 1px solid var(--admin-module-border-color);
padding: 0.75rem 1.25rem;
margin: 0;
font-size: 1rem;
font-weight: 500;
}
.module table {
border-collapse: collapse; /* Ensure borders look clean */
}
.module td, .module th {
padding: 0.75rem 1.25rem;
border-bottom: 1px solid var(--admin-module-border-color);
font-size: 0.9rem;
vertical-align: middle; /* Ensure vertical alignment is consistent */
}
.module tr:last-child td, .module tr:last-child th {
border-bottom: none; /* Remove border from last row */
}
.module th {
font-weight: 600;
color: var(--admin-body-fg);
background-color: #f8f9fa; /* Slightly different bg for table headers */
}
.module table thead th {
text-align: left;
border-bottom-width: 2px; /* Thicker border below table main headers */
border-bottom-color: var(--admin-module-border-color);
}
/* Alternating Row Colors (Applied to both td and th in tbody) */
.module table tbody tr:nth-child(even) td,
.module table tbody tr:nth-child(even) th {
background-color: var(--admin-body-bg);
}
/* --- Forms --- */
.form-row {
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--admin-module-border-color);
margin-bottom: 0; /* Remove default margin */
}
.form-row:last-child {
border-bottom: none;
}
.aligned label {
font-weight: 500;
padding-top: 0.6em; /* Align label better with input */
color: var(--admin-body-quiet-color);
width: 12em; /* Adjust label width as needed */
}
form .aligned p.help, form .aligned div.help {
font-size: 0.85rem;
color: var(--admin-body-quiet-color);
margin-left: 13em; /* Match label width + spacing */
}
input[type="text"], input[type="password"], input[type="email"], input[type="url"],
input[type="number"], input[type="date"], input[type="datetime-local"],
input[type="month"], input[type="week"], input[type="time"],
input[type="search"], input[type="tel"], textarea, select {
border: 1px solid var(--admin-module-border-color);
border-radius: var(--admin-border-radius);
padding: 0.5rem 0.75rem;
font-size: 0.9rem;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
input:focus, textarea:focus, select:focus {
border-color: var(--admin-primary-light);
box-shadow: 0 0 0 0.2rem rgba(67, 97, 238, 0.25);
outline: none;
}
.selector-available h2, .selector-chosen h2 {
font-size: 0.9rem;
font-weight: 500;
background: var(--admin-module-header-bg);
border: 1px solid var(--admin-module-border-color);
border-bottom: none;
padding: 0.5rem 0.75rem;
}
/* --- Buttons --- */
.button, input[type=submit], input[type=button], .submit-row input, a.button {
background: var(--admin-button-bg);
color: var(--admin-button-fg);
border: none;
border-radius: var(--admin-border-radius);
padding: 0.6rem 1.2rem;
font-size: 0.85rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
cursor: pointer;
transition: background-color 0.2s ease;
text-decoration: none;
line-height: normal; /* Reset line-height */
}
.button:hover, input[type=submit]:hover, input[type=button]:hover, .submit-row input:hover, a.button:hover {
background: var(--admin-button-hover-bg);
color: var(--admin-button-fg);
}
.button.default, input[type=submit].default, .submit-row input.default {
background: var(--admin-primary); /* Ensure default uses primary */
}
.button.default:hover, input[type=submit].default:hover, .submit-row input.default:hover {
background: var(--admin-primary-dark);
}
.button.cancel-link, a.button.cancel-link {
background: var(--admin-body-quiet-color);
}
.button.cancel-link:hover, a.button.cancel-link:hover {
background: var(--admin-body-fg);
}
a.deletelink {
background: var(--admin-delete-button-bg);
color: var(--admin-button-fg);
}
a.deletelink:hover {
background: var(--admin-delete-button-hover-bg);
}
.submit-row {
background: var(--admin-content-bg);
border-top: 1px solid var(--admin-module-border-color);
padding: 1rem 1.25rem;
overflow: visible;
border-radius: 0 0 var(--admin-border-radius) var(--admin-border-radius);
}
.submit-row p.deletelink-box {
float: left; /* Align delete button left */
}
.submit-row input {
margin-left: 0.5rem;
}
/* --- Object Tools --- */
.object-tools {
padding: 0;
margin: 0;
}
.object-tools a {
background: var(--admin-button-bg);
color: var(--admin-button-fg);
padding: 0.5rem 1rem;
border-radius: var(--admin-border-radius);
font-size: 0.8rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-left: 0.5rem;
transition: background-color 0.2s ease;
}
.object-tools a:hover {
background: var(--admin-button-hover-bg);
color: var(--admin-button-fg);
}
.object-tools a.addlink {
background-color: #28a745; /* Green for add */
}
.object-tools a.addlink:hover {
background-color: #218838;
}
.object-tools a.historylink {
background-color: #ffc107; /* Yellow for history */
color: var(--admin-body-fg);
}
.object-tools a.historylink:hover {
background-color: #e0a800;
}
/* --- Changelist --- */
#changelist-filter {
background: var(--admin-content-bg);
border-radius: var(--admin-border-radius);
box-shadow: var(--admin-box-shadow-sm);
padding: 1rem 1.25rem;
border: none;
margin-bottom: 1.5rem;
}
#changelist-filter h2 {
font-size: 1rem;
font-weight: 500;
color: var(--admin-body-fg);
margin-bottom: 0.75rem;
}
#changelist-filter h3 {
font-size: 0.9rem;
font-weight: 500;
color: var(--admin-body-quiet-color);
margin-bottom: 0.5rem;
}
#changelist-filter ul {
padding-left: 0;
list-style: none;
}
#changelist-filter li {
margin-bottom: 0.25rem;
}
#changelist-filter li.selected a {
font-weight: bold;
color: var(--admin-link-hover-color);
}
#changelist table thead th {
background-color: var(--admin-module-header-bg); /* Header background for changelist */
}
#changelist .actions {
background: var(--admin-module-header-bg);
border-top: 1px solid var(--admin-module-border-color);
border-bottom: 1px solid var(--admin-module-border-color);
padding: 0.75rem 1.25rem;
border-radius: 0; /* Remove radius from actions bar */
}
#changelist .paginator {
border-top: 1px solid var(--admin-module-border-color);
padding: 0.75rem 1.25rem;
background: var(--admin-content-bg);
border-radius: 0 0 var(--admin-border-radius) var(--admin-border-radius);
}
#changelist table tbody tr:hover {
background-color: #f0f3ff; /* Subtle hover effect */
}
#changelist table tbody th a {
font-weight: 500; /* Make main link slightly bolder */
}
/* --- Messages --- */
ul.messagelist li {
background-color: var(--admin-message-success-bg);
color: var(--admin-message-success-fg);
border: 1px solid rgba(0,0,0,0.1);
padding: 0.75rem 1.25rem;
border-radius: var(--admin-border-radius);
font-size: 0.9rem;
margin-bottom: 1rem;
}
ul.messagelist li.warning {
background-color: var(--admin-message-warning-bg);
color: var(--admin-message-warning-fg);
}
ul.messagelist li.error {
background-color: var(--admin-message-error-bg);
color: var(--admin-message-error-fg);
}
/* --- Related Widget Wrapper --- */
.related-widget-wrapper {
border: 1px solid var(--admin-module-border-color);
border-radius: var(--admin-border-radius);
}
.related-widget-wrapper > select {
border: none;
border-radius: 0;
}
/* --- Dashboard / Index --- */
.dashboard #content {
width: auto; /* Let content flow */
}
.dashboard .module table caption a {
color: var(--admin-module-header-fg); /* Match caption text color */
}
.dashboard .module table td a {
background: none;
color: var(--admin-link-fg);
display: inline;
padding: 0;
border-radius: 0;
text-align: left;
margin: 0;
text-decoration: underline;
}
.dashboard .module table td a:hover {
background: none;
color: var(--admin-link-hover-color);
}
.dashboard .addlink, .dashboard .changelink, .dashboard .viewlink {
margin-left: 0.5em;
}
.dashboard .module table th a {
font-weight: bold;
}
.dashboard .module table td, .dashboard .module table th {
white-space: normal; /* Allow wrapping in dashboard tables */
}
/* Custom Dashboard Widgets from index.html */
.dashboard-widget {
background: var(--admin-content-bg);
border-radius: var(--admin-border-radius);
box-shadow: var(--admin-box-shadow-sm);
margin-bottom: 1.5rem;
overflow: hidden;
}
.dashboard-widget-title {
background: var(--admin-module-header-bg);
color: var(--admin-module-header-fg);
border-bottom: 1px solid var(--admin-module-border-color);
padding: 0.75rem 1.25rem;
font-size: 1rem;
font-weight: 500;
}
.dashboard-widget-content {
padding: 1.25rem;
}
.dashboard-widget .module { /* Reset styling for modules inside widgets */
box-shadow: none;
border-radius: 0;
margin: 0;
background: none;
}
.dashboard-widget .module table {
margin-top: 1rem;
}
.dashboard-widget .module table caption {
background: none;
color: var(--admin-body-fg);
padding: 0 0 0.5rem 0;
border-bottom: none;
text-align: left;
font-size: 1.1rem;
font-weight: 600;
}
.dashboard-widget ul {
list-style: none;
padding: 0;
margin: 0;
}
.dashboard-widget ul li {
margin-bottom: 0.5rem;
}
/* Dashboard Stats */
.dashboard-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1.5rem;
margin-bottom: 1.5rem;
}
.stat-box {
background: var(--admin-content-bg);
border-radius: var(--admin-border-radius);
box-shadow: var(--admin-box-shadow-sm);
padding: 1.25rem;
text-align: center;
}
.stat-number {
font-size: 2rem;
font-weight: 600;
color: var(--admin-primary);
margin-bottom: 0.25rem;
line-height: 1.2;
}
.stat-label {
color: var(--admin-body-quiet-color);
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* --- Sidebar --- */
#content-related {
background: var(--admin-body-bg); /* Match main bg */
border: none;
margin-left: 1.5rem;
}
#content-related .module {
background: var(--admin-content-bg);
border: none; /* Remove border */
}
#content-related h2 {
font-size: 1rem;
font-weight: 500;
padding: 0.75rem 1.25rem;
background: var(--admin-module-header-bg);
color: var(--admin-module-header-fg);
border-bottom: 1px solid var(--admin-module-border-color);
}
#content-related .actionlist {
padding: 0.75rem 1.25rem;
}
#content-related .actionlist li {
margin-bottom: 0.5rem;
}
/* --- Responsive Adjustments --- */
@media (max-width: 1024px) {
#header {
padding: 0.5rem 1rem;
}
#content {
padding: 1rem;
}
.aligned label {
width: 10em;
}
form .aligned p.help, form .aligned div.help {
margin-left: 11em;
}
}
@media (max-width: 767px) {
#header {
display: flex;
flex-direction: column;
align-items: flex-start;
height: auto;
}
#branding {
margin-bottom: 0.5rem;
}
#user-tools {
width: 100%;
text-align: right;
padding-top: 0.5rem;
border-top: 1px solid rgba(255, 255, 255, 0.2);
}
.object-tools {
position: static;
margin: 1rem calc(-1 * var(--content-padding)) 0; /* Adjust based on #content padding */
padding: 0.75rem var(--content-padding);
background: var(--admin-module-header-bg);
border-top: 1px solid var(--admin-module-border-color);
border-bottom: 1px solid var(--admin-module-border-color);
}
.object-tools a {
margin-left: 0;
margin-right: 0.5rem;
}
#content-related {
margin-left: 0;
margin-top: 1.5rem;
}
.colM { /* Make main column full width on mobile */
width: 100%;
}
.colSM { /* Make sidebar full width on mobile */
width: 100%;
}
.aligned label {
width: 100%;
text-align: left;
float: none;
padding-bottom: 0.25rem;
}
.aligned .form-row div {
float: none;
width: 100%;
padding-left: 0;
}
form .aligned p.help, form .aligned div.help {
margin-left: 0;
margin-top: 0.25rem;
}
.submit-row {
padding: 1rem;
}
.submit-row input, .submit-row p.deletelink-box a {
display: block;
width: 100%;
margin: 0.5rem 0;
}
.submit-row p.deletelink-box {
float: none;
text-align: center;
margin-bottom: 1rem;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,108 @@
document.addEventListener('DOMContentLoaded', function () {
// User menu toggle
const userInfo = document.querySelector('.user-info');
const userMenu = document.querySelector('.user-menu');
if (userInfo && userMenu) {
userInfo.addEventListener('click', function (e) {
e.preventDefault();
userMenu.classList.toggle('show');
});
// Close menu when clicking outside
document.addEventListener('click', function (e) {
if (!userInfo.contains(e.target) && !userMenu.contains(e.target)) {
userMenu.classList.remove('show');
}
});
}
// Add icons to sidebar menu items based on model
const modelIcons = {
'user': 'fas fa-user',
'membership': 'fas fa-id-card',
'membershiptier': 'fas fa-layer-group',
'paymentcycle': 'fas fa-calendar-alt',
'customfield': 'fas fa-list-alt',
'payment': 'fas fa-money-bill-wave',
'paymentmethod': 'fas fa-credit-card',
'content': 'fas fa-file-alt',
'category': 'fas fa-folder',
'contenttype': 'fas fa-file-code',
'eventregistration': 'fas fa-calendar-check',
'certificate': 'fas fa-certificate',
'download': 'fas fa-download',
'page': 'fas fa-file',
'section': 'fas fa-puzzle-piece',
'form': 'fas fa-wpforms',
'formsubmission': 'fas fa-paper-plane',
'announcement': 'fas fa-bullhorn'
};
// Add default icon for models without specific icon
const defaultIcon = 'fas fa-cube';
// Find all model links in the sidebar
const modelLinks = document.querySelectorAll('.model-link');
modelLinks.forEach(link => {
const modelName = link.parentElement.className.split('model-')[1];
if (modelName) {
const icon = document.createElement('i');
icon.className = modelIcons[modelName] || defaultIcon;
icon.style.marginRight = '8px';
link.insertBefore(icon, link.firstChild);
}
});
// Enhance form widgets
enhanceFormWidgets();
});
function enhanceFormWidgets() {
// Add datepicker to date inputs
const dateInputs = document.querySelectorAll('input[type="date"]');
dateInputs.forEach(input => {
// Add calendar icon
const wrapper = document.createElement('div');
wrapper.className = 'date-input-wrapper';
wrapper.style.position = 'relative';
const icon = document.createElement('i');
icon.className = 'fas fa-calendar-alt';
icon.style.position = 'absolute';
icon.style.right = '10px';
icon.style.top = '50%';
icon.style.transform = 'translateY(-50%)';
icon.style.color = '#6c757d';
icon.style.pointerEvents = 'none';
input.parentNode.insertBefore(wrapper, input);
wrapper.appendChild(input);
wrapper.appendChild(icon);
});
// Add select2-like styling to select elements
const selectElements = document.querySelectorAll('select:not([multiple])');
selectElements.forEach(select => {
const wrapper = document.createElement('div');
wrapper.className = 'select-wrapper';
wrapper.style.position = 'relative';
const icon = document.createElement('i');
icon.className = 'fas fa-chevron-down';
icon.style.position = 'absolute';
icon.style.right = '10px';
icon.style.top = '50%';
icon.style.transform = 'translateY(-50%)';
icon.style.color = '#6c757d';
icon.style.pointerEvents = 'none';
select.parentNode.insertBefore(wrapper, select);
wrapper.appendChild(select);
wrapper.appendChild(icon);
// Style the select element
select.style.appearance = 'none';
select.style.paddingRight = '30px';
});
}

View file

@ -0,0 +1,47 @@
{% load i18n %}
{% if app_list %}
{% for app in app_list %}
<div class="app-{{ app.app_label }} module{% if app.app_url in request.path %} current-app{% endif %}">
<table>
<caption>
<a href="{{ app.app_url }}"
class="section"
title="{% blocktranslate with name=app.name %}Models in the {{ name }} application{% endblocktranslate %}">
<i class="fas fa-folder"></i> {{ app.name }}
</a>
</caption>
{% for model in app.models %}
<tr class="model-{{ model.object_name|lower }}{% if model.admin_url in request.path %} current-model{% endif %}">
{% if model.admin_url %}
<th scope="row">
<a href="{{ model.admin_url }}"
{% if model.admin_url in request.path %}aria-current="page"{% endif %}>
<i class="fas fa-table"></i> {{ model.name }}</a>
</th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.add_url %}
<td>
<a href="{{ model.add_url }}" class="addlink">
<i class="fas fa-plus"></i> {% translate 'Add' %}</a>
</td>
{% else %}
<td></td>
{% endif %}
{% if model.admin_url and show_changelinks %}
<td>
<a href="{{ model.admin_url }}" class="changelink">
<i class="fas fa-list"></i> {% translate 'Change' %}</a>
</td>
{% else %}
<td></td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endfor %}
{% else %}
<p>{% translate 'You don\'t have permission to view or edit anything.' %}</p>
{% endif %}

View file

@ -0,0 +1,200 @@
{% load i18n static %}
<!DOCTYPE html>
{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %}
<html lang="{{ LANGUAGE_CODE|default:"en-us" }}"
dir="{{ LANGUAGE_BIDI|yesno:'rtl,ltr,auto' }}">
<head>
<title>
{% block title %}{% endblock %}
</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet"
href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}">
<link rel="stylesheet"
href="https://nocdnbs.private.coffee/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" href="{% static 'admin/css/duckpond-admin.css' %}">
{% if not is_popup and is_nav_sidebar_enabled %}
<link rel="stylesheet" href="{% static "admin/css/nav_sidebar.css" %}">
<script src="{% static 'admin/js/nav_sidebar.js' %}" defer></script>
{% endif %}
{% block extrastyle %}{% endblock %}
{% if LANGUAGE_BIDI %}
<link rel="stylesheet"
href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}">
{% endif %}
{% block extrahead %}{% endblock %}
{% block responsive %}
<meta name="viewport"
content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0">
<link rel="stylesheet" href="{% static "admin/css/responsive.css" %}">
{% if LANGUAGE_BIDI %}
<link rel="stylesheet" href="{% static "admin/css/responsive_rtl.css" %}">
{% endif %}
{% endblock %}
{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE">{% endblock %}
<style type="text/css">
/* Force full width layout */
html, body {
width: 100%;
max-width: 100%;
margin: 0;
padding: 0;
}
#container {
width: 100%;
max-width: 100%;
margin: 0;
padding: 0;
}
#main {
width: 100%;
max-width: 100%;
}
#content {
width: 100%;
max-width: 100%;
padding: 20px;
box-sizing: border-box;
}
#content-main {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
/* Fix for app list */
#app-list {
width: 100%;
}
.app-section {
width: 100%;
}
/* Fix for colMS layout */
.colMS {
display: flex;
flex-wrap: wrap;
}
.colMS #content-main {
flex: 1;
min-width: 0;
max-width: 100%;
}
.colMS #content-related {
width: 260px;
margin-left: 20px;
}
@media (max-width: 1024px) {
.colMS {
flex-direction: column;
}
.colMS #content-main {
width: 100%;
}
.colMS #content-related {
width: 100%;
margin-left: 0;
margin-top: 20px;
}
}
</style>
</head>
<body class="{% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}"
data-admin-utc-offset="{% now "Z" %}">
<!-- Container -->
<div id="container">
{% if not is_popup %}
<!-- Header -->
<div id="header">
<div id="branding">
{% block branding %}{% endblock %}
</div>
{% block usertools %}
{% if has_permission %}
<div id="user-tools">
{% block welcome-msg %}
{% translate 'Welcome,' %}
<strong>{% firstof user.get_short_name user.get_username %}</strong>.
{% endblock %}
{% block userlinks %}
{% if site_url %}
<a href="{{ site_url }}">{% translate 'View site' %}</a> /
{% endif %}
{% if user.is_active and user.is_staff %}
{% url 'django-admindocs-docroot' as docsroot %}
{% if docsroot %}
<a href="{{ docsroot }}">{% translate 'Documentation' %}</a> /
{% endif %}
{% endif %}
{% if user.has_usable_password %}
<a href="{% url 'admin:password_change' %}">{% translate 'Change password' %}</a> /
{% endif %}
<form id="logout-form" method="post" action="{% url 'admin:logout' %}">
{% csrf_token %}
<button type="submit" class="logout-button">{% translate 'Log out' %}</button>
</form>
{% endblock %}
</div>
{% endif %}
{% endblock %}
{% block nav-global %}{% endblock %}
</div>
<!-- END Header -->
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
{% if title %}&rsaquo; {{ title }}{% endif %}
</div>
{% endblock %}
{% endif %}
<div id="main" class="{% if not is_popup %}full-width{% endif %}">
{% if not is_popup and is_nav_sidebar_enabled %}
{% block nav-sidebar %}
{% include "admin/nav_sidebar.html" %}
{% endblock %}
{% endif %}
<div id="content-wrapper"
class="{% if not is_popup and is_nav_sidebar_enabled %}shifted{% endif %}">
{% block messages %}
{% if messages %}
<ul class="messagelist">
{% for message in messages %}
<li {% if message.tags %}class="{{ message.tags }}"{% endif %}>{{ message|capfirst }}</li>
{% endfor %}
</ul>
{% endif %}
{% endblock messages %}
<!-- Content -->
<div id="content" class="{% block coltype %}colM{% endblock %}">
{% block pretitle %}{% endblock %}
{% block content_title %}
{% if title %}<h1>{{ title }}</h1>{% endif %}
{% endblock %}
{% block content_subtitle %}
{% if subtitle %}<h2>{{ subtitle }}</h2>{% endif %}
{% endblock %}
{% block content %}
{% block object-tools %}{% endblock %}
{{ content }}
{% endblock %}
{% block sidebar %}{% endblock %}
<br class="clear">
</div>
<!-- END Content -->
</div>
</div>
</div>
<!-- END Container -->
<script src="{% static 'admin/js/duckpond-admin.js' %}"></script>
</body>
</html>

View file

@ -1,21 +1,46 @@
{% extends "admin/base_site.html" %}
{% load static i18n %}
{% block title %}{{ title }} | {{ site_title|default:_('Duckpond Admin') }}{% endblock %}
{% block branding %}
<h1 id="site-name">
<a href="{% url 'admin:index' %}">
<img src="{% static 'core/img/logo_admin.png' %}" alt="{{ site_header|default:_('Duckpond Administration') }}" height="30" style="margin-right: 10px; vertical-align: middle;">
</a>
</h1>
{% endblock %}
{% block nav-global %}{% endblock %}
{% extends "admin/base.html" %}
{% load static %}
{% load i18n %}
{% block title %}{{ title }} | {{ site_title|default:_("Duckpond Admin") }}{% endblock %}
{% block extrastyle %}
{{ block.super }} {# Loads default admin CSS #}
<link rel="preconnect" href="https://googledonts.private.coffee" crossorigin>
<link href="https://googledonts.private.coffee/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/admin_override.css' %}">
<link rel="stylesheet" href="{% static 'admin/css/duckpond-admin.css' %}">
<link rel="stylesheet"
href="https://nocdnbs.private.coffee/ajax/libs/font-awesome/6.0.0/css/all.min.css">
{% endblock %}
{% block branding %}
<h1 id="site-name">
<a href="{% url 'admin:index' %}">
<span class="brand-icon"><i class="fas fa-water"></i></span>
<span class="brand-text">{{ site_header|default:_("Duckpond Administration") }}</span>
</a>
</h1>
{% endblock %}
{% block nav-global %}{% endblock %}
{% block usertools %}
<div id="user-tools" class="user-tools-container">
{% if user.is_active and user.is_staff %}
<div class="user-info">
<span class="user-avatar">
<i class="fas fa-user-circle"></i>
</span>
<span class="user-name">
{% firstof user.get_short_name user.get_username %}
<i class="fas fa-chevron-down"></i>
</span>
</div>
<div class="user-menu">
{% if user.has_usable_password %}
<a href="{% url 'admin:password_change' %}">
<i class="fas fa-key"></i> {% translate 'Change password' %}
</a>
{% endif %}
<a href="{% url 'admin:logout' %}">
<i class="fas fa-sign-out-alt"></i> {% translate 'Log out' %}
</a>
</div>
{% endif %}
</div>
{% endblock %}
{% block extrajs %}
<script src="{% static 'admin/js/duckpond-admin.js' %}"></script>
{% endblock %}

View file

@ -1,34 +0,0 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_urls static admin_modify %}
{% block extrahead %}
{{ block.super }}
<script src="{% url 'admin:jsi18n' %}"></script>
{{ form.media }}
{% endblock %}
{% block content %}
<div id="content-main">
<form method="post" id="add_section_form">
{% csrf_token %}
<div>
<fieldset class="module aligned">
{% for field in form %}
<div class="form-row">
<div class="field-box">
{{ field.errors }}
{{ field.label_tag }}
{{ field }}
{% if field.help_text %}<div class="help">{{ field.help_text|safe }}</div>{% endif %}
</div>
</div>
{% endfor %}
</fieldset>
<div class="submit-row">
<input type="submit"
value="{% trans 'Add Section' %}"
class="default"
name="_save">
</div>
</div>
</form>
</div>
{% endblock %}

View file

@ -1,116 +1,194 @@
{% extends "admin/index.html" %}
{% load i18n %}
{% extends "admin/base_site.html" %}
{% load i18n static %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet"
type="text/css"
href="{% static "admin/css/dashboard.css" %}">
{% endblock %}
{% block coltype %}colMS{% endblock %}
{% block bodyclass %}{{ block.super }} dashboard{% endblock %}
{% block breadcrumbs %}{% endblock %}
{% block nav-sidebar %}{% endblock %}
{% block content %}
<div id="content-main">
<div class="dashboard-stats">
<div class="stat-box">
<div class="stat-number">{{ user_count }}</div>
<div class="stat-label">Users</div>
</div>
<div class="stat-box">
<div class="stat-number">{{ active_memberships }}</div>
<div class="stat-label">Active Memberships</div>
</div>
<div class="stat-box">
<div class="stat-number">{{ pending_memberships }}</div>
<div class="stat-label">Pending Memberships</div>
</div>
<div class="stat-box">
<div class="stat-number">{{ recent_payments }}</div>
<div class="stat-label">Recent Payments</div>
</div>
</div>
<div class="dashboard-widget">
<div class="dashboard-widget-title">{% trans 'Quick Actions' %}</div>
<div class="dashboard-widget-content">
<div class="module">
<table>
<tr>
<td><a href="{% url 'import_member' %}">Import Existing Member</a></td>
</tr>
<tr>
<td><a href="{% url 'admin:core_membership_add' %}">Add New Membership</a></td>
</tr>
<tr>
<td><a href="{% url 'admin:core_page_add' %}">Create New Page</a></td>
</tr>
<tr>
<td><a href="{% url 'admin:core_announcement_add' %}">Add Announcement</a></td>
</tr>
</table>
<div id="content-main">
<!-- Stats Dashboard -->
<div class="dashboard">
<div class="stat-card stat-users">
<div class="stat-icon">
<i class="fas fa-users"></i>
</div>
<div class="stat-info">
<h3 class="stat-value">{{ user_count }}</h3>
<p class="stat-label">Total Users</p>
</div>
</div>
<div class="stat-card stat-memberships">
<div class="stat-icon">
<i class="fas fa-id-card"></i>
</div>
<div class="stat-info">
<h3 class="stat-value">{{ active_memberships }}</h3>
<p class="stat-label">Active Memberships</p>
</div>
</div>
<div class="stat-card stat-payments">
<div class="stat-icon">
<i class="fas fa-money-bill-wave"></i>
</div>
<div class="stat-info">
<h3 class="stat-value">{{ recent_payments }}</h3>
<p class="stat-label">Recent Payments</p>
</div>
</div>
<div class="stat-card stat-pending">
<div class="stat-icon">
<i class="fas fa-clock"></i>
</div>
<div class="stat-info">
<h3 class="stat-value">{{ pending_memberships }}</h3>
<p class="stat-label">Pending Approvals</p>
</div>
</div>
</div>
</div>
{% if app_list %}
<div class="dashboard-widget">
<div class="dashboard-widget-title">{% trans 'Applications' %}</div>
<div class="dashboard-widget-content">
<!-- Quick Actions -->
<div class="quick-actions">
<h2>Quick Actions</h2>
<div class="action-buttons">
<a href="{% url 'admin:core_user_add' %}" class="action-button primary">
<i class="fas fa-user-plus"></i> Add User
</a>
<a href="{% url 'admin:core_membership_add' %}"
class="action-button primary">
<i class="fas fa-id-card"></i> Create Membership
</a>
<a href="{% url 'admin:payments_payment_add' %}"
class="action-button primary">
<i class="fas fa-money-bill"></i> Record Payment
</a>
<a href="{% url 'admin:core_formsubmission_changelist' %}"
class="action-button">
<i class="fas fa-clipboard-list"></i> View Form Submissions
</a>
<a href="{% url 'admin:member_area_content_add' %}"
class="action-button">
<i class="fas fa-file-alt"></i> Add Content
</a>
<a href="{% url 'admin:member_area_eventregistration_changelist' %}"
class="action-button">
<i class="fas fa-calendar-check"></i> Event Registrations
</a>
</div>
</div>
<!-- Recent Activity -->
<div class="recent-activity">
<h2>Recent Activity</h2>
<ul class="activity-list">
{% for log in recent_actions %}
<li class="activity-item">
<div class="activity-icon">
{% if log.is_addition %}
<i class="fas fa-plus"></i>
{% elif log.is_change %}
<i class="fas fa-edit"></i>
{% elif log.is_deletion %}
<i class="fas fa-trash"></i>
{% endif %}
</div>
<div class="activity-content">
<p class="activity-text">
{{ log.user.username }}
{% if log.is_addition %}
added
{% elif log.is_change %}
changed
{% elif log.is_deletion %}
deleted
{% endif %}
{{ log.content_type.name }}: {{ log.object_repr }}
</p>
<p class="activity-meta">{{ log.action_time|timesince }} ago</p>
</div>
{% if log.is_addition or log.is_change %}
<a href="{{ log.get_admin_url }}" class="activity-action">
<i class="fas fa-eye"></i>
</a>
{% endif %}
</li>
{% empty %}
<li class="activity-item">
<p>No recent activity.</p>
</li>
{% endfor %}
</ul>
</div>
<!-- App List -->
<div id="app-list">
{% for app in app_list %}
<div class="app-{{ app.app_label }} module{% if app.app_url in request.path %} current-app{% endif %}">
<table>
<caption>
<a href="{{ app.app_url }}" class="section" title="{% blocktrans with name=app.name %}Models in the {{ name }} application{% endblocktrans %}">{{ app.name }}</a>
</caption>
<div class="app-section">
<h2 class="app-name">{{ app.name }}</h2>
<div class="app-models">
{% for model in app.models %}
<tr class="model-{{ model.object_name|lower }}{% if model.admin_url in request.path %} current-model{% endif %}">
<div class="model-item">
{% if model.admin_url %}
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
<a href="{{ model.admin_url }}" class="model-link">
<i class="fas fa-table"></i> {{ model.name }}
</a>
{% else %}
<th scope="row">{{ model.name }}</th>
<span class="model-link disabled">{{ model.name }}</span>
{% endif %}
{% if model.add_url %}
<td><a href="{{ model.add_url }}" class="addlink">{% trans 'Add' %}</a></td>
{% else %}
<td>&nbsp;</td>
{% endif %}
{% if model.admin_url %}
{% if model.view_only %}
<td><a href="{{ model.admin_url }}" class="viewlink">{% trans 'View' %}</a></td>
{% else %}
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
<div class="model-actions">
{% if model.add_url %}
<a href="{{ model.add_url }}" class="add-link">
<i class="fas fa-plus"></i>
</a>
{% endif %}
{% else %}
<td>&nbsp;</td>
{% endif %}
</tr>
{% if model.admin_url %}
<a href="{{ model.admin_url }}" class="change-link">
<i class="fas fa-list"></i>
</a>
{% endif %}
</div>
</div>
{% endfor %}
</table>
</div>
</div>
{% endfor %}
</div>
</div>
{% else %}
<p>{% trans "You don't have permission to view or edit anything." %}</p>
{% endif %}
</div>
{% endblock %}
{% block sidebar %}
{{ block.super }}
<div class="dashboard-widget">
<div class="dashboard-widget-title">{% trans 'Member Management' %}</div>
<div class="dashboard-widget-content">
<ul>
<li><a href="{% url 'import_member' %}">Import Existing Members</a></li>
<li><a href="{% url 'admin:core_membership_changelist' %}">Manage Memberships</a></li>
<li><a href="{% url 'admin:core_user_changelist' %}">Manage Users</a></li>
</ul>
<div id="content-related">
<div class="module" id="recent-actions-module">
<h2>{% translate 'My Actions' %}</h2>
<h3>{% translate 'My Recent Actions' %}</h3>
{% load log %}
{% get_admin_log 10 as admin_log for_user user %}
{% if not admin_log %}
<p>{% translate 'None available' %}</p>
{% else %}
<ul class="actionlist">
{% for entry in admin_log %}
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">
{% if entry.is_deletion or not entry.get_admin_url %}
{{ entry.object_repr }}
{% else %}
<a href="{{ entry.get_admin_url }}">{{ entry.object_repr }}</a>
{% endif %}
<br>
{% if entry.content_type %}
<span class="mini quiet">
{% filter capfirst %}
{{ entry.content_type.name }}
{% endfilter %}
</span>
{% else %}
<span class="mini quiet">{% translate 'Unknown content' %}</span>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
</div>
<div class="dashboard-widget">
<div class="dashboard-widget-title">{% trans 'Content Management' %}</div>
<div class="dashboard-widget-content">
<ul>
<li><a href="{% url 'admin:core_page_changelist' %}">Manage Pages</a></li>
<li><a href="{% url 'admin:core_announcement_changelist' %}">Manage Announcements</a></li>
<li><a href="{% url 'admin:member_area_content_changelist' %}">Manage Content</a></li>
</ul>
</div>
</div>
{% endblock %}