You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

118 lines
4.3 KiB

"""
Main URL Configuration for the Django Backend Starter Template.
"""
import os
import re
import mimetypes
from django.contrib import admin
from django.urls import path, include, re_path
from django.conf import settings
from django.conf.urls.i18n import i18n_patterns
from django.http import StreamingHttpResponse, HttpResponse, Http404
from django.views.static import serve as django_static_serve
from django.views.generic import RedirectView
from drf_spectacular.views import (
SpectacularAPIView,
SpectacularRedocView,
SpectacularSwaggerView,
)
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
TokenVerifyView,
)
from rest_framework import permissions
from utils.admin import project_admin_site, HomeView
# API v1 Patterns
api_v1_patterns = [
# JWT Authentication Endpoints (matching swagger-api-guide.md)
path('auth/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('auth/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('auth/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
path('', include('apps.api.urls')),
path('account/', include('apps.account.urls')),
path('settings/', include('dynamic_preferences.urls')),
]
urlpatterns = [
# Language prefix routing for Admin
path("i18n/", include("django.conf.urls.i18n")),
# REST API Endpoints
path("api/v1/", include(api_v1_patterns)),
# OpenAPI 3.0 & Interactive Documentation (matching swagger-api-guide.md)
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
path("api/schema/swagger-ui/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
path("api/schema/redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
# Backward compatibility redirect
path("swagger/", RedirectView.as_view(url="/api/schema/swagger-ui/", permanent=False)),
]
# Admin URLs wrapped in i18n
urlpatterns += i18n_patterns(
path("admin/", project_admin_site.urls),
prefix_default_language=True,
)
# Static & Media streaming for development mode
if settings.DEBUG:
def ranged_static_serve(request, path, document_root=None, **kwargs):
fullpath = os.path.join(document_root, path)
if not os.path.exists(fullpath) or os.path.isdir(fullpath):
raise Http404("File not found")
range_header = request.META.get('HTTP_RANGE', '').strip()
if not range_header:
return django_static_serve(request, path, document_root=document_root, **kwargs)
size = os.path.getsize(fullpath)
content_type, encoding = mimetypes.guess_type(fullpath)
content_type = content_type or 'application/octet-stream'
match = re.match(r'bytes=(\d+)-(\d*)', range_header)
if not match:
return HttpResponse("Invalid Range Header", status=400)
first_byte, last_byte = match.groups()
first_byte = int(first_byte) if first_byte else 0
last_byte = int(last_byte) if last_byte else size - 1
if first_byte >= size:
return HttpResponse("Requested Range Not Satisfiable", status=416)
if last_byte >= size:
last_byte = size - 1
length = last_byte - first_byte + 1
def file_iterator(file_path, offset, bytes_to_read, chunk_size=8192):
with open(file_path, 'rb') as f:
f.seek(offset)
remaining = bytes_to_read
while remaining > 0:
to_read = min(chunk_size, remaining)
data = f.read(to_read)
if not data:
break
yield data
remaining -= len(data)
response = StreamingHttpResponse(
file_iterator(fullpath, first_byte, length),
status=206,
content_type=content_type
)
response['Content-Range'] = f'bytes {first_byte}-{last_byte}/{size}'
response['Content-Length'] = str(length)
response['Accept-Ranges'] = 'bytes'
return response
urlpatterns += [
re_path(r'^%s(?P<path>.*)$' % re.escape(settings.MEDIA_URL.lstrip('/')), ranged_static_serve, {'document_root': settings.MEDIA_ROOT}),
]