Files
thrillwiki_django_no_react/memory-bank/projects/django-to-symfony-conversion/03-view-controller-analysis.md

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

  1. Route Definition - Convert Django URLs to Symfony routes
  2. Controller Classes - Map views to controller methods
  3. Security Configuration - Set up Symfony Security for authentication
  4. Form Types - Convert Django forms to Symfony form types
  5. Event System - Replace Django middleware with Symfony event listeners
  6. Template Migration - Convert Django templates to Twig
  7. 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