← Comparatif

Runique vs Django

Sommaire

CLI

Commandes réelles du binaire runique (voir runique/src/bin/runique.rs) : new, create-superuser, test, makemigrations, migration up | down | status. Pas de runique start — l'admin est du Rust plain, cargo run suffit.

CommandeDjangoRunique
Créer un projetdjango-admin startproject nomrunique new nom
Créer une apppython manage.py startapp nom—
Migrations (générer)python manage.py makemigrationsrunique makemigrations
Migrations (appliquer)python manage.py migraterunique migration up
Migrations (annuler)python manage.py migrate app 0001runique migration down -n 1
Statut migrationspython manage.py showmigrationsrunique migration status
Créer superuserpython manage.py createsuperuserrunique create-superuser
Testspython manage.py testrunique test
Démarrerpython manage.py runservercargo run

Routing

FonctionnalitéDjangoRunique
Déclaration des routesurls.py avec path()url.rs avec macro urlpatterns!{}
Routes dynamiquespath('users/<int:id>/', view)"/users/{id}" dans urlpatterns!
Namespacesapp_name + include()Router::new().nest("/prefix", ...)
Reverse URL{% url "nom_vue" %} natif{% link "nom_vue" %} → fonction Tera custom
Paramètre de chemin typé<int:id> dans path(), converti en intrequest.get_path_as::<i32>("id")
Paramètre de chemin brut<str:id>request.get_path("id")
Query param uniquerequest.GET.get('key')request.get_query("key")
Query string complèterequest.GETrequest.query::<MyStruct>()? (désérialise vers un struct Deserialize, 400 si la query ne correspond pas)
Headers HTTPrequest.META['HTTP_X_FOO']request.headers.get("x-foo")

Vues / Handlers

FonctionnalitéDjangoRunique
Vue fonctiondef ma_vue(request)async fn ma_vue(...)
Vue classeclass MaVue(View)—
Accès sessionrequest.sessionrequest.session via context::template::Request
Accès DBModel.objects.get(...)Model::objects.get(...) ou builders SeaORM
Rendu templaterender(request, "template.html", ctx)request.render("template.html")
Redirectredirect("nom_url")Redirect::to("/url") ou reverse(&engine, "nom")
Messages flashmessages.success(request, "...")macros success!, error!, info!, warning!
Connexions secondairesDATABASES['mongo']engine.extension::<mongodb::Client>() (TypeMap multi-type)

Formulaires

FonctionnalitéDjangoRunique
Définitionclass MonForm(ModelForm)#[form(schema = blog, fields = [...])], lié à son entité par le schéma, ou RuniqueForm manuel
Validationform.is_valid()ValidationForm::try_form(form, &request).await
Champs disponiblesCharField, EmailField, FileField, etc.TextField (+ constructeurs ::email/::password/::url/::phone → bon <input type> + validation), NumericField, BooleanField, CheckboxField, RadioField, ChoiceField, FileField, DateField, TimeField, DateTimeField, DurationField, ColorField, SlugField, UUIDField, IPAddressField, JSONField, HiddenField, MediaField, BinaryField
Rendu HTML{{ form.as_p }}{% form.nom_form %} (entier) ou {% form.nom_form.champ %}
CSRF intégréautomatiqueautomatique — injecté avant le premier champ
Sauvegardeform.save()validated.save(&db).await, sur le formulaire validé, pour tout formulaire qui porte son entité (FormEntity) : #[form(schema = …)] ou impl FormEntity
Accès aux donnéesform.cleaned_data['clé']validated.cleaned::<String>("clé"), validated.cleaned::<i32>(...), etc.
Validation asyncnonoui (accès DB dans clean())
Validation croiséeclean()clean() async
FichiersFileField (pas de validation de contenu par défaut)FileField / MediaField multipart natif — validation type/taille, signature du contenu pour les images, l'audio et la vidéo, extensions actives (.svg, .html, .js…) rejetées, écriture en staging seulement après le jeton CSRF, puis commit après validation (nom UUID, pas de path traversal)
Sanitisation HTMLnon (à la main)sanitize_rich / sanitize_strict appliquées aux champs richtext

Templates

FonctionnalitéDjangoRunique
MoteurDjango Template LanguageTera (syntaxe Jinja2 / Django-like)
Héritage{% extends %} / {% block %}idem
Fichiers statiques{% load static %} + {% static "file" %}{% static "file" %} natif
Fichiers media{{ MEDIA_URL }}file{% media "file" %} natif (variables supportées)
URL reverse{% url "nom" %}{% link "nom" %}
CSRF{% csrf_token %}{% csrf %}
Messages{% for m in messages %}{% messages %}
Internationalisation{% trans "..." %}— (t() et tf() côté Rust)

ORM / Base de données

