nickel
This commit is contained in:
parent
663268bbe9
commit
63496f1c2a
@ -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/")
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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")),
|
||||
]
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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() {
|
||||
<div className="border-t border-border bg-muted/20 px-6 py-3 text-xs text-muted-foreground">{filtered.length} compte(s) affiché(s) · Les comptes sensibles sont protégés.</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-dashed shadow-none"><CardContent className="flex flex-wrap items-center justify-between gap-4 py-5"><div className="flex items-center gap-3"><Database className="size-5 text-primary"/><div><p className="text-sm font-medium">Administration Django avancée</p><p className="text-xs text-muted-foreground">Groupes, permissions et modèles Django</p></div></div><Button size="sm" variant="outline" asChild><a href="/django-admin/">Ouvrir <ArrowUpRight className="size-4"/></a></Button></CardContent></Card>
|
||||
</main>
|
||||
</div>}
|
||||
</AppShell>
|
||||
|
||||
@ -17,7 +17,6 @@ export default function HomePage() {
|
||||
<p className="mt-5 max-w-xl text-base leading-relaxed text-muted-foreground">Docker Compose, Django REST Framework, React avec shadcn/ui, MySQL 8 et Oxlint. Une architecture modulaire, prête à être personnalisée.</p>
|
||||
<div className="mt-8 flex flex-wrap gap-3">
|
||||
<Button asChild><a href="/admin/">Ouvrir l’administration <ArrowRight className="size-4"/></a></Button>
|
||||
<Button variant="outline" asChild><a href="/django-admin/">Django avancé <ArrowRight className="size-4"/></a></Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -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 },
|
||||
]),
|
||||
|
||||
@ -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.")
|
||||
Loading…
x
Reference in New Issue
Block a user