# 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) ```python # 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) ```python # 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 ```python # 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 ```python 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 ```python 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 ```python 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 ```python # 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 ```python # parks/urls.py urlpatterns = [ path('', ParkListView.as_view(), name='park-list'), path('/', ParkDetailView.as_view(), name='park-detail'), path('/edit/', ParkEditView.as_view(), name='park-edit'), path('/photos/', ParkPhotoListView.as_view(), name='park-photos'), path('/reviews/', ParkReviewListView.as_view(), name='park-reviews'), path('/rides/', ParkRideListView.as_view(), name='park-rides'), # HTMX endpoints path('/rides/partial/', park_rides_partial, name='park-rides-partial'), path('/photos/partial/', park_photos_partial, name='park-photos-partial'), ] ``` #### Search URLs ```python # 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) ```python # 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 ```python # 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 ```python # 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python # 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 ```python 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 ```python 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 ```python # 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}` with requirements | | `include()` | Route prefixes | | `name='route-name'` | `name: 'route_name'` | ### Key Conversion Considerations #### 1. HTMX Integration ```yaml # 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 ```php // Symfony Security approach #[IsGranted('ROLE_MODERATOR')] class ModerationController extends AbstractController { #[Route('/moderation/dashboard')] public function dashboard(): Response { // Moderation logic } } ``` #### 3. Form Handling ```php // 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 ```php // 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 ```html {% extends 'base.html' %} {% load parks_tags %} {% block content %}
Loading rides...
{% if user.is_authenticated and can_edit %} Edit Park {% endif %} {% endblock %} ``` ### Symfony Twig Equivalent ```twig {# Twig template with HTMX #} {% extends 'base.html.twig' %} {% block content %}
Loading rides...
{% if is_granted('ROLE_USER') and can_edit %} Edit Park {% 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