Add secret management guide, client-side performance monitoring, and search accessibility enhancements

- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols.
- Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage.
- Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
This commit is contained in:
pacnpal
2025-12-23 16:41:42 -05:00
parent ae31e889d7
commit edcd8f2076
155 changed files with 22046 additions and 4645 deletions

138
docs/admin/CHANGELOG.md Normal file
View File

@@ -0,0 +1,138 @@
# Admin Interface Changelog
## Phase 11: Django Admin Standardization
### Added
- **Base Admin Classes and Mixins** (`apps/core/admin/`)
- `BaseModelAdmin` - Standard base class with consistent pagination and settings
- `QueryOptimizationMixin` - Automatic select_related/prefetch_related optimization
- `ReadOnlyAdminMixin` - Disable modifications for auto-generated data
- `TimestampFieldsMixin` - Standard handling for created_at/updated_at
- `SlugFieldMixin` - Automatic slug prepopulation
- `ExportActionMixin` - CSV/JSON export functionality
- `BulkStatusChangeMixin` - Bulk status change actions
- `ModerationMixin` - Standard moderation approve/reject actions
- **Query Optimization Across All Admin Classes**
- `list_select_related` for all ForeignKey fields in list_display
- `list_prefetch_related` for reverse relations and M2M fields
- Queryset annotations for calculated fields (ride counts, average ratings)
- **Custom Bulk Actions**
- Parks: `bulk_activate`, `bulk_deactivate`, `recalculate_stats`
- Rides: `bulk_set_operating`, `bulk_set_closed`, `bulk_set_sbno`, `recalculate_ratings`
- Accounts: `activate_users`, `deactivate_users`, `ban_users`, `unban_users`, `send_verification_email`, `recalculate_credits`
- Reviews: `bulk_approve`, `bulk_reject`, `flag_for_review`
- Moderation: `bulk_approve`, `bulk_reject`, `bulk_escalate`
- Photos: `set_primary`, `remove_primary`, `flag_missing_alt`
- Tokens: `resend_verification`, `delete_expired`, `cleanup_old_tokens`
- Lists: `publish_lists`, `unpublish_lists`, `move_up`, `move_down`
- **Export Functionality**
- CSV export for all major models
- JSON export for all major models
- Audit trail export for state logs
- SEO redirect export for slug history
- **Enhanced Display Methods**
- Color-coded status badges for all status fields
- Clickable links to related objects in list views
- Rating displays with star formatting
- Thumbnail previews for images
- Profile completeness indicators
- Moderation status indicators
- **Comprehensive Fieldsets**
- Standardized fieldset organization across all admins
- Collapsible sections for advanced/metadata fields
- Descriptive help text for all fieldsets
- **Documentation**
- `docs/admin/base_classes.md` - Base admin architecture documentation
- `docs/admin/CHANGELOG.md` - This changelog
- **Test Coverage**
- `apps/core/tests/test_admin.py` - Tests for base classes and mixins
- `apps/parks/tests/test_admin.py` - Tests for parks admin
- `apps/rides/tests/test_admin.py` - Tests for rides admin
- `apps/accounts/tests/test_admin.py` - Tests for accounts admin
- `apps/moderation/tests/test_admin.py` - Tests for moderation admin
### Changed
- **Parks Admin** (`apps/parks/admin.py`)
- Optimized all querysets with select_related/prefetch_related
- Added ride count and average rating annotations
- Enhanced list_display with clickable links
- Added autocomplete_fields for ForeignKeys
- Standardized fieldsets with descriptions
- **Rides Admin** (`apps/rides/admin.py`)
- Optimized all querysets with select_related/prefetch_related
- Added review count and average rating annotations
- Enhanced category and status badges with color coding
- Added FSM-aware status change actions
- Maintained read-only status for rankings
- **Accounts Admin** (`apps/accounts/admin.py`)
- Optimized user queryset with profile select_related
- Added total credits display with breakdown tooltip
- Enhanced user status badge display
- Added profile completeness indicator
- Added social media presence indicator
- **Core Admin** (`apps/core/admin.py`)
- Enhanced SlugHistory with content type display
- Added admin URL linking for related objects
- Added SEO export functionality
- **Media Admin** (`shared/media/admin.py`)
- Enhanced thumbnail preview with lazy loading
- Added alt text validation warnings
- Added primary photo management actions
- **Moderation Admin** (`apps/moderation/admin.py`)
- Enhanced dashboard with pending counts
- Added changes preview formatting
- Enhanced state log display with badges
- Added audit trail export
### Fixed
- N+1 query issues in all admin list views
- Inconsistent permission handling across admins
- Missing help text on admin fields
- Content object link errors when objects are deleted
### Removed
- Duplicate admin file at `apps/accounts/admin.py` (root level)
- Redundant code patterns replaced by mixins
### Performance Improvements
- **Query Optimization**
- Parks list view: Reduced from ~50+ queries to <10 queries
- Rides list view: Reduced from ~100+ queries to <15 queries
- User list view: Reduced from ~30+ queries to <10 queries
- Moderation views: Reduced from ~40+ queries to <12 queries
- **Page Settings**
- Consistent pagination at 50 items per page
- Disabled full result count for large datasets
- Added date hierarchy for time-based filtering
### Security Enhancements
- Standardized read-only permissions for auto-generated data
- Superuser-only delete permissions for audit logs
- Self-protection in user ban/deactivate actions
- FSM validation in status change actions
### Developer Experience
- Comprehensive docstrings on all admin classes
- Consistent patterns across all apps
- Reusable mixins reduce code duplication
- Clear fieldset descriptions guide admin usage

