mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-24 21:51:09 -05:00
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:
138
docs/admin/CHANGELOG.md
Normal file
138
docs/admin/CHANGELOG.md
Normal 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
180
docs/admin/base_classes.md
Normal 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
|
||||
267
docs/admin/best_practices.md
Normal file
267
docs/admin/best_practices.md
Normal 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
128
docs/admin/overview.md
Normal 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)
|
||||
Reference in New Issue
Block a user