HH/docs/SURVEY_404_ERROR_FIX.md
2026-01-24 15:27:30 +03:00

6.2 KiB

Survey 404 Error - URL Format Issue

Problem Description

When trying to access a survey URL, you may encounter a 404 (Page Not Found) error:

Page not found (404)
Request Method:	GET
Request URL:	http://localhost:8000/surveys/H8d9tlVs0BgeAp1XA4NczXoiCcqAaN0r_lc0Eb63U1Y/

Root Cause

The survey URL is missing the required /s/ prefix in the path. The correct URL format includes /s/ between /surveys/ and the access token.

Incorrect URL (404 Error)

http://localhost:8000/surveys/H8d9tlVs0BgeAp1XA4NczXoiCcqAaN0r_lc0Eb63U1Y/

Correct URL

http://localhost:8000/surveys/s/H8d9tlVs0BgeAp1XA4NczXoiCcqAaN0r_lc0Eb63U1Y/

Why the /s/ Prefix is Required

The /s/ prefix is necessary to avoid URL routing conflicts with other survey-related paths:

  1. UI Views (authenticated):

    • /surveys/instances/ - Survey instance list
    • /surveys/instances/<id>/ - Instance detail
    • /surveys/templates/ - Template list
    • /surveys/templates/<id>/ - Template detail
  2. API Endpoints:

    • /surveys/api/templates/ - Template API
    • /surveys/api/instances/ - Instance API
    • /surveys/public/<token>/ - Public API
  3. Public Survey Forms (token-based, no auth):

    • /surveys/s/<token>/ - Survey form
    • /surveys/s/<token>/thank-you/ - Thank you page

Without the /s/ prefix, a catch-all pattern like <str:token>/ would conflict with reserved words like "instances", "templates", "public", etc., causing routing conflicts.

Solution

Use the correct URL format when accessing surveys:

Method 1: Use Generated URLs

The survey model's get_survey_url() method automatically generates the correct URL format:

survey = SurveyInstance.objects.get(id=...)
survey_url = survey.get_survey_url()
# Returns: /surveys/s/H8d9tlVs0BgeAp1XA4NczXoiCcqAaN0r_lc0Eb63U1Y/

Method 2: Manual URL Construction

When constructing URLs manually, always include the /s/ prefix:

from django.urls import reverse
from apps.surveys.models import SurveyInstance

survey = SurveyInstance.objects.get(id=...)
survey_url = reverse('surveys:survey_form', kwargs={'token': survey.access_token})
# Returns: /surveys/s/H8d9tlVs0BgeAp1XA4NczXoiCcqAaN0r_lc0Eb63U1Y/

Method 3: Email Templates

When sending survey links via email, use the survey_url field from the serializer:

# In email template
{{ survey.survey_url }}
# Displays: http://localhost:8000/surveys/s/H8d9tlVs0BgeAp1XA4NczXoiCcqAaN0r_lc0Eb63U1Y/

URL Structure Overview

/surveys/
├── instances/              # UI: Instance list
├── instances/<uuid>/       # UI: Instance detail
├── templates/              # UI: Template list
├── templates/<uuid>/       # UI: Template detail
├── public/<token>/         # API: Public survey data
├── api/                   # API: Authenticated endpoints
│   ├── templates/
│   ├── questions/
│   ├── instances/
│   └── responses/
├── s/<token>/             # Public: Survey form (no auth)
└── s/<token>/thank-you/   # Public: Thank you page

Common Mistakes

Wrong

# Missing /s/ prefix
url = f"/surveys/{survey.access_token}/"
# Wrong reverse name
url = reverse('surveys:survey_form_direct', kwargs={'token': survey.access_token})

Correct

# Including /s/ prefix
url = survey.get_survey_url()
# Using correct reverse name
url = reverse('surveys:survey_form', kwargs={'token': survey.access_token})

Testing Survey Access

Check if Survey Exists

from apps.surveys.models import SurveyInstance
from django.utils import timezone

try:
    survey = SurveyInstance.objects.get(
        access_token="H8d9tlVs0BgeAp1XA4NczXoiCcqAaN0r_lc0Eb63U1Y",
        status__in=['pending', 'sent', 'in_progress'],
        token_expires_at__gt=timezone.now()
    )
    print(f"Survey found: {survey.survey_template.name}")
    print(f"URL: {survey.get_survey_url()}")
except SurveyInstance.DoesNotExist:
    print("Survey not found, expired, or invalid token")

Verify Survey URL

from django.test import Client

client = Client()
response = client.get('/surveys/s/H8d9tlVs0BgeAp1XA4NczXoiCcqAaN0r_lc0Eb63U1Y/')
print(f"Status: {response.status_code}")  # Should be 200
if response.status_code == 200:
    print("Survey accessible")
elif response.status_code == 404:
    print("Survey not found or expired")

URL Configuration

The survey URLs are defined in apps/surveys/urls.py:

urlpatterns = [
    # Public survey pages (no auth required)
    path('invalid/', public_views.invalid_token, name='invalid_token'),
    
    # UI Views (authenticated)
    path('instances/', ui_views.survey_instance_list, name='instance_list'),
    path('templates/', ui_views.survey_template_list, name='template_list'),
    
    # Public API endpoints
    path('public/<str:token>/', PublicSurveyViewSet.as_view({'get': 'retrieve'}), name='public-survey'),
    
    # Authenticated API endpoints
    path('', include(router.urls)),
    
    # Public survey token access (requires /s/ prefix)
    path('s/<str:token>/', public_views.survey_form, name='survey_form'),
    path('s/<str:token>/thank-you/', public_views.thank_you, name='thank_you'),
]

Security Considerations

The /s/ prefix also serves as a security measure:

  1. Separation of Concerns: Public survey forms are clearly separated from administrative and API endpoints
  2. URL Clarity: The /s/ prefix makes it immediately clear that this is a public, token-based survey access
  3. Prevents Ambiguity: Avoids confusion between different types of survey-related URLs

Summary

Key Points:

  • Survey URLs MUST include the /s/ prefix
  • Format: /surveys/s/<access_token>/
  • Use survey.get_survey_url() method to generate correct URLs
  • The /s/ prefix prevents URL routing conflicts
  • Survey access requires valid, non-expired token and appropriate status

Quick Reference:

  • Survey Form: /surveys/s/<token>/
  • Thank You: /surveys/s/<token>/thank-you/
  • Invalid Token: /surveys/invalid/

For more information, see:

  • apps/surveys/urls.py - URL patterns
  • apps/surveys/models.py - SurveyInstance.get_survey_url() method
  • apps/surveys/public_views.py - Public survey views