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.
137 lines
4.7 KiB
137 lines
4.7 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 = [
|
|
# Auth & Identity endpoints
|
|
path('auth/', include('apps.account.urls_auth')),
|
|
|
|
# Institution Profiles
|
|
path('profiles/', include('apps.profiles.urls')),
|
|
|
|
# Smart Geo-Atlas & Spatial Map
|
|
path('map/', include('apps.geo_map.urls')),
|
|
|
|
# International Content Management System (CMS)
|
|
path('cms/', include('apps.cms.urls')),
|
|
|
|
# Collaborative Projects & Kanban
|
|
path('projects/', include('apps.projects.urls')),
|
|
|
|
# Real-Time Messaging & Chat
|
|
path('chat/', include('apps.chat.urls')),
|
|
|
|
# Events & Programs Calendar
|
|
path('events/', include('apps.events.urls')),
|
|
|
|
# Inter-Center Video Meetings & Scheduling
|
|
path('meetings/', include('apps.meetings.urls')),
|
|
|
|
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}),
|
|
]
|