180
docs/admin/base_classes.md Normal file
View File

@@ -0,0 +1,180 @@
# Admin Base Classes and Mixins
This document describes the base admin classes and mixins available for building standardized Django admin interfaces in ThrillWiki.
## Overview
The admin infrastructure provides reusable components that ensure consistency, optimize performance, and reduce code duplication across all admin interfaces.
## Base Classes
### BaseModelAdmin
The foundational admin class that all model admins should inherit from.
```python
from apps.core.admin import BaseModelAdmin
class MyModelAdmin(BaseModelAdmin):
list_display = ['name', 'status', 'created_at']
```
**Features:**
- Consistent pagination (50 items per page)
- Optimized result count behavior
- Standard empty value display
- Save buttons at top of forms
- Filter preservation after saves
## Mixins
### QueryOptimizationMixin
Provides automatic query optimization to prevent N+1 queries.
```python
from apps.core.admin import QueryOptimizationMixin, BaseModelAdmin
class RideAdmin(QueryOptimizationMixin, BaseModelAdmin):
list_display = ['name', 'park', 'manufacturer']
list_select_related = ['park', 'manufacturer']
list_prefetch_related = ['reviews', 'photos']
```
**Attributes:**
- `list_select_related`: List of ForeignKey fields to select
- `list_prefetch_related`: List of related fields to prefetch
### ReadOnlyAdminMixin
Disables add, change, and delete permissions for auto-generated data.
```python
from apps.core.admin import ReadOnlyAdminMixin, BaseModelAdmin
class RankingAdmin(ReadOnlyAdminMixin, BaseModelAdmin):
list_display = ['ride', 'rank', 'calculated_at']
```
**Use cases:**
- Rankings and leaderboards
- Audit logs and history
- Calculated statistics
- State transition logs
### TimestampFieldsMixin
Provides standard handling for `created_at` and `updated_at` fields.
```python
from apps.core.admin import TimestampFieldsMixin, BaseModelAdmin
class MyModelAdmin(TimestampFieldsMixin, BaseModelAdmin):
fieldsets = [
('Basic Info', {'fields': ['name', 'description']}),
] + TimestampFieldsMixin.get_timestamp_fieldset()
```
**Features:**
- Automatically adds timestamp fields to readonly_fields
- Provides a collapsible fieldset for metadata
### SlugFieldMixin
Configures automatic slug population from name field.
```python
from apps.core.admin import SlugFieldMixin, BaseModelAdmin
class ParkAdmin(SlugFieldMixin, BaseModelAdmin):
slug_source_field = 'name' # Optional, defaults to 'name'
```
### ExportActionMixin
Adds CSV and JSON export functionality.
```python
from apps.core.admin import ExportActionMixin, BaseModelAdmin
class ParkAdmin(ExportActionMixin, BaseModelAdmin):
export_fields = ['id', 'name', 'status', 'created_at']
export_filename_prefix = 'parks'
```
**Actions added:**
- Export selected to CSV
- Export selected to JSON
### BulkStatusChangeMixin
Provides bulk status change actions.
```python
from apps.core.admin import BulkStatusChangeMixin, BaseModelAdmin
class RideAdmin(BulkStatusChangeMixin, BaseModelAdmin):
status_field = 'status'
status_choices = [
('active', 'Activate'),
('inactive', 'Deactivate'),
]
```
### ModerationMixin
Adds moderation actions for user-generated content.
```python
from apps.core.admin import ModerationMixin, BaseModelAdmin
class ReviewAdmin(ModerationMixin, BaseModelAdmin):
moderation_status_field = 'moderation_status'
moderated_by_field = 'moderated_by'
moderated_at_field = 'moderated_at'
```
**Actions added:**
- Approve selected items
- Reject selected items
## Combining Mixins
Mixins can be combined to create feature-rich admin classes:
```python
from apps.core.admin import (
BaseModelAdmin,
QueryOptimizationMixin,
ExportActionMixin,
TimestampFieldsMixin,
SlugFieldMixin,
)
class ParkAdmin(
QueryOptimizationMixin,
ExportActionMixin,
TimestampFieldsMixin,
SlugFieldMixin,
BaseModelAdmin
):
list_display = ['name', 'operator', 'status', 'created_at']
list_select_related = ['operator', 'location']
list_prefetch_related = ['areas', 'rides']
export_fields = ['id', 'name', 'slug', 'status']
```
## Best Practices
1. **Always use BaseModelAdmin** as the final parent class
2. **List mixins before BaseModelAdmin** in inheritance order
3. **Define list_select_related** for all ForeignKey fields in list_display
4. **Use prefetch_related** for reverse relations and M2M fields
5. **Test query counts** using Django Debug Toolbar
6. **Add export_fields** explicitly for control over exported data
## Performance Targets
- List views: < 10 queries
- Change views: < 15 queries
- Page load time: < 500ms for 100 records