FonctionnalitéDjangoRunique
ORMDjango ORM natifSeaORM (Rust async)
Définition modèleclass User(models.Model)macro model!{} (types sémantiques)
Migrations autooui (détection changements)runique makemigrations
QuerySet chaînableUser.objects.filter(...).order_by(...)User::objects.filter(...).order_by_asc(Col)
.get() strictlève MultipleObjectsReturned.one() — retourne Err si plusieurs lignes
Tri aléatoireorder_by('?').order_by_random()
Tri par expressionorder_by(Lower("titre").desc()).order_by_expr(expr, order)
RelationsForeignKey, ManyToMany, OneToOneRelations SeaORM standard
Transactionswith transaction.atomic()request.db_atomic() ou transaction SeaORM
Multi-moteur SQLouiPostgreSQL, MySQL/MariaDB, SQLite (3 backends SeaORM ; MariaDB passe par le driver MySQL avec son propre dialecte de migration)
Connexions secondairesDATABASES multi-entrées.with_custom_db(client), un client par type (TypeMap)
Extension table framework—extend!{} — ALTER TABLE ADD COLUMN sur tables eihwaz_*

Authentification

FonctionnalitéDjangoRunique
Login / Logoutauthenticate() + login()authenticate_user() + login(), logout()
Vérif authentificationrequest.user.is_authenticatedis_authenticated(&session).await
Utilisateur courantrequest.userrequest.user
Protection route@login_required.login_required("/profil", "profil", view!(profil), "/login")
Sessionsnatiftower-sessions (MemoryStore + DB fallback)
Protection brute forcedjango-axes (tiers)LoginGuard natif (lockout auto)
Hashage mot de passePBKDF2 par défaut, Argon2 en optionArgon2 par défaut
Reset passwordtoken signé lié au hash du mot de passe (stateless)tokens persistés en DB, hashés, single-use (durcis IDOR — clé user_id), TTL configurable via .token_ttl(...), template email personnalisable
Déconnexion forcéechanger le mot de passe ferme ses sessionschanger le mot de passe aussi ; invalidate_all(user_id) sur RuniqueSessionStore

Sécurité

FonctionnalitéDjangoRunique
CSRFnatifnatif (comparaison constant-time)
CSPnatif depuis Django 6.0 (ContentSecurityPolicyMiddleware), à activernatif (nonce par requête toujours actif)
HSTSSECURE_HSTS_SECONDSnatif — émis uniquement sur HTTPS réel (enforce_https/ACME), max-age/includeSubDomains/preload configurables (preload opt-in)
SameSite cookiesconfigurableStrict par défaut
HttpOnly cookiespar défauttoujours true
Rate limitingdjango-ratelimit (tiers)RateLimiter natif, déclaré par route — clé = IP cliente résolue par les proxys de confiance, sinon le pair ConnectInfo ; jamais un en-tête non validé
Open Redirect—natif — Location des réponses 3xx et en-tête Refresh de toute réponse, cible lue par l'analyseur d'URL WHATWG (le même que les navigateurs), refus en cas de doute
CORSdjango-cors-headers (tiers)natif via .with_cors(...)
Permissions-Policy—natif — preset sécurisé par défaut (~20 features refusées)
Trusted Proxies / XFFSECURE_PROXY_SSL_HEADER (partiel)natif — validation chaîne XFF complète (droite→gauche), preset RFC 1918, défaut edge-aware : none() auto quand Runique termine le TLS (ACME = pas de proxy) ; une plage /0 (faire confiance à tout le monde) est refusée au démarrage
Secret keymanuelgénérée auto à runique new — refus de boot en prod si absente / valeur par défaut / < 32 caractères

Panel Admin

FonctionnalitéDjangoRunique
Activationadmin.site.register(Model)builder typé ModelAdmin + AdminSite.register(...)
CRUD completnatifnatif
Pagination listenatif.page_size(n) au site + .per_page(n) par modèle
Colonnes affichéeslist_display.list(|l| l.display([Title, Status])) — colonnes SeaORM typées
Recherche / filtresnatif.search([...]) + .filter([...]) — colonnes directes uniquement, filtres cumulables
Actions de groupeactions.action(Col, value, "Libellé") — valeur fixée dans le code, l'UI envoie l'index
Édition en masselist_editable (plusieurs lignes depuis la liste)édition en lot sur une sélection
Suppression en massenatifnatif si le compte peut supprimer
Relations M2M / listesfilter_horizontal / ManyRelatedFieldchamps liste du modèle (multichoice / checkbox)
Routes admin customget_urls()admin "…" => … dans urlpatterns!
Templates customouioui (hiérarchie Tera + .templates(...) par modèle)
Permissionspar ressourceRangs, droits des admins par compte (table et colonne)
Comptes superutilisateurun staff ayant le droit de modifier les utilisateurs peut se rendre superutilisateur (la doc de Django le signale)is_superuser jamais modifiable depuis l'admin, et le compte d'un superutilisateur (email, réinitialisation, suppression) n'est modifiable que par un superutilisateur
Historique modificationsnatif (LogEntry, lien « History ») ; diffs de champs avec django-simple-history (tiers)historique natif (créé/modifié/supprimé) avec diff de champs, vue timeline et filtres (ressource/action/utilisateur)
Comptes étendusAUTH_USER_MODEL (modèle utilisateur personnalisé)extend!{} + AdminSite::accounts_users(ModelAdmin)
Verrouillage optimiste—natif — détecte les éditions concurrentes (__original_updated_at)
Protection brute-force logindjango-axes (tiers)natif — LoginGuard + rate limiter sur le login admin
Email création compteplugin tiersnatif — lien « choisir mon mot de passe » envoyé à l'adresse du compte, activé à la première définition
Rafraîchissement liste partiel—natif — list_partial HTMX sans rechargement page
Champs lecture seule / create-onlyreadonly_fields.readonly([...]) / .create_only([...]) sur le builder form
Fieldsetsfieldsets—
Navigation par datedate_hierarchy—
Exporttiers (django-import-export)—
Boutons save avancés"Save and continue" / "Save and add"—

