Vue HTML de déconnexion égoïste. Modèle Model-Update-View et types dépendants. Types Scala et dépendants du chemin

Principalement pour développer des interfaces utilisateur. Pour l'utiliser, vous devez créer un type Modèle qui représente l'état complet du programme, un type Message qui décrit les événements de l'environnement externe auxquels le programme doit répondre en changeant son état, une fonction de mise à jour qui crée un nouvel état. du programme à partir de l'ancien état et du message, et une fonction de visualisation qui, en fonction de l'état du programme, calcule les impacts requis sur l'environnement externe, qui génèrent des événements de type Message. Le modèle est très pratique, mais il présente un petit inconvénient : il ne vous permet pas de décrire quels événements ont du sens pour des états de programme spécifiques.

Un problème similaire se pose (et est résolu) lors de l’utilisation du modèle State OO.

Le langage Elm est simple, mais très strict : il vérifie que la fonction de mise à jour gère d'une manière ou d'une autre toutes les combinaisons possibles d'états de modèle et d'événements de message. Par conséquent, vous devez écrire du code supplémentaire, quoique trivial, qui laisse généralement le modèle inchangé. Je veux démontrer comment cela peut être évité dans des langages plus complexes - Idris, Scala, C++ et Haskell.

Tout le code présenté ici est disponible sur GitHub pour expérimentation. Regardons les endroits les plus intéressants.


La fonction msg est inhabituelle : elle renvoie un type, pas une valeur. Pendant l'exécution, on ne sait rien des types valeur - le compilateur efface toutes les informations inutiles. Autrement dit, une telle fonction ne peut être appelée qu'au stade de la compilation.

MUV est un constructeur. Il accepte des paramètres : model - l'état initial du programme, updater - une fonction de mise à jour de l'état lors d'un événement externe et view - une fonction de création d'une vue externe. Notez que le type des fonctions de mise à jour et d'affichage dépend de la valeur du modèle (en utilisant la fonction msg à partir des paramètres de type).

Voyons maintenant comment lancer cette application

MuvRun : (Application modelType msgType IO) -> IO a muvRun (vue de mise à jour du modèle MUV) = do msg<- view model muvRun (MUV (updater model msg) updater view)
Nous avons choisi une opération d'entrée/sortie comme représentation externe (vue) (dans Idris, comme dans Haskell, les opérations d'entrée/sortie sont des valeurs de première classe ; pour qu'elles soient exécutées, des actions supplémentaires doivent être entreprises, renvoyant généralement une telle opération de la fonction principale).

En bref sur IO

Lors de l'exécution d'une opération de type (IO a), un certain impact sur le monde extérieur se produit, éventuellement vide, et une valeur de type a est renvoyée au programme, mais les fonctions de la bibliothèque standard sont conçues de telle manière qu'elle puisse être traité uniquement en générant une nouvelle valeur de type IO b. De cette façon, les fonctions pures sont séparées des fonctions ayant des effets secondaires. C'est inhabituel pour de nombreux programmeurs, mais cela permet d'écrire du code plus fiable.


Puisque la fonction muvRun génère des E/S, elle devrait renvoyer IO, mais comme elle ne se terminera jamais, le type d'opération peut être n'importe quoi - IO a.

Décrivons maintenant les types d'entités avec lesquelles nous allons travailler

Modèle de données = Déconnecté | Données de chaîne connectées MsgOuted = Données de chaîne de connexion MsgIned = Déconnexion | Accueil msgType total : Modèle -> Type msgType Logouted = MsgOuted msgType (Connecté _) = MsgIned
Nous décrivons ici un type de modèle qui reflète la présence de deux états d'interface : l'utilisateur n'est pas connecté et l'utilisateur avec un nom de type String est connecté.

Nous décrivons ensuite deux différents types de messages pertinents pour différentes variantes du modèle - si nous sommes déconnectés, nous ne pouvons nous connecter que sous un certain nom, et si nous sommes déjà connectés, nous pouvons soit nous déconnecter, soit dire bonjour. Idris est un langage fortement typé qui ne permet pas de mélanger différents types.

Et enfin, une fonction qui définit la correspondance de la valeur du modèle avec le type de message.

La fonction est déclarée totale - c'est-à-dire qu'elle ne doit pas planter ou se bloquer, le compilateur essaiera de surveiller cela. msgType est appelé au moment de la compilation, et sa totalité signifie que la compilation ne se bloquera pas à cause de notre erreur, bien qu'elle ne puisse pas garantir que l'exécution de cette fonction épuisera les ressources du système.
Il est également garanti qu'il n'exécutera pas "rm -rf /" car il n'y a pas d'IO dans sa signature.

Décrivons le programme de mise à jour :

Programme de mise à jour total : (m:Modèle) -> (msgType m) -> Programme de mise à jour du modèle Déconnecté (Nom de connexion) = Nom de connexion du programme de mise à jour (Nom de connexion) Déconnexion = Programme de mise à jour déconnecté (Nom de connexion) Greet = Nom de connexion
Je pense que la logique de cette fonction est claire. Je voudrais souligner encore une fois la totalité - cela signifie que le compilateur Idris vérifiera que nous avons pris en compte toutes les alternatives autorisées par le système de types. Elm effectue également cette vérification, mais il ne peut pas savoir que nous ne pouvons pas nous déconnecter si nous ne sommes pas encore connectés et nécessitera un traitement explicite de la condition.

Programme de mise à jour déconnecté Déconnexion = ???
Idris trouvera des incompatibilités de type lors d'une vérification inutile.

Passons maintenant à la vue - comme d'habitude dans l'interface utilisateur, ce sera la partie la plus difficile du code.

Total loginPage : IO MsgOuted loginPage = do putStr "Login : " map Login getLine total genMsg : String -> MsgIned genMsg "" = Déconnexion genMsg _ = Saluer total workPage : String -> IO MsgIned workPage name = do putStr ("Bonjour, " ++ nom ++ "\n") putStr "Saisissez une chaîne vide pour la déconnexion ou non vide pour le message d'accueil\n" map genMsg getLine vue totale : (m : modèle) -> vue IO (msgType m) Logouted = loginPage view (Nom de connexion ) = nom de la page de travail
view doit créer une opération d’E/S qui renvoie des messages, dont le type dépend encore une fois de la valeur du modèle. Nous avons deux options : loginPage, qui imprime un message "Login :", lit une chaîne du clavier et l'enveloppe dans un message de connexion, et workPage avec un paramètre de nom d'utilisateur, qui imprime un message d'accueil et renvoie des messages différents (mais du même type). - MsgIned) selon que l'utilisateur saisit une chaîne vide ou non vide. view renvoie l'une de ces opérations en fonction de la valeur du modèle, et le compilateur vérifie leur type, même s'il est différent.

Nous pouvons maintenant créer et exécuter notre application

Application : Modèle d'application Main.msgType IO app = MUV Vue du programme de mise à jour déconnecté principale : IO () main = application muvRun
Un point subtil doit être noté ici : la fonction muvRun renvoie IO un, où a n'a pas été spécifié et la valeur main est de type IO(), Où () est le nom d'un type habituellement appelé Unité, qui a une valeur unique, également écrite sous forme de tuple vide () . Mais le compilateur peut gérer cela facilement. en remplaçant a() à la place.

Types Scala et dépendants du chemin

Scala ne prend pas entièrement en charge les types dépendants, mais il existe des types qui dépendent de l'instance de l'objet par lequel il est référencé (types dépendants du chemin). Dans la théorie des types dépendants, ils peuvent être décrits comme une variante du type sigma. Les types dépendants du chemin permettent d'interdire l'ajout de vecteurs issus de différents espaces vectoriels, ou de décrire qui peut embrasser qui. Mais nous les utiliserons pour des tâches plus simples.

