mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 14:31:08 -05:00
16 KiB
16 KiB
Django Views & URL Analysis - Controller Pattern Mapping
Date: January 7, 2025
Analyst: Roo (Architect Mode)
Purpose: Django view/URL pattern analysis for Symfony controller conversion
Status: Complete view layer analysis for conversion planning
Overview
This document analyzes Django view patterns, URL routing, and controller logic to facilitate conversion to Symfony's controller and routing system. Focus on HTMX integration, authentication patterns, and RESTful designs.
Django View Architecture Analysis
View Types and Patterns
1. Function-Based Views (FBV)
# Example: Search functionality
def search_view(request):
query = request.GET.get('q', '')
if request.htmx:
# Return HTMX partial
return render(request, 'search/partials/results.html', {
'results': search_results,
'query': query
})
# Return full page
return render(request, 'search/index.html', {
'results': search_results,
'query': query
})
2. Class-Based Views (CBV)
# Example: Park detail view
class ParkDetailView(DetailView):
model = Park
template_name = 'parks/detail.html'
context_object_name = 'park'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['rides'] = self.object.rides.filter(status='OPERATING')
context['photos'] = self.object.photos.filter(approval_status='APPROVED')
context['reviews'] = self.object.reviews.filter(is_approved=True)[:5]
return context
3. HTMX-Enhanced Views
# Example: Autocomplete endpoint
def park_autocomplete(request):
query = request.GET.get('q', '')
if not request.htmx:
return JsonResponse({'error': 'HTMX required'}, status=400)
parks = Park.objects.filter(
name__icontains=query
).select_related('operator')[:10]
return render(request, 'parks/partials/autocomplete.html', {
'parks': parks,
'query': query
})
Authentication & Authorization Patterns
1. Decorator-Based Protection
from django.contrib.auth.decorators import login_required, user_passes_test
@login_required
def submit_review(request, park_id):
# Review submission logic
pass
@user_passes_test(lambda u: u.role in ['MODERATOR', 'ADMIN'])
def moderation_dashboard(request):
# Moderation interface
pass
2. Permission Checks in Views
class ParkEditView(UpdateView):
model = Park
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return redirect('login')
if request.user.role not in ['MODERATOR', 'ADMIN']:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
3. Context-Based Permissions
def park_detail(request, slug):
park = get_object_or_404(Park, slug=slug)
context = {
'park': park,
'can_edit': request.user.is_authenticated and
request.user.role in ['MODERATOR', 'ADMIN'],
'can_review': request.user.is_authenticated,
'can_upload': request.user.is_authenticated,
}
return render(request, 'parks/detail.html', context)
URL Routing Analysis
Main URL Structure
# thrillwiki/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('', HomeView.as_view(), name='home'),
path('parks/', include('parks.urls')),
path('rides/', include('rides.urls')),
path('operators/', include('operators.urls')),
path('manufacturers/', include('manufacturers.urls')),
path('designers/', include('designers.urls')),
path('property-owners/', include('property_owners.urls')),
path('search/', include('search.urls')),
path('accounts/', include('accounts.urls')),
path('ac/', include('autocomplete.urls')), # HTMX autocomplete
path('moderation/', include('moderation.urls')),
path('history/', include('history.urls')),
path('photos/', include('media.urls')),
]
App-Specific URL Patterns
Parks URLs
# parks/urls.py
urlpatterns = [
path('', ParkListView.as_view(), name='park-list'),
path('<slug:slug>/', ParkDetailView.as_view(), name='park-detail'),
path('<slug:slug>/edit/', ParkEditView.as_view(), name='park-edit'),
path('<slug:slug>/photos/', ParkPhotoListView.as_view(), name='park-photos'),
path('<slug:slug>/reviews/', ParkReviewListView.as_view(), name='park-reviews'),
path('<slug:slug>/rides/', ParkRideListView.as_view(), name='park-rides'),
# HTMX endpoints
path('<slug:slug>/rides/partial/', park_rides_partial, name='park-rides-partial'),
path('<slug:slug>/photos/partial/', park_photos_partial, name='park-photos-partial'),
]
Search URLs
# search/urls.py
urlpatterns = [
path('', SearchView.as_view(), name='search'),
path('suggestions/', search_suggestions, name='search-suggestions'),
path('parks/', park_search, name='park-search'),
path('rides/', ride_search, name='ride-search'),
]
Autocomplete URLs (HTMX)
# autocomplete/urls.py
urlpatterns = [
path('parks/', park_autocomplete, name='ac-parks'),
path('rides/', ride_autocomplete, name='ac-rides'),
path('operators/', operator_autocomplete, name='ac-operators'),
path('manufacturers/', manufacturer_autocomplete, name='ac-manufacturers'),
path('designers/', designer_autocomplete, name='ac-designers'),
]
SEO and Slug Management
Historical Slug Support
# Custom middleware for slug redirects
class SlugRedirectMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if response.status_code == 404:
# Check for historical slugs
old_slug = request.path.split('/')[-2] # Extract slug from path
# Look up in slug history
try:
slug_history = SlugHistory.objects.get(old_slug=old_slug)
new_url = request.path.replace(old_slug, slug_history.current_slug)
return redirect(new_url, permanent=True)
except SlugHistory.DoesNotExist:
pass
return response
Form Handling Patterns
Django Form Integration
1. Model Forms
# forms.py
class ParkForm(forms.ModelForm):
class Meta:
model = Park
fields = ['name', 'description', 'website', 'operator', 'property_owner']
widgets = {
'description': forms.Textarea(attrs={'rows': 4}),
'operator': autocomplete.ModelSelect2(url='ac-operators'),
'property_owner': autocomplete.ModelSelect2(url='ac-property-owners'),
}
def clean_name(self):
name = self.cleaned_data['name']
# Custom validation logic
return name
2. HTMX Form Processing
def park_form_view(request, slug=None):
park = get_object_or_404(Park, slug=slug) if slug else None
if request.method == 'POST':
form = ParkForm(request.POST, instance=park)
if form.is_valid():
park = form.save()
if request.htmx:
# Return updated partial
return render(request, 'parks/partials/park_card.html', {
'park': park
})
return redirect('park-detail', slug=park.slug)
else:
form = ParkForm(instance=park)
template = 'parks/partials/form.html' if request.htmx else 'parks/form.html'
return render(request, template, {'form': form, 'park': park})
3. File Upload Handling
def photo_upload_view(request):
if request.method == 'POST':
form = PhotoUploadForm(request.POST, request.FILES)
if form.is_valid():
photo = form.save(commit=False)
photo.uploaded_by = request.user
# Extract EXIF data
if photo.image:
photo.exif_data = extract_exif_data(photo.image)
photo.save()
if request.htmx:
return render(request, 'media/partials/photo_preview.html', {
'photo': photo
})
return redirect('photo-detail', pk=photo.pk)
return render(request, 'media/upload.html', {'form': form})
API Patterns and JSON Responses
HTMX JSON Responses
def search_api(request):
query = request.GET.get('q', '')
results = {
'parks': list(Park.objects.filter(name__icontains=query).values('name', 'slug')[:5]),
'rides': list(Ride.objects.filter(name__icontains=query).values('name', 'slug')[:5]),
}
return JsonResponse(results)
Error Handling
def api_view_with_error_handling(request):
try:
# View logic
return JsonResponse({'success': True, 'data': data})
except ValidationError as e:
return JsonResponse({'success': False, 'errors': e.message_dict}, status=400)
except PermissionDenied:
return JsonResponse({'success': False, 'error': 'Permission denied'}, status=403)
except Exception as e:
logger.exception('Unexpected error in API view')
return JsonResponse({'success': False, 'error': 'Internal error'}, status=500)
Middleware Analysis
Custom Middleware Stack
# settings.py
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'core.middleware.PgHistoryContextMiddleware', # Custom history context
'allauth.account.middleware.AccountMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
'django_htmx.middleware.HtmxMiddleware', # HTMX support
'analytics.middleware.PageViewMiddleware', # Custom analytics
]
Custom Middleware Examples
History Context Middleware
class PgHistoryContextMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Set context for history tracking
with pghistory.context(
user=getattr(request, 'user', None),
ip_address=self.get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', '')
):
response = self.get_response(request)
return response
Page View Tracking Middleware
class PageViewMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
# Track page views for successful responses
if response.status_code == 200 and not request.htmx:
self.track_page_view(request)
return response
Context Processors
Custom Context Processors
# moderation/context_processors.py
def moderation_access(request):
"""Add moderation permissions to template context"""
return {
'can_moderate': (
request.user.is_authenticated and
request.user.role in ['MODERATOR', 'ADMIN', 'SUPERUSER']
),
'pending_submissions_count': (
EditSubmission.objects.filter(status='PENDING').count()
if request.user.is_authenticated and request.user.role in ['MODERATOR', 'ADMIN']
else 0
)
}
Conversion Mapping to Symfony
View → Controller Mapping
| Django Pattern | Symfony Equivalent |
|---|---|
| Function-based views | Controller methods |
| Class-based views | Controller classes |
@login_required |
Security annotations |
user_passes_test |
Voter system |
render() |
$this->render() |
JsonResponse |
JsonResponse |
redirect() |
$this->redirectToRoute() |
get_object_or_404 |
Repository + exception |
URL → Route Mapping
| Django Pattern | Symfony Equivalent |
|---|---|
path('', view) |
#[Route('/', name: '')] |
<slug:slug> |
{slug} with requirements |
include() |
Route prefixes |
name='route-name' |
name: 'route_name' |
Key Conversion Considerations
1. HTMX Integration
# Symfony equivalent approach
# Route annotations for HTMX endpoints
#[Route('/parks/{slug}/rides', name: 'park_rides')]
#[Route('/parks/{slug}/rides/partial', name: 'park_rides_partial')]
public function parkRides(Request $request, Park $park): Response
{
$rides = $park->getRides();
if ($request->headers->has('HX-Request')) {
return $this->render('parks/partials/rides.html.twig', [
'rides' => $rides
]);
}
return $this->render('parks/rides.html.twig', [
'park' => $park,
'rides' => $rides
]);
}
2. Authentication & Authorization
// Symfony Security approach
#[IsGranted('ROLE_MODERATOR')]
class ModerationController extends AbstractController
{
#[Route('/moderation/dashboard')]
public function dashboard(): Response
{
// Moderation logic
}
}
3. Form Handling
// Symfony Form component
#[Route('/parks/{slug}/edit', name: 'park_edit')]
public function edit(Request $request, Park $park, EntityManagerInterface $em): Response
{
$form = $this->createForm(ParkType::class, $park);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->flush();
if ($request->headers->has('HX-Request')) {
return $this->render('parks/partials/park_card.html.twig', [
'park' => $park
]);
}
return $this->redirectToRoute('park_detail', ['slug' => $park->getSlug()]);
}
$template = $request->headers->has('HX-Request')
? 'parks/partials/form.html.twig'
: 'parks/form.html.twig';
return $this->render($template, [
'form' => $form->createView(),
'park' => $park
]);
}
4. Middleware → Event Listeners
// Symfony event listener equivalent
class PageViewListener
{
public function onKernelResponse(ResponseEvent $event): void
{
$request = $event->getRequest();
$response = $event->getResponse();
if ($response->getStatusCode() === 200 &&
!$request->headers->has('HX-Request')) {
$this->trackPageView($request);
}
}
}
Template Integration Analysis
Django Template Features
<!-- Django template with HTMX -->
{% extends 'base.html' %}
{% load parks_tags %}
{% block content %}
<div hx-get="{% url 'park-rides-partial' park.slug %}"
hx-trigger="load">
Loading rides...
</div>
{% if user.is_authenticated and can_edit %}
<a href="{% url 'park-edit' park.slug %}"
hx-get="{% url 'park-edit' park.slug %}"
hx-target="#edit-form">Edit Park</a>
{% endif %}
{% endblock %}
Symfony Twig Equivalent
{# Twig template with HTMX #}
{% extends 'base.html.twig' %}
{% block content %}
<div hx-get="{{ path('park_rides_partial', {slug: park.slug}) }}"
hx-trigger="load">
Loading rides...
</div>
{% if is_granted('ROLE_USER') and can_edit %}
<a href="{{ path('park_edit', {slug: park.slug}) }}"
hx-get="{{ path('park_edit', {slug: park.slug}) }}"
hx-target="#edit-form">Edit Park</a>
{% endif %}
{% endblock %}
Next Steps for Controller Conversion
- Route Definition - Convert Django URLs to Symfony routes
- Controller Classes - Map views to controller methods
- Security Configuration - Set up Symfony Security for authentication
- Form Types - Convert Django forms to Symfony form types
- Event System - Replace Django middleware with Symfony event listeners
- Template Migration - Convert Django templates to Twig
- HTMX Integration - Ensure seamless HTMX functionality in Symfony
Status: ✅ COMPLETED - View/controller pattern analysis for Symfony conversion
Next: Template system analysis and frontend architecture conversion planning