Email

FonctionnalitéDjangoRunique
Envoi emailsend_mail() natifEmail::new().send() natif
Templates emailnatiftemplates Tera via .template(&tera, "emails/mon.html", ctx.into())
Backend SMTPconfigurableconfiguration via .env
Backend dev (console)EMAIL_BACKEND = 'console'EMAIL_BACKEND=console dans .env

Observabilité / Logging

FonctionnalitéDjangoRunique
Config loggingdict LOGGING (logging Python)arbre RuniqueLog par domaine (forms, middleware, session, auth, admin, db, mailer, migration, templates, errors, builder) — chaque feuille un Option<LogLevel>
Tracing structurétiers (structlog)natif (tracing) ; les Result avalés loggent leur file:line via TraceResult::trace / trace_or
Sortieshandlers (console / fichier / …).output(...) : stdout couleurs, fichier roulant (JSON ou texte, non bloquant), LogSink custom (DB / HTTP / file)
Override fichier runtime—variable d'env RUNIQUE_LOG_FILE
Subscriber côté apple logging appartient à l'app.external() — Runique n'installe rien, émet quand même vers la façade tracing

Internationalisation

FonctionnalitéDjangoRunique
Langues supportéesillimitées9 langues par défaut (JSON compilé)
Fallbackouioui (Lang::En)
Traduction_("...")t("section.clé") / tf("...", &[var]) en Rust, textes du framework

Performance & Déploiement

AspectDjangoRunique
RuntimeCPython (interprété)Tokio async Rust (compilé)
Compilation—binaire unique
ACME / TLS autocertbot (externe)natif via feature acme

Ce qu'il manque encore (comparé à Django)

  • Redimensionnement automatique d'images : resize/cropping côté serveur non natif.
  • Vues CRUD publiques génériques : pas d'équivalent aux DetailView, ListView, CreateView de Django pour les vues publiques — envisagé via crud!{} (feuille de route, sans engagement). Le panel admin couvre le CRUD backoffice via ModelAdmin.
  • Signals / hooks modèles : before_save, after_save, before_delete, after_delete ne se déclarent pas dans model!{}. La structure interne HooksDef existe, sans lien avec le DSL ; le comportement généré par model!{} ne remplit que les colonnes auto_now. Prévu à la feuille de route, sans engagement.
  • Management commands : pas d'équivalent à manage.py custom_command — les opérations one-shot passent par src/bin/.
  • Test client intégré : pas de client HTTP de test natif. runique test et runique_test couvrent le lancement et l'isolation de la base ; les requêtes passent par reqwest ou tower::ServiceExt::oneshot.
  • Fixtures : pas de loaddata/dumpdata — les seeds sont des fonctions Rust.
  • Admin inline : pas d'édition d'objets liés directement dans le formulaire parent.
  • Admin actions de groupe custom : .action(...) ne supporte que des mises à jour de colonnes à valeur fixe — aucune logique Rust arbitraire possible sur la sélection (équivalent Django : actions avec une fonction quelconque).
  • Admin filtre FK : .filter([...]) ne supporte que les colonnes directes — pas de traversée de relation (article__auteur__nom).
  • Admin fieldsets : pas de groupement de champs par section dans les formulaires admin (fieldsets Django).
  • Admin navigation par date : pas de date_hierarchy pour filtrer la liste par année/mois/jour.
  • Admin export : pas d'export CSV/JSON natif depuis la liste.
  • Admin boutons save avancés : pas de "Enregistrer et continuer" / "Enregistrer et ajouter un autre".
  • i18n complète : t() et tf() sont des fonctions Rust pour les textes du framework. Pas de fonction de traduction dans les templates Tera, pas de pluralisation, et un projet ne peut pas encore ajouter ses propres traductions.
  • Tâches de fond : pas de framework de tâches ; Django en a un depuis la 6.0 (@task, avec un worker externe).
  • Sitemap / RSS : pas de génération native. .sitemap(url) ajoute seulement la directive Sitemap: au /robots.txt généré.
  • Authentification tiers : le mode Delegated nomme des fournisseurs (Google, Microsoft, Apple, LDAP, SAML, personnalisé), mais aucun flow n'est implémenté. JWT et clés d'API absents.
  • Observabilité sécurité : CSP report-uri/report-to absent, les violations ne sont pas collectées. Les connexions (réussies, échouées, verrouillages) passent par les logs (RuniqueLog) et ne sont pas enregistrées en base ; eihwaz_history trace les écritures de l'admin, les droits et les resets.

Ce qui arrive dans Runique