Classe abstraite scellée Classe de cas MsgLogouted Login (nom : chaîne) étend la classe abstraite scellée MsgLogouted Classe de cas MsgLogined Logout() étend la classe de cas MsgLogined Greet() étend la classe abstraite MsgLogined View ( def run() : Msg ) classe abstraite scellée Modèle ( type Message def view() : View ) la classe de cas Logouted() étend le modèle (type Message = MsgLogined remplace la vue def() : View .... ) la classe de cas Logined(nom : String) étend le modèle (type Message = MsgLogined remplace la vue def( ): Voir .... )
Les types algébriques dans Scala sont modélisés par héritage. Le type correspond à certains classe abstraite scellée, et chaque constructeur en a hérité classe de cas. Nous allons essayer de les utiliser exactement comme des types algébriques, décrivant toutes les variables comme appartenant au parent classe abstraite scellée.

Les classes MsgLogined et MsgLogouted de notre programme n'ont pas d'ancêtre commun. La fonction de visualisation devait être répartie sur différentes classes du modèle afin d'avoir accès à un type de message spécifique. Cela a ses avantages, que les partisans d'OO apprécieront : le code est regroupé selon la logique métier, tout ce qui concerne un cas d'utilisation est à proximité. Mais je préfère séparer le regard en une fonction distincte, dont le développement pourrait être transféré à une autre personne.

Maintenant, implémentons le programme de mise à jour

Objet Updater ( def update(model: Model)(msg: model.Message) : Model = ( correspondance du modèle ( case Logouted() => msg match ( case Login(name) => Logined(name) ) case Logined(name) => correspondance du msg ( case Logout() => Logouted() case Greet() => model ) ) ) )
Ici, nous utilisons des types dépendants du chemin pour décrire le type du deuxième argument à partir de la valeur du premier. Pour que Scala accepte de telles dépendances, les fonctions doivent être décrites sous forme curry, c'est-à-dire comme une fonction du premier argument, qui renvoie une fonction du deuxième argument. Malheureusement, Scala n'effectue pas actuellement beaucoup de vérifications de type pour lesquelles le compilateur dispose de suffisamment d'informations.

Donnons maintenant une implémentation complète du modèle et de la vue