View File

@@ -0,0 +1,267 @@
# Admin Best Practices
This guide outlines best practices for developing and maintaining Django admin interfaces in ThrillWiki.
## Query Optimization
### Always Use select_related for ForeignKeys
```python
class RideAdmin(BaseModelAdmin):
list_display = ['name', 'park', 'manufacturer']
list_select_related = ['park', 'manufacturer'] # Prevents N+1 queries
```
### Use prefetch_related for Reverse Relations
```python
class ParkAdmin(BaseModelAdmin):
list_display = ['name', 'ride_count']
list_prefetch_related = ['rides'] # For efficient count queries
```
### Use Annotations for Calculated Fields
```python
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.annotate(
_ride_count=Count('rides', distinct=True),
_avg_rating=Avg('reviews__rating'),
)
```
## Display Methods
### Always Use format_html for Safety
```python
@admin.display(description="Status")
def status_badge(self, obj):
return format_html(
'<span style="color: {};">{}</span>',
'green' if obj.is_active else 'red',
obj.get_status_display(),
)
```
### Handle Missing Data Gracefully
```python
@admin.display(description="Park")
def park_link(self, obj):
if obj.park:
url = reverse("admin:parks_park_change", args=[obj.park.pk])
return format_html('<a href="{}">{}</a>', url, obj.park.name)
return "-" # Or format_html for styled "N/A"
```
## Fieldsets Organization
### Standard Fieldset Structure
1. Basic Information - Name, slug, description
2. Relationships - ForeignKeys, ManyToMany
3. Status & Dates - Status fields, timestamps
4. Specifications - Technical details (collapsed)
5. Media - Images, photos (collapsed)
6. Metadata - created_at, updated_at (collapsed)
### Include Descriptions
```python
fieldsets = (
(
"Basic Information",
{
"fields": ("name", "slug", "description"),
"description": "Core identification for this record.",
},
),
)
```
## Custom Actions
### Use Proper Action Decorator
```python
@admin.action(description="Approve selected items")
def bulk_approve(self, request, queryset):
count = queryset.update(status='approved')
self.message_user(request, f"Approved {count} items.")
```
### Handle Errors Gracefully
```python
@admin.action(description="Process selected items")
def process_items(self, request, queryset):
processed = 0
errors = 0
for item in queryset:
try:
item.process()
processed += 1
except Exception as e:
errors += 1
self.message_user(
request,
f"Error processing {item}: {e}",
level=messages.ERROR,
)
if processed:
self.message_user(request, f"Processed {processed} items.")
```
### Protect Against Dangerous Operations
```python
@admin.action(description="Ban selected users")
def ban_users(self, request, queryset):
# Prevent banning self
queryset = queryset.exclude(pk=request.user.pk)
# Prevent banning superusers
queryset = queryset.exclude(is_superuser=True)
updated = queryset.update(is_banned=True)
self.message_user(request, f"Banned {updated} users.")
```
## Permissions
### Read-Only for Auto-Generated Data
```python
class RankingAdmin(ReadOnlyAdminMixin, BaseModelAdmin):
"""Rankings are calculated automatically - no manual editing."""
pass
```
### Field-Level Permissions
```python
def get_readonly_fields(self, request, obj=None):
readonly = list(super().get_readonly_fields(request, obj))
if not request.user.is_superuser:
readonly.extend(['sensitive_field', 'admin_notes'])
return readonly
```
### Object-Level Permissions
```python
def has_change_permission(self, request, obj=None):
if obj is None:
return super().has_change_permission(request)
# Only allow editing own content or moderator access
if obj.user == request.user:
return True
return request.user.has_perm('app.moderate_content')
```
## Performance Tips
### Disable Full Result Count
```python
class MyAdmin(BaseModelAdmin):
show_full_result_count = False # Faster for large datasets
```
### Use Pagination
```python
class MyAdmin(BaseModelAdmin):
list_per_page = 50 # Reasonable page size
```
### Limit Inline Items
```python
class ItemInline(admin.TabularInline):
model = Item
extra = 1 # Only show 1 empty form
max_num = 20 # Limit total items
```
## Testing
### Test Query Counts
```python
def test_list_view_query_count(self):
with self.assertNumQueries(10): # Set target
response = self.client.get('/admin/app/model/')
```
### Test Permissions
```python
def test_readonly_permissions(self):
request = self.factory.get('/admin/')
request.user = User(is_superuser=False)
assert self.admin.has_add_permission(request) is False
assert self.admin.has_change_permission(request) is False
```
### Test Custom Actions
```python
def test_bulk_approve_action(self):
items = [create_item(status='pending') for _ in range(5)]
queryset = Item.objects.filter(pk__in=[i.pk for i in items])
self.admin.bulk_approve(self.request, queryset)
for item in items:
item.refresh_from_db()
assert item.status == 'approved'
```
## Common Pitfalls
### Avoid N+1 Queries
```python
# BAD: Creates N+1 queries
@admin.display()
def related_count(self, obj):
return obj.related_set.count()
# GOOD: Use annotation
def get_queryset(self, request):
return super().get_queryset(request).annotate(
_related_count=Count('related_set')
)
@admin.display()
def related_count(self, obj):
return obj._related_count
```
### Don't Forget Error Handling
```python
# BAD: May raise AttributeError
@admin.display()
def user_name(self, obj):
return obj.user.username
# GOOD: Handle missing relations
@admin.display()
def user_name(self, obj):
return obj.user.username if obj.user else "-"
```
### Use Autocomplete for Large Relations
```python
# BAD: Loads all options
class MyAdmin(BaseModelAdmin):
raw_id_fields = ['park'] # Works but poor UX
# GOOD: Search with autocomplete
class MyAdmin(BaseModelAdmin):
autocomplete_fields = ['park']
```

