Fix admin login 500 when clients omit the trailing slash.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Shayan Azadi
2026-08-17 18:49:15 +03:30
parent 26b83dabac
commit e8423facaa
5 changed files with 66 additions and 9 deletions

View File

@@ -3,6 +3,7 @@ from datetime import timedelta
from django.contrib.auth import get_user_model
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import override_settings
from django.utils import timezone
from PIL import Image
from rest_framework import status
@@ -343,6 +344,26 @@ class AdminLoginAPITests(APITestCase):
self.assertIn('token', response.data)
self.assertEqual(response.data['user']['username'], self.admin_username)
def test_admin_login_without_trailing_slash(self):
"""Axios/fetch omit the slash; this used to 500 with APPEND_SLASH + DEBUG."""
response = self.client.post(
'/api/admin/login',
{'username': self.admin_username, 'password': self.admin_password},
format='json',
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('token', response.data)
def test_admin_login_without_trailing_slash_debug(self):
with override_settings(DEBUG=True):
response = self.client.post(
'/api/admin/login',
{'username': self.admin_username, 'password': self.admin_password},
format='json',
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('token', response.data)
def test_admin_login_wrong_password(self):
response = self.client.post(
'/api/admin/login/',

View File

@@ -1,5 +1,6 @@
from django.urls import path, include
from django.urls import include, re_path
from rest_framework.routers import DefaultRouter
from .views import (
ContactUsViewSet,
CompositionViewSet,
@@ -9,14 +10,20 @@ from .views import (
admin_me,
)
router = DefaultRouter()
class OptionalSlashRouter(DefaultRouter):
"""Match /api/resource and /api/resource/ so POST clients do not hit APPEND_SLASH."""
trailing_slash = '/?'
router = OptionalSlashRouter()
router.register(r'contact-us', ContactUsViewSet, basename='contact-us')
router.register(r'compositions', CompositionViewSet, basename='composition')
router.register(r'campaigns', CampaignViewSet, basename='campaign')
urlpatterns = [
path('admin/login/', admin_login, name='admin-login'),
path('admin/logout/', admin_logout, name='admin-logout'),
path('admin/me/', admin_me, name='admin-me'),
path('', include(router.urls)),
re_path(r'^admin/login/?$', admin_login, name='admin-login'),
re_path(r'^admin/logout/?$', admin_logout, name='admin-logout'),
re_path(r'^admin/me/?$', admin_me, name='admin-me'),
re_path(r'^', include(router.urls)),
]

View File

@@ -1,5 +1,10 @@
from rest_framework import viewsets, status
from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.decorators import (
action,
api_view,
authentication_classes,
permission_classes,
)
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
@@ -17,6 +22,7 @@ from .serializers import (
@api_view(['POST'])
@authentication_classes([])
@permission_classes([AllowAny])
def admin_login(request):
"""