diff --git a/backend/common/tests.py b/backend/common/tests.py index a7516ca..fa567d3 100644 --- a/backend/common/tests.py +++ b/backend/common/tests.py @@ -1,7 +1,8 @@ """Régressions des droits admin. Lancer avec : python manage.py test common.""" +from django.conf import settings from django.contrib.auth import get_user_model from django.test import Client, TestCase -from django.urls import reverse +from django.urls import Resolver404, resolve, reverse class AdminApiTests(TestCase): @@ -52,3 +53,13 @@ class AdminApiTests(TestCase): response = self.client.post(reverse("auth-login"), data='{"username":"member", "password":"TestPassword2026!"}', content_type="application/json") self.assertEqual(response.status_code, 403) self.assertNotIn("_auth_user_id", self.client.session) + + +class NoDjangoAdminTests(TestCase): + def test_native_django_admin_is_absent(self): + self.assertNotIn("django.contrib.admin", settings.INSTALLED_APPS) + with self.assertRaises(Resolver404): + resolve("/django-admin/") + + def test_custom_admin_api_still_exists(self): + self.assertEqual(reverse("admin-dashboard"), "/api/admin/dashboard/") diff --git a/backend/core/settings.py b/backend/core/settings.py index 776c35e..479124b 100644 --- a/backend/core/settings.py +++ b/backend/core/settings.py @@ -28,11 +28,10 @@ _csrf_backend = effective_origins( auto_port=config("AUTO_PORT_CSRF", default=True, cast=bool), ) # Ajoute également l'origine du frontend : nécessaire aux requêtes CSRF -# de l'interface (par exemple lorsque Django Admin est exposé via le proxy). +# du tableau de bord React utilisant les sessions Django. CSRF_TRUSTED_ORIGINS = list(dict.fromkeys([*_csrf_backend, *CORS_ALLOWED_ORIGINS])) INSTALLED_APPS = [ - "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", diff --git a/backend/core/urls.py b/backend/core/urls.py index 71a4969..2fd32dd 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -1,13 +1,7 @@ -from django.contrib import admin from django.urls import include, path -admin.site.site_header = "Administration avancée" -admin.site.site_title = "Administration Django" -admin.site.index_title = "Gestion technique" - +# L'administration est une interface React (/admin/) servie par le frontend. +# Le backend n'expose que l'API, y compris les routes administrateur sécurisées. urlpatterns = [ - # /admin/ est le tableau de bord React basé sur shadcn/ui. - # Django Admin reste accessible pour les fonctions avancées. - path("django-admin/", admin.site.urls), path("api/", include("common.urls")), ] diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 4f65a60..dd23168 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -12,13 +12,6 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } - location /django-admin/ { - proxy_pass http://backend:8000; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - location /static/ { proxy_pass http://backend:8000; proxy_set_header Host $http_host; diff --git a/frontend/src/pages/AdminPage.jsx b/frontend/src/pages/AdminPage.jsx index 403883f..44d3b4e 100644 --- a/frontend/src/pages/AdminPage.jsx +++ b/frontend/src/pages/AdminPage.jsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useState } from "react" -import { Activity, ArrowUpRight, Database, LayoutDashboard, LogOut, RefreshCw, Search, ShieldCheck, Users } from "lucide-react" +import { Activity, LayoutDashboard, LogOut, RefreshCw, Search, ShieldCheck, Users } from "lucide-react" import { AppShell } from "@/components/layout/AppShell" import { LoginForm } from "@/components/admin/LoginForm" import { CreateUserDialog } from "@/components/admin/CreateUserDialog" @@ -118,7 +118,6 @@ export default function AdminPage() {
{filtered.length} compte(s) affiché(s) · Les comptes sensibles sont protégés.
-

Administration Django avancée

Groupes, permissions et modèles Django

} diff --git a/frontend/src/pages/HomePage.jsx b/frontend/src/pages/HomePage.jsx index 86fc0b6..a151c4d 100644 --- a/frontend/src/pages/HomePage.jsx +++ b/frontend/src/pages/HomePage.jsx @@ -17,7 +17,6 @@ export default function HomePage() {

Docker Compose, Django REST Framework, React avec shadcn/ui, MySQL 8 et Oxlint. Une architecture modulaire, prête à être personnalisée.

-
diff --git a/frontend/vite.config.js b/frontend/vite.config.js index d3da811..41db78b 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -9,7 +9,7 @@ export default defineConfig(({ mode }) => { const backendPort = process.env.BACKEND_PORT || env.BACKEND_PORT || "1029" const proxyTarget = `http://127.0.0.1:${backendPort}` const proxy = Object.fromEntries( - ["/api", "/django-admin", "/static"].map((path) => [ + ["/api", "/static"].map((path) => [ path, { target: proxyTarget, changeOrigin: true }, ]), diff --git a/scripts/init_env.py b/scripts/init_env.py deleted file mode 100644 index 4aa45bb..0000000 --- a/scripts/init_env.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Complète les secrets manquants du .env, sans écraser les valeurs existantes.""" -from pathlib import Path -import secrets -import stat - -ROOT = Path(__file__).resolve().parents[1] -ENV = ROOT / ".env" -SOURCE = ROOT / ".env.example" - -if not ENV.exists(): - ENV.write_text(SOURCE.read_text(encoding="utf-8"), encoding="utf-8") - -values = ENV.read_text(encoding="utf-8").splitlines(keepends=True) -secrets_to_generate = { - "SECRET_KEY": lambda: secrets.token_urlsafe(64), - "MYSQL_PASSWORD": lambda: secrets.token_urlsafe(24), - "MYSQL_ROOT_PASSWORD": lambda: secrets.token_urlsafe(28), - "ADMIN_PASSWORD": lambda: secrets.token_urlsafe(16), -} -updated = set() -result = [] -for line in values: - if line.lstrip().startswith("#") or "=" not in line: - result.append(line) - continue - key, val = line.rstrip("\n").split("=", 1) - if key in secrets_to_generate and not val.strip(): - line = f"{key}={secrets_to_generate[key]()}\n" - updated.add(key) - result.append(line) -ENV.write_text("".join(result), encoding="utf-8") -if hasattr(ENV, "chmod"): - ENV.chmod(stat.S_IRUSR | stat.S_IWUSR) - -print("Fichier .env prêt.") -if updated: - print("Secrets générés :", ", ".join(sorted(updated))) - print("Le mot de passe admin est dans .env : conserve-le de manière sûre.") -else: - print("Aucune valeur existante modifiée.")