128
docs/admin/overview.md Normal file
View File

@@ -0,0 +1,128 @@
# Admin Interface Overview
This document provides an overview of the Django admin interface architecture for ThrillWiki.
## Architecture
The admin interface is built on a layered architecture:
```
┌─────────────────────────────────────────────────────┐
│ App-Specific Admins │
│ (ParkAdmin, RideAdmin, UserAdmin, etc.) │
├─────────────────────────────────────────────────────┤
│ Mixins │
│ (QueryOptimizationMixin, ExportActionMixin, etc.) │
├─────────────────────────────────────────────────────┤
│ BaseModelAdmin │
│ (Standard settings, base functionality) │
├─────────────────────────────────────────────────────┤
│ django.contrib.admin │
└─────────────────────────────────────────────────────┘
```
## Key Components
### Base Classes (`apps/core/admin/`)
- **BaseModelAdmin**: Standard settings (pagination, display, ordering)
- **Mixins**: Reusable functionality (query optimization, export, permissions)
### Admin Sites
- **Default Admin Site**: Full admin access at `/admin/`
- **Moderation Admin Site**: Dedicated moderation interface at `/moderation/`
## Performance Targets
| View Type | Query Target | Load Time |
|-----------|--------------|-----------|
| List View | < 15 queries | < 500ms |
| Change View | < 20 queries | < 500ms |
| Bulk Actions | < 2 seconds | per 100 records |
## Quick Reference
### Adding a New Admin
```python
from apps.core.admin import (
BaseModelAdmin,
QueryOptimizationMixin,
ExportActionMixin,
)
class MyModelAdmin(
QueryOptimizationMixin,
ExportActionMixin,
BaseModelAdmin,
):
list_display = ['name', 'related_obj', 'status', 'created_at']
list_select_related = ['related_obj']
list_prefetch_related = ['many_to_many_field']
export_fields = ['id', 'name', 'status']
export_filename_prefix = 'my_model'
```
### Read-Only Admin for Auto-Generated Data
```python
from apps.core.admin import ReadOnlyAdminMixin, BaseModelAdmin
class RankingAdmin(ReadOnlyAdminMixin, BaseModelAdmin):
list_display = ['ride', 'rank', 'calculated_at']
```
### Admin with Moderation Actions
```python
from apps.core.admin import ModerationMixin, BaseModelAdmin
class ReviewAdmin(ModerationMixin, BaseModelAdmin):
moderation_status_field = 'status'
```
## Files Structure
```
backend/
├── apps/
│ ├── core/
│ │ ├── admin/
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ └── mixins.py
│ │ └── admin.py
│ ├── parks/
│ │ └── admin.py
│ ├── rides/
│ │ └── admin.py
│ ├── accounts/
│ │ └── admin.py
│ └── moderation/
│ └── admin.py
└── shared/
└── media/
└── admin.py
docs/admin/
├── overview.md
├── base_classes.md
└── CHANGELOG.md
```
## Best Practices
1. **Always use BaseModelAdmin** as the final parent class
2. **List mixins before BaseModelAdmin** in inheritance order
3. **Define list_select_related** for all ForeignKeys in list_display
4. **Use prefetch_related** for reverse relations and M2M fields
5. **Add export_fields** explicitly for control over exported data
6. **Include descriptive help text** in fieldset descriptions
7. **Test query counts** using Django Debug Toolbar
## Related Documentation
- [Base Classes and Mixins](base_classes.md)
- [Changelog](CHANGELOG.md)