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.
 
 

155 lines
5.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 = [
# 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')),
# Online Learning Management System (LMS)
path('lms/', include('apps.lms.urls')),
# Official Inquiries & Support Ticketing
path('tickets/', include('apps.tickets.urls')),
# Dynamic Custom Forms & Surveys
path('forms/', include('apps.dynamic_forms.urls')),
# Donations & Financial Campaigns
path('donations/', include('apps.donations.urls')),
# Field Representative Diplomacy Reports
path('diplomacy/', include('apps.diplomacy.urls')),
# Executive Analytics Dashboard
path('analytics/', include('apps.analytics.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}),
]