La classe de cas Logouted() étend le modèle ( tapez Message = MsgLogouted override def view() : View = new View ( override def run() = ( println("Enter name ") val name = scala.io.StdIn.readLine() Connexion (nom) ) ) case class Logined(name: String) extends Model ( type Message = MsgLogined override def view() : View = new View ( override def run() = ( println(s"Bonjour, $name") println ( "Chaîne vide pour la déconnexion, non empy pour le message d'accueil.") scala.io.StdIn.readLine() match ( case "" => Logout() case _ => Greet() ) ) ) classe abstraite View ( def run( ) : Msg ) Visionneuse d'objets ( def view(model: Model): View = ( model.view() ) )
Le type de retour d'une fonction de vue dépend de l'instance de son argument. Mais pour la mise en œuvre, on se tourne vers le modèle.

L'application ainsi créée se lance ainsi :

Objet principal ( import scala.annotation.tailrec @tailrec def process(m: Model) ( val msg = Viewer.view(m).run() process(Updater.update(m)(msg)) ) def main(args: Tableau) = (processus (Déconnecté()) ) )
Le code système d'exécution ne sait donc rien de la structure interne des modèles et des types de messages, mais le compilateur peut vérifier que le message correspond au modèle actuel.

Ici, nous n'avions pas besoin de toutes les capacités fournies par les types dépendants du chemin. Des propriétés intéressantes apparaîtront si l’on travaille en parallèle avec plusieurs instances de systèmes Model-Updater-View, par exemple lors de la simulation d’un monde multi-agents (la vue représenterait alors l’influence de l’agent sur le monde et la réception de feedback). Dans ce cas, le compilateur a vérifié que le message a été traité exactement par l'agent auquel il était destiné, malgré le fait que tous les agents ont le même type.

C++

Le C++ est toujours sensible à l'ordre des définitions, même si elles sont toutes créées dans le même fichier. Cela crée certains désagréments. Je présenterai le code dans un ordre pratique pour démontrer des idées. Une version compilable peut être trouvée sur GitHub.

Les types algébriques peuvent être implémentés de la même manière qu'en Scala - une classe abstraite correspond à un type, et les descendants concrets correspondent aux constructeurs (appelons-les « classes de constructeurs », pour ne pas être confondus avec les constructeurs C++ ordinaires) du type algébrique. taper.

C++ prend en charge les types dépendants du chemin, mais le compilateur ne peut pas utiliser le type dans l'abstrait sans connaître le type réel auquel il est associé. Par conséquent, il est impossible d'implémenter Model-Updater-View avec leur aide.

Mais C++ dispose d’un puissant système de modèles. La dépendance du type à la valeur du modèle peut être masquée dans un paramètre de modèle d'une version spécialisée du système exécutif.

Processeur Struct ( Processeur const virtuel *next() const = 0; ); modèle struct ProcessorImpl : processeur public ( const CurModel * modèle ; ProcessorImpl (const CurModel* m) : modèle(m) ( ); const Processeur *next() const ( const Vue * view = modèle->view(); const typename CurModel::Message * msg = view->run(); supprimer la vue ; const Modèle * newModel = msg->process(model); supprimer le message ; return newModel->processor(); ) );
Nous décrivons un système d'exécution abstrait, avec une méthode unique pour faire tout ce qui est requis et renvoyer un nouveau système d'exécution adapté à l'itération suivante. La version spécifique possède un paramètre de modèle et sera spécialisée pour chaque « classe de constructeur » du modèle. Il est important ici que toutes les propriétés de type CurModel soient vérifiées lors de la spécialisation du modèle avec un paramètre de type spécifique, et au moment de la compilation du modèle lui-même, il n'est pas nécessaire de les décrire (bien qu'il soit possible d'utiliser concepts ou d'autres moyens d'implémenter des classes de types). Scala dispose également d'un système assez puissant de types paramétrés, mais il vérifie les propriétés des types de paramètres lors de la compilation du type paramétré. Là, la mise en œuvre d'un tel modèle est difficile, mais possible, grâce au support des classes de types.

Décrivons le modèle.

Modèle Struct ( virtual ~Model() (); virtual const Processeur *processor() const = 0; ); struct Connecté : modèle public ( struct Message ( const modèle virtuel * process(const Connecté * m) const = 0; virtual ~Message() (); struct Déconnexion : message public ( const Model * process(const Connecté * m) const; ); struct Greet: public Message ( const Model * process(const Logined * m) const; ); const std::string name; Logined(std::string lname) : name(lname) ( ); struct LoginedView: public View (...); vue const * view() const ( return new LoginedView(name); ); const Processeur *processor() const (retourne un nouveau ProcessorImpl (ce); ); ); struct Déconnecté : modèle public ( struct Message ( const modèle virtuel * processus (const déconnecté * m) const = 0; virtuel ~Message() (); struct Connexion : message public ( const std::nom de chaîne; Connexion (std :: string lname) : name(lname) ( ); const Model * process(const Logouted * m) const; ); struct LogoutedView: public View (...); vue const * view() const ( return new LogoutedView(); ); const Processeur *processor() const (retourne un nouveau ProcessorImpl (ce); ); );
Les « classes de constructeurs » du modèle « emportent tout avec elles » - c'est-à-dire qu'elles contiennent des classes de messages et de vues spécialisées pour elles, et sont également capables de créer elles-mêmes un système exécutif. Les types Native View ont un ancêtre commun à tous les modèles, ce qui peut être utile lors du développement de systèmes d'exécution plus complexes. Il est important que les types de messages soient complètement isolés et n'aient pas d'ancêtre commun.

L'implémentation du programme de mise à jour est distincte du modèle car elle nécessite que le type de modèle soit déjà entièrement décrit.

Modèle Const * Logouted::Login::process(const Logouted * m) const ( delete m; return new Logined(name); ); const Modèle * Logined::Logout::process(const Logined * m) const ( delete m; return new Logouted(); ); const Modèle * Logined::Greet::process(const Logined * m) const ( return m; );
Rassemblons maintenant tout ce qui concerne la vue, y compris les entités internes des modèles

Modèle struct View ( virtual const Message * run() const = 0; virtuel ~View () (); ); struct Connecté : modèle public ( struct LoginedView : vue publique ( const std::string name; LoginedView(std::string lname) : name(lname) (); virtual const Message * run() const ( char buf; printf("Bonjour %s", name.c_str()) ; fgets(buf, 15, stdin); return (*buf == 0 || *buf == "\n" || *buf == "\r") ? static_cast (nouvelle déconnexion()) : static_cast (nouveau salut); ); ); vue const * view() const ( return new LoginedView(name); ); ); struct Logouted : modèle public ( struct LogoutedView : vue publique ( virtual const Message * run() const ( char buf; printf("Connexion: "); fgets(buf, 15, stdin); return new Login(buf); ); ); vue const * view() const ( return new LogoutedView(); ); );
Et enfin, écrivons main

Int main(int argc, char ** argv) ( const Processeur * p = new ProcessorImpl (nouveau Déconnecté()); while(true) ( ​​​​const Processeur * pnew = p->next(); delete p; p = pnew; ) return 0; )

Et encore Scala, cette fois avec des classes de types

En termes de structure, cette implémentation reproduit presque entièrement la version C++.

Partie similaire du code

classe abstraite View ( def run(): Message ) classe abstraite Processeur ( def next() : Processeur; ) classe abstraite scellée Modèle ( def processeur() : Processeur ) classe abstraite scellée Classe de cas LoginedMessage Logout() étend la classe de cas LoginedMessage Greet( ) étend la classe de cas LoginedMessage Logined (nom de la valeur : String) étend le modèle (remplace le processeur def (): Processor = new ProcessorImpl (this)) classe abstraite scellée Classe de cas LogoutedMessage Login (nom : chaîne) étend la classe de cas LogoutedMessage Logouted () étend le modèle ( override def processeur() : Processeur = new ProcessorImpl(this) ) object Main ( import scala.annotation.tailrec @tailrec def process(p: Processor) ( process(p.next()) ) def main(args: Array) = (processus(nouveau ProcesseurImpl(Déconnecté())) ) )


Mais dans la mise en œuvre de l'environnement d'exécution, des subtilités surviennent.

Classe ProcessorImpl(model: M)(mise à jour implicite: (M, Message) => Modèle, vue: M => View) extends Processor ( def next(): Processor = ( val v = view(model) val msg = v. run() val newModel = updater(model,msg) newModel.processor() ) )
Ici nous voyons de nouveaux paramètres mystérieux (mise à jour implicite : (M, Message) => Modèle, vue : M => Vue). Le mot-clé implicite signifie que lors de l'appel de cette fonction (plus précisément, le constructeur de classe), le compilateur recherchera des objets de types appropriés marqués comme implicites dans le contexte et les transmettra comme paramètres appropriés. Il s'agit d'un concept assez complexe dont l'une des applications est l'implémentation de classes de types. Ici, ils promettent au compilateur que pour des implémentations spécifiques du modèle et du message, nous fournirons toutes les fonctions nécessaires. Maintenant, tenons cette promesse.

Mises à jour d'objets ( implicite def logoutedUpdater(model: Logouted, msg: LogoutedMessage): Model = ( (model, msg) match ( case (Logouted(), Login(name)) => Logined(name) ) ) implicite def viewLogouted(model : Logouted) = new View (remplacer def run() : LogoutedMessage = ( println("Enter name ") val name = scala.io.StdIn.readLine() Login(name) ) ) implicite def loginedUpdater(model: Connecté, msg : LoginedMessage): Model = ( (model, msg) match ( case (Logined(name), Logout()) => Logouted() case (Logined(name), Greet()) => model ) ) implicite def viewLogined( model: Connecté) = new View ( val name = model.name override def run() : LoginedMessage = ( println(s"Bonjour, $name") println("Chaîne vide pour la déconnexion, nonempy pour le message d'accueil.") scala.io .StdIn.readLine() match ( case "" => Logout() case _ => Greet() ) ) ) importer les mises à jour._

Haskell

Il n'y a pas de types dépendants dans Haskell traditionnel. Il manque également d'héritage, que nous avons largement utilisé lors de l'implémentation du modèle en Scala et C++. Mais l'héritage à un seul niveau (avec des éléments de types dépendants) peut être modélisé à l'aide d'extensions de langage plus ou moins standard - TypeFamilies et ExistentialQuantification. Pour l'interface commune des classes POO enfants, une classe de types est créée, dans laquelle se trouve un type « famille » dépendant, les classes enfants elles-mêmes sont représentées par un type distinct, puis enveloppées dans un type « existentiel » avec un seul constructeur. .

Modèle de données = pour tous m. (M pouvant être mis à jour, m visualisable) => Classe modèle m M pouvant être mis à jour où données Message m:: * update:: m -> (Message m) -> Classe de modèle (M pouvant être mis à jour) => M visualisable où vue :: m -> (Afficher (Message m)) données Déconnecté = Données déconnectées Connecté = Chaîne connectée
J'ai essayé de séparer le programme de mise à jour et l'affichage autant que possible, j'ai donc créé deux classes de types différentes, mais jusqu'à présent, cela n'a pas bien fonctionné.

L'implémentation du programme de mise à jour est simple

Instance pouvant être mise à jour Déconnectée où les données Message Déconnecté = Mise à jour de la chaîne de connexion Déconnecté (Nom de connexion) = Modèle (Nom de connexion) Instance pouvant être mise à jour Connecté où les données Message Connecté = Déconnexion | Mise à jour du message d'accueil m Déconnexion = Modèle Mise à jour déconnectée m Message d'accueil = Modèle m
J'ai dû corriger IO en tant que View. Les tentatives pour le rendre plus abstrait ont tout compliqué et augmenté le couplage du code - le type Model doit savoir quelle vue nous allons utiliser.

Importer le type System.IO View a = IO une instance Visualisable Déconnecté où view Logouted = do putStr "Connexion : " hFlush stdout fmap Connexion instance getLine Visualisable Connecté où view (Nom connecté) = do putStr $ "Bonjour " ++ nom ++ " !\n" hFlush sortie standard l<- getLine pure $ if l == "" then Logout else Greeting
Eh bien, l'environnement exécutable diffère peu de celui similaire d'Idris

RunMUV :: Model -> IO a runMUV (Modèle m) = faire msg<- view m runMUV $ update m msg main:: IO () main = runMUV (Model Logouted)

Django est livré avec de nombreuses ressources intégrées pour les cas d'utilisation les plus courants d'une application Web. L’application d’enregistrement est un très bon exemple et l’avantage est que les fonctionnalités peuvent être utilisées directement.

Avec l'application d'enregistrement Django, vous pouvez profiter des fonctionnalités suivantes :

  • Se connecter
  • Se déconnecter
  • S'inscrire
  • Réinitialisation du mot de passe

Dans ce didacticiel, nous nous concentrerons sur les fonctionnalités de connexion et de déconnexion. Pour vous inscrire et réinitialiser votre mot de passe, consultez les didacticiels ci-dessous :

Commencer

Avant de commencer, assurez-vous d'avoir django.contrib.auth dans votre INSTALLED_APPS et le middleware d'authentification correctement configuré dans les paramètres MIDDLEWARE_CLASSES.

Les deux sont déjà configurés lorsque vous démarrez un nouveau projet Django à l'aide de la commande startproject . Donc, si vous n’avez pas supprimé les configurations initiales, vous devriez être prêt.

Si vous démarrez un nouveau projet juste pour suivre ce tutoriel, créez un utilisateur en utilisant la ligne de commande afin que nous puissions tester les pages de connexion et de déconnexion.

$ python manage.py créer un superutilisateur

A la fin de cet article je fournirai le code source de l'exemple avec la configuration minimale.

Configurer les routes d'URL

Importez d’abord le module django.contrib.auth.views et ajoutez une route URL pour les vues de connexion et de déconnexion :

à partir de django.conf.urls importer l'URL de django.contrib importer l'administrateur de django.contrib.auth importer des vues en tant que auth_views urlpatterns = [ url (r"^login/$" , auth_views . login , name = "login" ), url ( r"^logout/$" , auth_views . logout , nom = "logout" ), url (r"^admin/" , admin . site . urls ), ]

Créer un modèle de connexion

Par défaut, la vue django.contrib.auth.views.login tentera de restituer le modèle Registration/login.html. La configuration de base consisterait donc à créer un dossier nommé Registration et à y placer un modèle login.html.

Suivant un modèle de connexion minimal :

(% étend "base.html" %) (% titre du bloc %)Connexion(% endblock %) (% contenu du bloc %)

Se connecter

(% csrf_token %) (( form.as_p ))
(%bloc final%)

Cet exemple simple valide déjà le nom d'utilisateur et le mot de passe et authentifie correctement l'utilisateur.

Personnalisation de la vue de connexion

Il existe quelques paramètres que vous pouvez transmettre à la vue de connexion pour l'adapter à votre projet. Par exemple, si vous souhaitez stocker votre modèle de connexion ailleurs que Registration/login.html, vous pouvez transmettre le nom du modèle en paramètre :

url (r"^login/$" , auth_views . login , ( "template_name" : "core/login.html" ), nom = "login" ),

Vous pouvez également transmettre un formulaire d'authentification personnalisé à l'aide du paramètre identifier_form , au cas où vous auriez implémenté un modèle utilisateur personnalisé.

Désormais, une configuration très importante est effectuée dans le fichier settings.py, qui est l'URL que Django redirigera l'utilisateur après une authentification réussie.

Dans le fichier settings.py, ajoutez :

LOGIN_REDIRECT_URL = "accueil"

La valeur peut être une URL codée en dur ou un nom d'URL. La valeur par défaut de LOGIN_REDIRECT_URL est /accounts/profile/ .

Il est également important de noter que Django tentera de rediriger l'utilisateur vers le prochain paramètre GET.

Configuration de la vue de déconnexion

Après avoir accédé à la vue django.contrib.auth.views.logout, Django affichera le modèle Registration/logged_out.html. De la même manière que nous l'avons fait dans la vue de connexion, vous pouvez transmettre un modèle différent comme ceci :

url (r"^logout/$" , auth_views . logout , ( "template_name" : "logged_out.html" ), name = "logout" ),

Habituellement, je préfère utiliser le paramètre next_page et rediriger soit vers la page d'accueil de mon projet, soit vers la page de connexion lorsque cela a du sens.

De nombreuses personnes commencent à écrire un projet pour travailler avec une seule tâche, sans impliquer qu'il puisse devenir un système de gestion multi-utilisateurs, par exemple du contenu ou, Dieu nous en préserve, de la production. Et tout semble génial et cool, tout fonctionne, jusqu'à ce que vous commenciez à comprendre que le code écrit est entièrement constitué de béquilles et de code en dur. Le code est mêlé de mise en page, de requêtes et de béquilles, parfois même illisibles. Un problème urgent se pose : lors de l'ajout de nouvelles fonctionnalités, il faut bricoler ce code pendant très longtemps, en se rappelant « qu'est-ce qui y était écrit ? et maudis-toi dans le passé.

Vous avez peut-être même entendu parler des modèles de conception et même feuilleté ces merveilleux livres :

  • E. Gamma, R. Helm, R. Johnson, J. Vlissides « Techniques de conception orientées objet. Modèles de conception";
  • M. Fowler « Architecture des applications logicielles d'entreprise ».
Et beaucoup, intrépides devant les manuels et la documentation énormes, ont essayé d'étudier n'importe lequel des cadres modernes et, face à la complexité de la compréhension (due à la présence de nombreux concepts architecturaux intelligemment liés les uns aux autres), ont reporté l'étude et l'utilisation de les outils modernes « en veilleuse ».

Cet article sera utile principalement aux débutants. Dans tous les cas, j'espère que dans quelques heures, vous pourrez vous faire une idée de la mise en œuvre du modèle MVC, qui sous-tend tous les frameworks Web modernes, et également obtenir de la « nourriture » pour une réflexion plus approfondie sur « comment fais-le." À la fin de l'article, vous trouverez une sélection de liens utiles qui vous aideront également à comprendre en quoi consistent les frameworks Web (outre MVC) et comment ils fonctionnent.

Il est peu probable que les programmeurs PHP chevronnés trouvent quelque chose de nouveau pour eux-mêmes dans cet article, mais leurs commentaires et commentaires sur le texte principal seraient très utiles ! Parce que Sans théorie, la pratique est impossible, et sans pratique, la théorie est inutile, alors il y aura d'abord un peu de théorie, et ensuite nous passerons à la pratique. Si vous connaissez déjà le concept MVC, vous pouvez sauter la section théorique et passer directement à la pratique.

1. Théorie

Le modèle MVC décrit une manière simple de structurer une application, dont le but est de séparer la logique métier de l'interface utilisateur. En conséquence, l’application est plus facile à mettre à l’échelle, à tester, à maintenir et, bien sûr, à mettre en œuvre.

Regardons le diagramme conceptuel du modèle MVC (à mon avis, c'est le diagramme le plus réussi que j'ai vu) :

Dans l'architecture MVC, le modèle fournit les données et les règles de logique métier, la vue est responsable de l'interface utilisateur et le contrôleur assure l'interaction entre le modèle et la vue.

Un flux typique d'une application MVC peut être décrit comme suit :

  1. Lorsqu'un utilisateur visite une ressource Web, le script d'initialisation crée une instance de l'application et la lance pour exécution.
    Cela affiche une vue, par exemple, de la page principale du site.
  2. L'application reçoit une demande de l'utilisateur et détermine le contrôleur et l'action demandés. Dans le cas de la page principale, l'action par défaut est effectuée ( indice).
  3. L'application instancie le contrôleur et exécute la méthode d'action,
    qui, par exemple, contient des appels de modèle qui lisent les informations de la base de données.
  4. Après cela, l'action crée une vue avec les données obtenues à partir du modèle et affiche le résultat à l'utilisateur.
Modèle- contient la logique métier de l'application et inclut des méthodes d'échantillonnage (il peut s'agir de méthodes ORM), de traitement (par exemple des règles de validation) et de fourniture de données spécifiques, ce qui la rend souvent très épaisse, ce qui est tout à fait normal.
Le modèle ne doit pas interagir directement avec l'utilisateur. Toutes les variables liées à la demande de l'utilisateur doivent être traitées dans le contrôleur.
Le modèle ne doit pas générer de code HTML ou autre code d'affichage pouvant changer en fonction des besoins de l'utilisateur. Ce code doit être traité dans les vues.
Le même modèle, par exemple : le modèle d'authentification des utilisateurs peut être utilisé à la fois dans la partie utilisateur et administrative de l'application. Dans ce cas, vous pouvez déplacer le code général dans une classe distincte et en hériter, en définissant des méthodes spécifiques à la sous-application dans ses descendants.

Voir- utilisé pour définir l'affichage externe des données reçues du contrôleur et du modèle.
Les vues contiennent du balisage HTML et de petits inserts de code PHP pour parcourir, formater et afficher les données.
Ne devrait pas accéder directement à la base de données. C'est ce que les modèles devraient faire.
Ne devrait pas fonctionner avec les données obtenues à partir d’une demande d’utilisateur. Cette tâche doit être effectuée par le contrôleur.
Peut accéder directement aux propriétés et méthodes d’un contrôleur ou de modèles pour obtenir des données prêtes à être sorties.
Les vues sont généralement divisées en un modèle commun, contenant un balisage commun à toutes les pages (par exemple, un en-tête et un pied de page) et des parties du modèle utilisées pour afficher les données de sortie du modèle ou afficher les formulaires de saisie de données.

Manette- la colle qui relie les modèles, les vues et autres composants dans une application fonctionnelle. Le responsable du traitement est responsable du traitement des demandes des utilisateurs. Le contrôleur ne doit pas contenir de requêtes SQL. Il vaut mieux les conserver dans des modèles. Le contrôleur ne doit pas contenir de HTML ou autre balisage. Cela vaut la peine de le mettre en évidence.
Dans une application MVC bien conçue, les contrôleurs sont généralement très fins et ne contiennent que quelques dizaines de lignes de code. On ne peut pas en dire autant des Stupid Fat Controllers (SFC) dans le CMS Joomla. La logique du contrôleur est assez typique et la majeure partie est transférée aux classes de base.
Les modèles, au contraire, sont très épais et contiennent la majeure partie du code lié au traitement des données, car la structure des données et la logique métier qu'elles contiennent sont généralement assez spécifiques à une application particulière.

1.1. Contrôleur frontal et contrôleur de page

Dans la plupart des cas, l'interaction de l'utilisateur avec une application Web se produit en cliquant sur des liens. Regardez maintenant la barre d'adresse de votre navigateur - vous avez reçu ce texte à partir de ce lien. D'autres liens, comme ceux situés sur le côté droit de cette page, vous fourniront un contenu différent. Ainsi, le lien représente une commande spécifique à l'application Web.

J'espère que vous avez déjà remarqué que différents sites peuvent avoir des formats complètement différents pour construire la barre d'adresse. Chaque format peut afficher l'architecture d'une application web. Même si ce n’est pas toujours le cas, dans la plupart des cas, c’est un fait évident.

Considérons deux options pour la barre d'adresse, qui affichent du texte et un profil utilisateur.

Première possibilité :

  1. www.example.com/article.php?id=3
  2. www.example.com/user.php?id=4
Ici, chaque script est chargé d'exécuter une commande spécifique.

Deuxième option:

  1. www.example.com/index.php?article=3
  2. www.example.com/index.php?user=4
Et ici, tous les appels se produisent dans un seul scénario index.php.

Vous pouvez voir l'approche multi-touchpoint sur les forums phpBB. La navigation sur le forum s'effectue via un script vueforum.php, voir le sujet via viewtopic.php etc. La deuxième approche, avec accès via un seul fichier de script physique, est visible dans mon CMS MODX préféré, où tous les accès passent par index.php.

Ces deux approches sont complètement différentes. La première est typique du modèle Page Controller et la seconde approche est implémentée par le modèle Front Controller. Le contrôleur de page convient aux sites avec une logique assez simple. À son tour, le contrôleur de requêtes consolide toutes les activités de traitement des requêtes en un seul endroit, ce qui lui confère des capacités supplémentaires qui peuvent vous permettre de mettre en œuvre des tâches plus complexes que celles habituellement résolues par le contrôleur de page. Je n'entrerai pas dans les détails de l'implémentation du contrôleur de page, mais dirai seulement que dans la partie pratique, ce sera le contrôleur de requêtes (quelque chose de similaire) qui sera développé.

1.2. Routage d'URL

Le routage d'URL vous permet de configurer votre application pour accepter les requêtes provenant d'URL qui ne correspondent pas aux fichiers d'application réels et d'utiliser des CNC sémantiquement significatives pour les utilisateurs et préférées pour l'optimisation des moteurs de recherche.

Par exemple, pour une page standard affichant un formulaire de contact, l'URL pourrait ressembler à ceci :
http://www.example.com/contacts.php?action=feedback

Code de traitement approximatif dans ce cas :
switch ($_GET ["action" ]) ( case "about" : require_once ("about.php" ); // Page "À propos de nous" casser ; cas "contacts" : require_once ( "contacts.php" ); //page "Contacts" casser ; case "feedback" : require_once("feedback.php" ); // Page "Commentaires" casser ; par défaut : require_once ( "page404.php" ); // page "404" pause ; )
Je pense que presque tout le monde a déjà fait ça.

À l'aide d'un moteur de routage d'URL, vous pouvez configurer votre application pour accepter des requêtes comme celle-ci afin d'afficher les mêmes informations :
http://www.example.com/contacts/feedback

Ici, les contacts représentent le contrôleur et les commentaires sont la méthode du contrôleur de contacts qui affiche le formulaire de commentaires, etc. Nous reviendrons sur cette problématique dans la partie pratique.

Il convient également de savoir que les routeurs de nombreux frameworks Web vous permettent de créer des itinéraires d'URL personnalisés (précisez la signification de chaque partie de l'URL) et des règles pour leur traitement.
Nous disposons désormais de connaissances théoriques suffisantes pour passer à la pratique.

2. Pratique

Tout d’abord, créons la structure de fichiers et de dossiers suivante :

Pour l’avenir, je dirai que les classes principales Model, View et Controller seront stockées dans le dossier principal.
Leurs enfants seront stockés dans les répertoires contrôleurs, modèles et vues. Déposer index.php C'est le point d'entrée dans l'application. Déposer bootstrap.php lance le téléchargement de l'application, en connectant tous les modules nécessaires, etc.

Nous procéderons séquentiellement ; Ouvrons le fichier index.php et remplissons-le avec le code suivant :
ini_set("display_errors" , 1 ); require_once "application/bootstrap.php" ;
Il ne devrait y avoir aucune question ici.

Passons ensuite immédiatement à la drisse bootstrap.php:
require_once "core/model.php" ; require_once "core/view.php" ; require_once "core/controller.php" ; require_once "core/route.php" ; Route::start(); //démarre le routeur
Les trois premières lignes incluront des fichiers du noyau actuellement inexistants. Les dernières lignes incluent le fichier avec la classe du routeur et le lancent pour exécution en appelant la méthode de démarrage statique.

2.1. Implémentation d'un routeur URL

Pour l'instant, s'écartons de l'implémentation du modèle MVC et passons au routage. La première étape que nous devons faire est d'écrire le code suivant dans .htaccess:
RewriteEngine sur RewriteCond %(REQUEST_FILENAME) !-f RewriteCond %(REQUEST_FILENAME) !-d RewriteRule .* index.php [L]
Ce code redirigera le traitement de toutes les pages vers index.php, c'est ce dont nous avons besoin. Vous vous souvenez dans la première partie, nous avons parlé de Front Controller ?!

Nous placerons le routage dans un fichier séparé route.php au répertoire principal. Dans ce fichier, nous décrirons la classe Route, qui exécutera les méthodes du contrôleur, qui à leur tour généreront la vue de la page.

Contenu du fichier route.php

classe Itinéraire( statique fonction start() ( // contrôleur et action par défaut$controller_name = "Principal" ; $nom_action = "index" ; $routes = éclater("/" , $_SERVER ["REQUEST_URI" ]); // récupère le nom du contrôleur if (!empty ($routes )) ( $controller_name = $routes ; ) // récupère le nom de l'action if (!empty ($routes )) ( $action_name = $routes ; ) // ajoute des préfixes$model_name = "Model_" .$controller_name ; $nom_contrôleur = "Contrôleur_" .$nom_contrôleur ; $nom_action = "action_" .$nom_action ; // relie le fichier à la classe modèle (il se peut qu'il n'y ait pas de fichier modèle)$model_file = strtolower($model_name )..php" ; $model_path = "application/models/" .$model_file ; if (file_exists($model_path )) ( include "application/models/" .$model_file ; ) // connecte le fichier à la classe du contrôleur$controller_file = strtolower($controller_name )..php" ; $controller_path = "application/controllers/" .$controller_file ; if (file_exists($controller_path )) ( include "application/controllers/" .$controller_file ; ) else ( /* il serait correct de lever une exception ici, mais pour simplifier, nous redirigerons immédiatement vers la page 404 */ Route ::ErrorPage404(); ) // crée un contrôleur$controller = nouveau $controller_name ; $action = $nom_action ; if (method_exists($controller , $action )) ( // appelle l'action du contrôleur$contrôleur ->$action(); ) autre ( // il serait également plus sage de lever une exception ici Route ::ErrorPage404(); ) ) fonction ErreurPage404() ($hôte = "http://" .$_SERVER ["HTTP_HOST" ]."/" ; en-tête("HTTP/1.1 404 introuvable" ); header("Statut : 404 introuvable" ); header("Emplacement :" .$host ."404" ); ) )


Je constate que la classe implémente une logique très simplifiée (malgré le code volumineux) et peut même avoir des problèmes de sécurité. Cela a été fait intentionnellement, parce que... l'écriture d'une classe de routage à part entière mérite au moins un article séparé. Voyons les points principaux...

L'élément de tableau global $_SERVER["REQUEST_URI"] contient l'adresse complète à laquelle l'utilisateur a contacté.
Par exemple : example.ru/contacts/feedback

Utilisation de la fonction exploser L'adresse est divisée en composants. Du coup, on obtient le nom du contrôleur, pour l'exemple donné, il s'agit de contrôleur Contacts et le nom de l'action, dans notre cas - retour.

Ensuite, le fichier modèle (le modèle peut être manquant) et le fichier contrôleur, le cas échéant, sont connectés et enfin, une instance du contrôleur est créée et l'action est appelée, à nouveau, si elle a été décrite dans la classe du contrôleur.

Ainsi, en se rendant par exemple à l'adresse :
exemple.com/portfolio
ou
exemple.com/portfolio/index
Le routeur effectuera les actions suivantes :

  1. inclura le fichier model_portfolio.php du dossier models, contenant la classe Model_Portfolio ;
  2. inclura le fichier contrôleur_portfolio.php du dossier contrôleurs, contenant la classe Controller_Portfolio ;
  3. créera une instance de la classe Controller_Portfolio et appellera l'action par défaut, action_index, qui y est décrite.
Si l'utilisateur tente d'accéder à l'adresse d'un contrôleur inexistant, par exemple :
exemple.com/ufo
puis il sera redirigé vers la page « 404 » :
exemple.com/404
La même chose se produira si l'utilisateur accède à une action qui n'est pas décrite dans le contrôleur.

2.2. Revenons à l'implémentation de MVC

Allons dans le dossier principal et ajoutons trois autres fichiers au fichier route.php : model.php, view.php et contrôleur.php

Permettez-moi de vous rappeler qu'ils contiendront des classes de base, que nous allons maintenant commencer à écrire.

Contenu du fichier modèle.php
Modèle de classe ( publique fonction get_data() ( } }
La classe modèle contient une seule méthode de récupération de données vide, qui sera remplacée dans les classes descendantes. Lorsque nous créerons des classes descendantes, tout deviendra plus clair.

Contenu du fichier vue.php
classView( //public $template_view; // ici, vous pouvez spécifier la vue générale par défaut. fonction générer( $content_view , $template_view , $data = null) { /* if(is_array($data)) ( // convertit les éléments du tableau en variables extract($data); ) */ inclure "application/views/" .$template_view ; ) )
Il n'est pas difficile de deviner que la méthode générer destiné à former une vue. Les paramètres suivants lui sont transmis :

  1. $content_file - vues affichant le contenu de la page ;
  2. $template_file — modèle commun à toutes les pages ;
  3. $data est un tableau contenant des éléments de contenu de page. Généralement renseigné dans le modèle.
La fonction include connecte dynamiquement un modèle général (vue) dans lequel la vue sera intégrée
pour afficher le contenu d'une page spécifique.

Dans notre cas, le modèle général contiendra un en-tête, un menu, une barre latérale et un pied de page, et le contenu de la page sera contenu dans un formulaire séparé. Encore une fois, ceci est fait par souci de simplicité.

Contenu du fichier contrôleur.php
Contrôleur de classe ( modèle $ public ; vue publique $ ; fonction __construct() ($this ->view = new View(); ) ) )
Méthode index_action- c'est l'action appelée par défaut ; nous la remplacerons lors de l'implémentation des classes descendantes.

2.3. Implémentation des classes descendantes Model et Controller, création des View's

Maintenant, le plaisir commence ! Notre site Web de cartes de visite comprendra les pages suivantes :
  1. maison
  2. Prestations de service
  3. Portefeuille
  4. Contacts
  5. Et aussi - la page "404"
Chaque page possède son propre contrôleur du dossier contrôleurs et une vue du dossier vues. Certaines pages peuvent utiliser un ou plusieurs modèles du dossier models.

Dans la figure précédente, le fichier est mis en évidence séparément modèle_view.php est un modèle contenant un balisage commun à toutes les pages. Dans le cas le plus simple, cela pourrait ressembler à ceci :
<html lang="ru" > <tête > <métacharset="utf-8"> <titre > maisontitre > tête > <corps > $content_view ; ?> corps > HTML >
Pour donner au site un aspect présentable, nous créons un modèle CSS et l'intégrons dans notre site en modifiant la structure du balisage HTML et en connectant les fichiers CSS et JavaScript :
<lien rel ="feuille de style" type ="text/css" href ="/css/style.css" /> <script src="/js/jquery-1.6.2.js" type="text/javascript" >scénario >

A la fin de l'article, dans la section « Résultat », il y a un lien vers un dépôt GitHub avec un projet dans lequel des étapes ont été prises pour intégrer un modèle simple.

2.3.1. Création de la page principale

Commençons par le contrôleur contrôleur_main.php, voici son code :
la classe Controller_Main étend Controller ( fonction action_index() ($this ->view->generate("main_view.php" , "template_view.php" ); ) )
En méthode générer une instance de la classe View, les noms des fichiers du modèle général et la vue avec le contenu de la page sont transmis.
En plus de l'action d'indexation, le contrôleur peut bien entendu contenir d'autres actions.

Nous avons examiné le fichier de vue générale plus tôt. Considérez le fichier de contenu main_view.php:
<h1 > Accueillir!h1 > <p> <img src ="/images/office-small.jpg" aligner ="gauche" > <une href = "/" > L'ÉQUIPE OLOLOSHAun >- une équipe de spécialistes de premier ordre dans le domaine du développement de sites Web avec de nombreuses années d'expérience dans la collecte de masques mexicains, de statues de bronze et de pierre d'Inde et de Ceylan, de bas-reliefs et de sculptures créés par des maîtres de l'Afrique équatoriale il y a cinq ou six siècles. ..p>
Celui-ci contient un balisage simple sans aucun appel PHP.
Pour afficher la page principale, vous pouvez utiliser l'une des adresses suivantes :

  • méthodes de bibliothèques qui implémentent l'abstraction de données. Par exemple, les méthodes de la bibliothèque PEAR MDB2 ;
  • Méthodes ORM ;
  • méthodes pour travailler avec NoSQL ;
  • et etc.
  • Par souci de simplicité, nous n'utiliserons pas ici de requêtes SQL ou d'instructions ORM. Au lieu de cela, nous émulerons des données réelles et renverrons immédiatement un tableau de résultats.
    Fichier modèle modèle_portfolio.php mettez-le dans le dossier modèles. Voici son contenu :
    la classe Model_Portfolio étend le modèle ( publique fonction get_data() ( return array (array ("Année" => "2012" , "Site" => "http://DunkelBeer.ru" , "Description" => "Site promotionnel de la bière brune Dunkel du fabricant allemand Löwenbraü, produite en Russie par la brasserie SUN InBev."), tableau ("Année" => "2012" , "Site" => "http://ZopoMobile.ru" , "Description" => "Catalogue en russe de téléphones chinois de Zopo basés sur le système d'exploitation Android et de leurs accessoires."), // faire ); ) )

    La classe du contrôleur de modèle est contenue dans le fichier contrôleur_portfolio.php, voici son code :
    la classe Controller_Portfolio étend Controller ( fonction __construct() ($this ->model = new Model_Portfolio(); $this ->view = new View(); ) fonction action_index() ($data = $this ->model->get_data(); $this ->view->generate("portfolio_view.php" , "template_view.php" , $data ); ) )
    Vers une variable données le tableau renvoyé par la méthode est écrit get_data que nous avons examiné plus tôt.
    Cette variable est ensuite passée en paramètre de méthode générer, qui contient également : le nom du fichier avec le modèle général et le nom du fichier contenant la vue avec le contenu de la page.

    La vue contenant le contenu de la page est dans le fichier portefeuille_view.php.

    Portefeuille

    Tous les projets du tableau suivant sont fictifs, alors n'essayez même pas de suivre les liens fournis. " ; }


    Lien GitHub : https://github.com/vitalyswipe/tinymvc/zipball/v0.1

    Mais dans cette version, j'ai esquissé les classes suivantes (et leurs types correspondants) :

    • Controller_Login dans lequel une vue est générée avec un formulaire de saisie du login et du mot de passe, après avoir rempli la procédure d'authentification et, en cas de succès, l'utilisateur est redirigé vers le panneau d'administration.
    • Contorller_Admin avec une action d'index qui vérifie si l'utilisateur était précédemment autorisé sur le site en tant qu'administrateur (si tel est le cas, la vue du panneau d'administration s'affiche) et une action de déconnexion pour se déconnecter.
    L'authentification et l'autorisation sont un sujet différent, ils ne sont donc pas abordés ici, mais seul le lien donné ci-dessus est fourni afin que vous ayez quelque chose par où commencer.

    4. Conclusion

    Le modèle MVC est utilisé comme base architecturale dans de nombreux frameworks et CMS, qui ont été créés afin de pouvoir développer des solutions qualitativement plus complexes dans un délai plus court. Cela a été rendu possible en augmentant le niveau d’abstraction, car il existe une limite à la complexité des structures avec lesquelles le cerveau humain peut fonctionner.

    Mais, lors du développement d'applications Web simples (par exemple, des sites de cartes de visite), il n'est pas toujours conseillé d'utiliser des frameworks Web tels que Yii ou Kohana, composés de plusieurs centaines de fichiers. Nous pouvons désormais créer un beau modèle MVC afin de ne pas mélanger le code Php, Html, CSS et JavaScript dans un seul fichier.

    Cet article est plus un point de départ pour apprendre le CMF qu’un exemple de quelque chose de vraiment correct que vous pouvez utiliser comme base pour votre application Web. Peut-être que cela vous a même inspiré et que vous envisagez déjà d'écrire votre propre microframework ou CMS basé sur MVC. Mais, avant de réinventer la roue suivante avec « blackjack et putes », détrompez-vous : peut-être serait-il plus raisonnable d'orienter vos efforts vers le développement et l'aide à la communauté d'un projet déjà existant ?!

    P.S. : L'article a été réécrit en tenant compte de certains commentaires laissés dans les commentaires. La critique s’est avérée très utile. À en juger par la réponse : commentaires, MP et nombre d'utilisateurs qui ont ajouté le message aux favoris, l'idée d'écrire ce message s'est avérée pas si mauvaise. Malheureusement, il n'est pas possible de prendre en compte tous les souhaits et d'écrire de plus en plus en détail par manque de temps... mais peut-être que ces mystérieux individus qui ont voté contre la version originale le feront. Bonne chance avec vos projets!

    5. Une sélection de liens utiles sur le sujet

    L'article aborde très souvent le sujet des frameworks Web - c'est un sujet très vaste, car même les microframeworks sont constitués de nombreux composants intelligemment interconnectés et il faudrait plus d'un article pour parler de ces composants. Cependant, j'ai décidé de présenter ici une petite sélection de liens (que j'ai suivis en écrivant cet article) qui se rapportent d'une manière ou d'une autre au thème des frameworks.

    Modifier le fichier URL.py applications compte:

    à partir de django.conf.urls, importez l'URL à partir de . importer des vues urlpatterns = [ # vue de connexion précédente # url(r"^login/$", vues.user_login, name="login"),# URL de connexion/déconnexion url(r"^login/$" , "django.contrib.auth.views.login", nom="connexion" ), url(r"^logout/$" , "django.contrib.auth.views.logout", nom="logout" ), url(r"^logout-then-login/$" , "django.contrib.auth.views.logout_then_login", nom="logout_then_login" ), ]

    Nous avons commenté le modèle d'URL pour la vue Utilisateur en ligne, créé précédemment pour utiliser la vue se connecter Django.

    Créez un nouveau répertoire dans le répertoire des modèles d'application compte et nomme-le inscription. Créez un nouveau fichier dans un nouveau répertoire, nommez-le connexion.html

    (% extends "base.html" %) (% titre du bloc %)Connexion(% endblock %) (% contenu du bloc %)

    Se connecter

    (% si erreurs de formulaire %)

    Votre nom d'utilisateur et votre mot de passe ne correspondent pas. Veuillez réessayer.

    (%autre%)

    Veuillez utiliser le formulaire suivant pour vous connecter :

    (% fin si %) (%bloc final%)

    Ce modèle de connexion est très similaire à celui créé précédemment. Django utilise Formulaire d'authentification, situé dans django.contrib.auth.forms. Ce formulaire tente d'authentifier l'utilisateur et génère une erreur de validation si le nom d'utilisateur était incorrect. Dans ce cas, nous pouvons rechercher les erreurs à l'aide de la commande (% if form.errors %) . Veuillez noter que nous avons ajouté un élément caché pour envoyer la valeur d'une variable nommée suivant.

    Paramètre suivant doit être une URL. Si ce paramètre est spécifié, une fois que l'utilisateur s'est connecté, il est redirigé vers l'URL spécifiée.

    Créez maintenant un modèle déconnecté_out.html dans le répertoire des modèles inscription et collez-y le code suivant :

    (% extends "base.html" %) (% titre du bloc %)Déconnecté(% endblock %) (% contenu du bloc %)

    Déconnecté

    Vous avez été déconnecté avec succès. Vous pouvez vous reconnecter.

    (%bloc final%)

    Il s'agit du modèle qui sera affiché une fois que l'utilisateur se connectera.

    Après avoir ajouté les modèles d'URL et les modèles pour les vues d'entrée et de sortie, le site est prêt pour les connexions à l'aide des vues d'authentification de Django.

    Veuillez noter que la présentation logout_then_login inclus dans notre urlconf, n'a pas besoin de modèle puisqu'il redirige vers se connecter voir.

    Créons maintenant une nouvelle vue pour afficher un tableau de bord pour l'utilisateur afin que nous sachions quand l'utilisateur se connecte à son compte. Ouvrir le fichier vues.py applications compte et ajoutez-y le code suivant :

    depuis django.contrib.auth.decorators import login_required @login_required tableau de bord def (demande) : return render(request, "account/dashboard.html" , ("section" : "dashboard" ))

    Nous ajoutons un décorateur à notre vue Connexion requise cadre d'authentification. Décorateur Connexion requise vérifie si l'utilisateur actuel est authentifié. Si l'utilisateur est authentifié, la soumission sera exécutée ; Si l'utilisateur n'est pas authentifié, il sera redirigé vers la page de connexion.

    Nous avons également défini une variable section. Nous allons utiliser cette variable pour suivre quelle section du site l'utilisateur regarde.

    Vous devez maintenant créer un modèle pour la vue du tableau de bord. Créer un nouveau fichier dans les modèles/compte modèles/compte/ et nomme-le tableau de bord.html :

    (% étend "base.html" %) (% titre du bloc %)Dashboard(% endblock %) (% contenu du bloc %)

    Tableau de bord

    Bienvenue sur votre tableau de bord.

    (%bloc final%)

    Ajoutez ensuite le modèle d'URL suivant pour ce fichier de modification URL.py applications compte:

    Modèles d'URL = [ # ... url(r"^$" , vues.dashboard, name="dashboard" ), ]

    Maintenant, éditez le fichier paramètres.py:

    depuis django.core.urlresolvers import reverse_lazy LOGIN_REDIRECT_URL = reverse_lazy("dashboard" ) LOGIN_URL = reverse_lazy("login" ) LOGOUT_URL = reverse_lazy("logout" )
    • LOGIN_REDIRECT_URL: Indique vers quelle URL rediriger l'utilisateur après la connexion.
    • LOGIN_URL: URL pour rediriger l'utilisateur vers la connexion (par exemple, en utilisant un décorateur Connexion requise)
    • LOGOUT_URL: URL pour rediriger l'utilisateur vers la sortie

    Nous allons maintenant ajouter des liens de connexion et de déconnexion à notre modèle de base.

    Pour ce faire, il est nécessaire de déterminer si l'utilisateur actuel est connecté ou non afin d'afficher un lien correspondant à l'état actuel de l'utilisateur. L'utilisateur actuel est spécifié dans Demande Http objet de la classe intermédiaire d'authentification. On peut y accéder en utilisant demande.utilisateur. La requête trouvera un objet utilisateur, même si l'utilisateur n'est pas authentifié. Utilisateur non authentifié, spécifié dans la demande en tant qu'instance Utilisateur anonyme. La meilleure façon de vérifier l'état d'authentification de l'utilisateur actuel est d'appeler requête.user.is_authenticated()

    Modifier dans le modèle base.html

    avec en-tête ID :

    Comme vous pouvez le constater, le menu du site s'affiche uniquement pour les utilisateurs authentifiés. Nous vérifions également la section actuelle pour ajouter l'attribut de classe sélectionné à l'élément correspondant

  • pour mettre en évidence la section actuelle dans le menu en utilisant CSS. Il affiche également le nom d'utilisateur et un lien pour se déconnecter si l'utilisateur est authentifié, ou un lien pour se connecter.

    Ouvrez http://127.0.0.1:8000/account/login/ dans votre navigateur. Vous devriez voir une page de connexion. Entrez un identifiant et un mot de passe valides. Vous verrez ce qui suit :

    Vous pouvez voir que la section Mon tableau de bord est mise en évidence avec CSS car elle possède une classe choisi. Une fois l'utilisateur authentifié, le nom d'utilisateur est affiché sur le côté droit de l'en-tête. Cliquer sur le lien Se déconnecter. Vous verrez la page suivante :

    Sur cette page, vous pouvez voir que l'utilisateur est déconnecté et que le menu du site Web n'est donc plus affiché. Le lien sur le côté droit de l'en-tête s'affiche désormais Se connecter.

    Si vous voyez la page de déconnexion du site d'administration de Django plutôt que votre propre page de déconnexion, vérifiez vos paramètres INSTALLED_APPS et assurez-vous que django.contrib.admin c'est après compte. Les deux modèles se trouvent dans le même chemin relatif et le chargeur de modèles Django utilisera le premier qu'il trouvera.

    Cet exemple montre comment se déconnecter automatiquement avec la configuration de sécurité Spring par défaut.

    Pour se déconnecter, il suffit d'accéder à l'URL "/logout" avec une requête POST.

    Dans le formulaire POST/logout, nous devons également inclure le jeton CSRF, qui est une protection contre les attaques CSRF.

    Voyons l'exemple comment procéder.

    Classe de configuration Java

    @Configuration @EnableWebSecurity @EnableWebMvc @ComponentScan public class AppConfig étend WebSecurityConfigurerAdapter ( protected void configure(HttpSecurity http) lève une exception ( http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin(); ) @Override public void configure (AuthenticationManagerBuilder builder) lève une exception ( builder.inMemoryAuthentication() .withUser("joe") .password("123") .roles("ADMIN"); ) @Bean public ViewResolver viewResolver() ( InternalResourceViewResolver viewResolver = new InternalResourceViewResolver (); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); return viewResolver; ) )

    Notez que, dans la configuration ci-dessus, nous remplaçons également configure(HttpSecurity http) pour omettre l'authentification de base par défaut (voir la méthode d'origine dans le code source de WebSecurityConfigurerAdapter) et utiliser l'authentification basée sur un formulaire. Nous le faisons parce que les navigateurs mettent en cache les informations d'authentification de base de manière agressive (après la première connexion réussie) et qu'il n'existe aucun moyen de déconnecter l'utilisateur dans la session en cours. Dans la plupart des exemples, nous n'utiliserons pas le mécanisme d'authentification de base.

    Un contrôleur

    @Controller public class ExempleController ( @RequestMapping("/") public String handleRequest2(ModelMap map) ( map.addAttribute("time", LocalDateTime.now().toString()); return "ma-page"; ) )

    La page JSP

    src/main/webapp/WEB-INF/views/my-page.jsp

    Exemple de sécurité Spring

    Heure : $(heure)



    Pour essayer des exemples, exécutez Tomcat intégré (configuré dans pom.xml de l'exemple de projet ci-dessous) :

    Mvn tomcat7:run-war

    Sortir

    L'accès initial à l'URI "/" sera redirigé vers "/login" :

    Après avoir soumis le nom d'utilisateur et le mot de passe lors de la configuration dans notre classe AppConfig :

    En cliquant sur le bouton « Déconnexion » :


    Exemple de projet

    Dépendances et technologies utilisées :

    • spring-security-web 4.2.3.RELEASE : spring-security-web.
    • spring-security-config 4.2.3.RELEASE : spring-security-config.
    • spring-webmvc 4.3.9.RELEASE : Spring Web MVC.
    • javax.servlet-api 3.1.0 API de servlet Java
    • JDK 1.8
    • Maven3.3.9


  • AnnéeProjetDescription
    " .$ligne ["Année" ]."" .$ligne ["Site" ]."" .$ligne ["Description" ]."