Представления log in и log out. Паттерн Model-Update-View и зависимые типы Усердие logout html view

This example demonstrates how to automatically logout with default Spring security configuration.

To logout, we just need to access URL "/logout" with POST request.

In the POST /logout form, we also need to include the CSRF token, which is a protection against CSRF attack .

Let"s see the example how to do that.

Java Config class

@Configuration @EnableWebSecurity @EnableWebMvc @ComponentScan public class AppConfig extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin(); } @Override public void configure(AuthenticationManagerBuilder builder) throws 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; } }

Note that, in above configuration, we are also overriding configure(HttpSecurity http) to omit the default Basic Authentication (see the original method in WebSecurityConfigurerAdapter source code) and use form based Authentication. We are doing so because browsers cache the Basic Authentication information aggressively (after the first successful login) and there is no way to logout the user in the current session. In most of the examples, we will not be using Basic Authentication mechanism.

A controller

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

The JSP page

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

Spring Security Example

Time: ${time}

To try examples, run embedded tomcat (configured in pom.xml of example project below):

Mvn tomcat7:run-war

Output

Initial access to URI "/" will redirect to "/login":

After submitting user name and password as we setup in our AppConfig class:

Clicking on "Logout" button:


Example Project

Dependencies and Technologies Used:

  • 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 Java Servlet API
  • JDK 1.8
  • Maven 3.3.9

В основном для разработки пользовательских интерфейсов. Что бы им воспользоваться надо создать тип Model, представляющий полное состояние программы, тип Message, описывающий события внешней среды, на которые программа должна реагировать, меняя свое состояние, функцию updater, которая из старого состояния и сообщения создает новое состояние прораммы и функции view, которая вычисляет по состоянию программы требуемые воздействия на внешнюю среду, которые порождают события типа Message. Паттерн очень удобный, но у него есть маленький недостаток - он не позволяет описать какие события имеют смысл для конкретных состояний программы.

Схожая проблема возникает (и решается) и при использовании ОО-паттерна State.

Язык Elm простой, но очень строгий - он проверяет, что функция updater хоть как-то обрабатывает все возможные сочетания модели-состояние и сообщения-события. По этому приходится писать лишний, пусть и тривиальный - как правило оставляющий модель без изменений, код. Я хочу продемонстрировать, как этого можно избежать в более сложных языках - Idris, Scala, C++ и Haskell.

Весь приведенный здесь код доступен на GitHub для экспериментов. Рассмотрим наиболее интересные места.


Функция msg необычна - она возвращает не значение, а тип. Во время исполнения про типы значений ни чего не известно - компилятор выполняет стирание всей лишней информации. То есть такая функция может быть вызвана только на этапе компиляции.

MUV - это конструктор. Он принимает параметры: model - начальное состояние программы, updater - функция обновления состояния при внешнем событии, и view - функция создания внешнего представления. Заметьте что тип функций updater и view зависит от значения модели (с помощью функции msg из параметров типа).

Теперь посмотрим, как это приложение запустить

MuvRun: (Application modelType msgType IO) -> IO a muvRun (MUV model updater view) = do msg <- view model muvRun (MUV (updater model msg) updater view)
В качестве внешнего представления (view) мы выбрали операцию ввода/вывода (в Idris, как и в Haskell, операции ввода/вывода - first class values, что бы они выполнились надо предпринять дополнительные действия, обычно вернуть такую операцию из функции main).

Кратко об IO

При выполнении операции типа (IO a) происходит некоторое воздействие на внешний мир, возможно пустое, и в программу возвращается значение типа a, но функции стандартной библиотеки устроены так, что обработать его можно только порождая новое значение типа IO b. Таким образом чистые функции отделены от функций с побочными эффектами. Это непривычно многим программистам, но помогает писать более надежный код.


Так как функция muvRun порождает ввод/вывод, она должна вернуть IO, но так как она ни когда не завершиться, тип операции может быть любой - IO a.

Теперь опишем типы сущностей, с которыми мы собираемся работать

Data Model = Logouted | Logined String data MsgOuted = Login String data MsgIned = Logout | Greet total msgType: Model -> Type msgType Logouted = MsgOuted msgType (Logined _) = MsgIned
Здесь описан тип модели, отражающий наличие двух состояний интерфейса - пользователь не залогинен, и залогинен пользователь с именем типа String.

Далее мы описывает два различных типов сообщений, релевантрых для разных вариантов модели - если мы разлогинены, то мы можем только залогиниться под некоторым именем, а если уже залогинены, то можем либо разлогиниться, либо поздороваться. Idris - строго типизированный язык, который не допустит возможности перепутать разные типы.

И наконец функция, задающая соответствие значения модели типу сообщения.

Функция объявлена тотальной - то есть она не должна упасть или зависнуть, компилятор постарается за этим проследить. msgType вызывается на этапе компиляции, а значит ее тотальность означает, что компиляция не зависнит из-за нашей ошибки, хотя и не может гарантировать, что выполнение этой функции приведет к исчерпанию ресурсов системы.
Так же гарантировано, что она не выполнит «rm -rf /», потому что в ее сигнатуре нет IO.

Опишем updater:

Total updater: (m:Model) -> (msgType m) -> Model updater Logouted (Login name) = Logined name updater (Logined name) Logout = Logouted updater (Logined name) Greet = Logined name
Думаю логика этой функции понятна. Хочу еще раз отметить тотальность - она означает что компилятор Idris проверит, что мы рассмотрели все разрешенные системой типов альтернативы. Elm тоже осуществляет такую проверку, но он не может знать, что мы не можем разлогиниться, если еще не залогинены, и потребует явную обработку условия

Updater Logouted Logout = ???
Idris же в лишней проверки найдет несоотвествие типов.

Теперь приступим к view - как обычно в UI это будет самой сложной частью кода.

Total loginPage: IO MsgOuted loginPage = do putStr "Login: " map Login getLine total genMsg: String -> MsgIned genMsg "" = Logout genMsg _ = Greet total workPage: String -> IO MsgIned workPage name = do putStr ("Hello, " ++ name ++ "\n") putStr "Input empty string for logout or nonempty for greeting\n" map genMsg getLine total view: (m: Model) -> IO (msgType m) view Logouted = loginPage view (Logined name) = workPage name
view должна создавать операцию ввода/вывода, которая возвращает сообщения, тип которого снова зависит от значения модели. У нас есть два варианта: loginPage, который выводит сообщение «Login:», читает строку с клавиатуры и заворачивает ее в сообщение Login и workPage с параметром именем пользователя, который выводит приветсвие и возвращает различные сообщения (но одинакового типа - MsgIned) в зависимости от того, введет пользоваль пустую или не пустую строку. view возвращает одну из этих операций в зависимости от значения модели, и компилятор проверяет их тип, несмотря на то, что он разный.

Теперь мы можем создать и запустить наше приложение

App: Application Model Main.msgType IO app = MUV Logouted updater view main: IO () main = muvRun app
Здесь надо отметить тонкий момент - функция muvRun возврящает IO a , где a не было специфицировано, а значение main имеет тип IO () , где () - это имя типа, обычно называемого Unit , у которого есть единственное значение, тоже записываемое как пустой тупл () . Но компилятор с этим легко справляется. подставив вместо a ().

Scala и зависимые от пути типы

В Scala нет полноценной поддержки зависимых типов, но есть типы, зависимые от экземпляра объекта, через который на него ссылаются (path dependent types). В теории зависимых типов их можно описать как вариант сигма-типа. Зависимые от пути типы позволяют запретить складывать вектора из разных векторных пространств, или описать кому с кем можно целоваться . Но мы их применим для более простых задач.

Sealed abstract class MsgLogouted case class Login(name: String) extends MsgLogouted sealed abstract class MsgLogined case class Logout() extends MsgLogined case class Greet() extends MsgLogined abstract class View { def run() : Msg } sealed abstract class Model { type Message def view() : View } case class Logouted() extends Model { type Message = MsgLogouted override def view() : View .... } case class Logined(name: String) extends Model { type Message = MsgLogined override def view() : View .... }
Алгебраические типы в Scala моделируются через наследование. Типу соотвествует некоторый sealed abstract class , а каждому конструктору унаследованный от него case class . Мы будем стараться их использовать именно как алгебраические типы, описывая все переменные как принадлежащие к родительскому sealed abstract class .

Классы MsgLogined и MsgLogouted в рамках нашей программы не имеют общего предка. Функцию view пришлось размазать по разным классам модели, что бы иметь доступ к конкретному типу сообщений. В этом есть свои плюсы, которые оценят сторонники ОО - код получается сгруппирован в соотвествии с бизнес-логикой, все что связано с одним use case оказывается рядом. Но мне бы больше понравилось выделить view в отдельную функцию, разработку которой можно было бы передать другому человеку.

Теперь реализуем updater

Object Updater { def update(model: Model)(msg: model.Message) : Model = { model match { case Logouted() => msg match { case Login(name) => Logined(name) } case Logined(name) => msg match { case Logout() => Logouted() case Greet() => model } } } }
Здесь мы, используя зависимые от пути типы, описываем тип второго аргумента от значения первого. Что бы Scala воспринимала подобные зависимости, функции приходится описывать в карррированном виде, то есть в виде функции от первого аргумента, которая возвращает функцию от второго аргумента. К сожалению, Scala в этом месте не осуществляет многих проверок типов, для которых у компилятора достаточно информации.

Теперь дадим полную реализацию модели и view

Case class Logouted() extends Model { type Message = MsgLogouted override def view() : View = new View { override def run() = { println("Enter name ") val name = scala.io.StdIn.readLine() Login(name) } } } case class Logined(name: String) extends Model { type Message = MsgLogined override def view() : View = new View { override def run() = { println(s"Hello, $name") println("Empty string for logout, nonempy for greeting.") scala.io.StdIn.readLine() match { case "" => Logout() case _ => Greet() } } } } abstract class View { def run() : Msg } object Viewer { def view(model: Model): View = { model.view() } }
Тип возвращаемого функцией view зависит от экземпляра ее аргумента. Но за реализацией она обращается в модель.

Запускается созданное так приложение так

Object Main { import scala.annotation.tailrec @tailrec def process(m: Model) { val msg = Viewer.view(m).run() process(Updater.update(m)(msg)) } def main(args: Array) = { process(Logouted()) } }
Код runtime-системы, таким образом, ни чего не знает о внутреннем устройстве моделий и типах сообщений, но компилятор может проверить что сообщение подходит к текущей модели.

Здесь нам понадобились не все возможности, продоставляемые зависимыми от пути типами. Интересные свойства проявятся, если мы будем параллельно работать с неколькими экземплярами систем Model-Updater-View, например при симуляции многоагентного мира (view тогда бы представлял из себя воздействие агента на мир и получение обратной связи). В этом случае компилятор проверял, что сообщение обрабатывается именно тем агентом, для которого преднозначено, несмотря на то, что все агенты имеют одинаковый тип.

С++

С++ до сих пор чувствителен к порядку определений, даже если они все сделаны в одном файле. Это создает некоторые неудобства. Я буду приводить код в удобной для демонстрации идей последовательнсоти. Упорядоченную для компилируемости версию можно посмотреть на GitHub .

Алгебраические типы могут быть реализованы так же, как в Scala - абстрактный класс соответствует типу, а конкретные наследники - конструкторам (назовем их «классами-конструкторами», что бы не путать с обычными конструкторами C++) алгебраического типа.

В C++ есть поддержка зависимых от пути типов, но компилятор не может использовать этот тип абстрактно, не зная реального типа, с которым он связан. По этому реализовать Model-Updater-View с их помощью не получается.

Но C++ располагает мощной системой шаблонов. Зависимость типа от значения модели можно спрятав его в шаблонный параметр специализированной версии исполнительной системы.

Struct Processor { virtual const Processor *next() const = 0; }; template struct ProcessorImpl: public Processor { const CurModel * model; ProcessorImpl(const CurModel* m) : model(m) { }; const Processor *next() const { const View * view = model->view(); const typename CurModel::Message * msg = view->run(); delete view; const Model * newModel = msg->process(model); delete msg; return newModel->processor(); } };
Мы описываем абстрактную исполнительную систему, с единственным методом - выполнить все, что требуется, и вернуть новую исполнительную систему, подходящую для следующей итерации. Конкреная версия имеет шаблонный параметр и будет специализирована для каждого «класса-конструктора» модели. Здесь важно, что все свойства типа CurModel будут проверены во время специализации шаблона конкретным параметром-типом, а на момент компиляции самого шаблона их описывать не требуется (хотя и возможно с помощью концептов или других способов реализации классов типов). Scala тоже имеет достаточно мощную систему параметризованных типов, но проверки свойств типов-параметров она осуществляет во время компиляции параметризованного типа. Там реализация такого паттерна затруднена, но возможна, благодоря поддержке классов типов.

Опишем модель.

Struct Model { virtual ~Model() {}; virtual const Processor *processor() const = 0; }; struct Logined: public Model { struct Message { const virtual Model * process(const Logined * m) const = 0; virtual ~Message() {}; }; struct Logout: public Message { const Model * process(const Logined * 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 { ... }; const View * view() const { return new LoginedView(name); }; const Processor *processor() const { return new ProcessorImpl(this); }; }; struct Logouted: public Model { struct Message { const virtual Model * process(const Logouted * m) const = 0; virtual ~Message() {}; }; struct Login: public Message { const std::string name; Login(std::string lname) : name(lname) { }; const Model * process(const Logouted * m) const; }; struct LogoutedView: public View { ... }; const View * view() const { return new LogoutedView(); }; const Processor *processor() const { return new ProcessorImpl(this); }; };
«Классы-конструкторы» модели «все свое носят с собой» - то есть содержат спициализированные для них классы сообщений и view, а так же умеют создавать исполнительную систему под себя. Собственные типы View имеют общего для всех моделей предка, что может оказаться полезно при разработке более сложных исполнительных систем. Принципиально что типы сообщений полностью изолированы и не имеют общего предка.

Реализация updater отделена от модели, поскольку требует что бы тип модели был уже полностью описан.

Const Model * Logouted::Login::process(const Logouted * m) const { delete m; return new Logined(name); }; const Model * Logined::Logout::process(const Logined * m) const { delete m; return new Logouted(); }; const Model * Logined::Greet::process(const Logined * m) const { return m; };
Теперь соберем вместе все, что относится к view, включая внутренние сущности моделей

Template struct View { virtual const Message * run() const = 0; virtual ~View() {}; }; struct Logined: public Model { struct LoginedView: public View { const std::string name; LoginedView(std::string lname) : name(lname) {}; virtual const Message * run() const { char buf; printf("Hello %s", name.c_str()); fgets(buf, 15, stdin); return (*buf == 0 || *buf == "\n" || *buf == "\r") ? static_cast(new Logout()) : static_cast(new Greet); }; }; const View * view() const { return new LoginedView(name); }; }; struct Logouted: public Model { struct LogoutedView: public View { virtual const Message * run() const { char buf; printf("Login: "); fgets(buf, 15, stdin); return new Login(buf); }; }; const View * view() const { return new LogoutedView(); }; };
И, наконец, напишем main

Int main(int argc, char ** argv) { const Processor * p = new ProcessorImpl(new Logouted()); while(true) { const Processor * pnew = p->next(); delete p; p = pnew; } return 0; }

И снова Scala, уже с классами типов

По структуре эта реализация почти полностью повторяет версию на C++.

Аналогичная часть кода

abstract class View { def run(): Message } abstract class Processor { def next(): Processor; } sealed abstract class Model { def processor(): Processor } sealed abstract class LoginedMessage case class Logout() extends LoginedMessage case class Greet() extends LoginedMessage case class Logined(val name: String) extends Model { override def processor(): Processor = new ProcessorImpl(this) } sealed abstract class LogoutedMessage case class Login(name: String) extends LogoutedMessage case class Logouted() extends Model { override def processor(): Processor = new ProcessorImpl(this) } object Main { import scala.annotation.tailrec @tailrec def process(p: Processor) { process(p.next()) } def main(args: Array) = { process(new ProcessorImpl(Logouted())) } }


А вот в реализации среды исполнения возникают тонкости.

Class ProcessorImpl(model: M)(implicit updater: (M, Message) => Model, view: M => View) extends Processor { def next(): Processor = { val v = view(model) val msg = v.run() val newModel = updater(model,msg) newModel.processor() } }
Здесь мы видим новые таинственные параметры (implicit updater: (M, Message) => Model, view: M => View) . Ключевое слово implicit означает что компилятор при вызове этой функции (точнее конструктора класса) будет искать в контексте помечанные как implicit объекты подходящих типов и передавать их в качестве соответствующих параметров. Это достаточно сложная концепция, одно их применений которой - реализация классов типов. Здесь они обещают компилятору, что для конкретных реализаций модели и сообщения все необходимые функции нами будут предоставлены. Теперь выполним это обещание.

Object updaters { implicit def logoutedUpdater(model: Logouted, msg: LogoutedMessage): Model = { (model, msg) match { case (Logouted(), Login(name)) => Logined(name) } } implicit def viewLogouted(model: Logouted) = new View { override def run() : LogoutedMessage = { println("Enter name ") val name = scala.io.StdIn.readLine() Login(name) } } implicit def loginedUpdater(model: Logined, msg: LoginedMessage): Model = { (model, msg) match { case (Logined(name), Logout()) => Logouted() case (Logined(name), Greet()) => model } } implicit def viewLogined(model: Logined) = new View { val name = model.name override def run() : LoginedMessage = { println(s"Hello, $name") println("Empty string for logout, nonempy for greeting.") scala.io.StdIn.readLine() match { case "" => Logout() case _ => Greet() } } } } import updaters._

Haskell

В мейнстримовом Haskell нет зависимых типов. В нем так же отсутствиет наследование, которое мы существенно применяли при реализации паттерна в Scala и C++. Но одноуровневое наследование (с элементами зависимых типов) может быть смоделировано с помощью более-менее стандандартных расширений языка -TypeFamilies и ExistentialQuantification. Для общего интерфейса дочерних ООП-классов заводится класс типов, в котором присутствует зависимый «семейный» тип, сами дочерние классы представляются отдельным типом, а потом заворачиваются в «экзистенциональный» тип с единственным конструктором.

Data Model = forall m. (Updatable m, Viewable m) => Model m class Updatable m where data Message m:: * update:: m -> (Message m) -> Model class (Updatable m) => Viewable m where view:: m -> (View (Message m)) data Logouted = Logouted data Logined = Logined String
Я попытался разнести updater и view как можно дальше, по этому создал два разных класса типов, но пока это плохо получилось.

Реализация updater проста

Instance Updatable Logouted where data Message Logouted = Login String update Logouted (Login name) = Model (Logined name) instance Updatable Logined where data Message Logined = Logout | Greeting update m Logout = Model Logouted update m Greeting = Model m
В качестве View пришлось зафиксировать IO. Попытки сделать его более абстрактным сильно все усложняли и повышали связанность кода - тип Model должен знать, какой именно View мы собираемся использовать.

Import System.IO type View a = IO a instance Viewable Logouted where view Logouted = do putStr "Login: " hFlush stdout fmap Login getLine instance Viewable Logined where view (Logined name) = do putStr $ "Hello " ++ name ++ "!\n" hFlush stdout l <- getLine pure $ if l == "" then Logout else Greeting
Ну и исполняемая среда мало отличается от аналогичной в Idris

RunMUV:: Model -> IO a runMUV (Model m) = do msg <- view m runMUV $ update m msg main:: IO () main = runMUV (Model Logouted)

Отредактируйте файл urls.py приложения account :

from django.conf.urls import url from . import views urlpatterns = [ # previous login view # url(r"^login/$", views.user_login, name="login"), # login / logout urls url(r"^login/$" , "django.contrib.auth.views.login" , name="login" ), url(r"^logout/$" , "django.contrib.auth.views.logout" , name="logout" ), url(r"^logout-then-login/$" , "django.contrib.auth.views.logout_then_login" , name="logout_then_login" ), ]

Мы заккоментировали шаблон URL-адреса для представления user_login , созданного ранее для использования представления login Джанго.

Создайте новый каталог в каталоге шаблонов приложения account и назовите его registration. Создайте новый файл в новом каталоге, назовите его login.html

{% extends "base.html" %} {% block title %}Log-in{% endblock %} {% block content %}

Log-in

{% if form.errors %}

Your username and password didn"t match. Please try again.

{% else %}

Please, use the following form to log-in:

{% endif %} {% endblock %}

Этот шаблон для входа очень похож на созданный ранее. Джанго использует AuthenticationForm , расположенную в django.contrib.auth.forms . Эта форма пытается проверить подлинность пользователя и порождает ошибку проверки, если имя пользователя было не верно. В этом случае мы можем искать ошибки с помощью команды {% if form.errors %} . Обратите внимание, что мы добавили скрытый элемент для отправки значения переменной с именем next .

Параметр next должен быть URL-адресом. Если этот параметр указан, то после входа пользователя в систему он перенаправляется на заданный URL-адрес.

Теперь создайте шаблон logged_out.html внутри каталога шаблона registration и вставьте в него следующий код:

{% extends "base.html" %} {% block title %}Logged out{% endblock %} {% block content %}

Logged out

You have been successfully logged out. You can log-in again.

{% endblock %}

Это шаблон, который будет отображаться после входа пользователя в систему.

После добавления шаблонов URL-адресов и шаблонов для входных и выходных представлений сайт готов к входу в систему с использованием представлений аутентификации Джанго.

Обратите внимание, что представление logout_then_login , включенное в наш urlconf , не нуждается в шаблоне, поскольку он выполняет перенаправление на log in view .

Теперь создадим новый view для отображения панели мониторинга для пользователя, чтобы знать когда пользователь войдет в свою учетную запись. Откройте файл views.py приложения account и добавьте в него следующий код:

from django.contrib.auth.decorators import login_required @login_required def dashboard (request) : return render(request, "account/dashboard.html" , {"section" : "dashboard" })

Мы добавляем в наше представление декоратор login_required authentication framework. Декоратор login_required проверяет, прошел ли текущий пользователь аутентификацию. Если пользователь прошел аутентификацию, рпедставление выполнится; Если пользователь не прошел аутентификацию, он будет перенаправлян на страницу входа.

Мы также определили переменную section . Мы собираемся использовать эту переменную для отслеживания того раздела сайта, за которым наблюдает пользователь.

Теперь необходимо создать шаблон для представления панели мониторинга. Создайте новый файл внутри шаблонов/учетной templates/account/ и назовите его dashboard.html :

{% extends "base.html" %} {% block title %}Dashboard{% endblock %} {% block content %}

Dashboard

Welcome to your dashboard.

{% endblock %}

Затем добавьте следующий шаблон URL-адреса для этого измените файл urls.py приложения account :

Urlpatterns = [ # ... url(r"^$" , views.dashboard, name="dashboard" ), ]

Теперь отредактируйте файл settings.py :

from 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 : Сообщает о том, на какой URL-адрес перенаправлять пользователя после входа в систему.
  • LOGIN_URL : URL-адрес для перенаправления пользователя на вход (например, с помощью декоратора login_required )
  • LOGOUT_URL : URL-адрес для перенаправления пользователя на выход

Теперь мы собираемся добавить к нашему базовому шаблону ссылки на вход и выход с сайта.

Для этого необходимо определить, вошел ли текущий пользователь в систему или нет, чтобы отобразить соответствующую текущему состоянию пользователя, ссылку. Текущий пользователь задается в HttpRequest объекте промежуточного класса authentication. Доступ к нему можно получить с помощью request.user . В запросе будет найден объект user, даже если user не прошел аутентификацию. Неаутентифицированный пользователь, задается в запросе в качестве экземпляра AnonymousUser . Наилучший способ проверки состояния аутентификаци текущего пользователя — вызов request.user.is_authenticated()

Отредактируйте в шаблоне base.html

с ID header:

Как можно видеть, меню сайта отображается только для пользователей, прошедших аутентификации. Мы также проверяем текущий раздел, чтобы добавить выбранный атрибут класса к соответствующему элементу

  • , чтобы выделить текущий раздел в меню с помощью CSS. Также отображается имя пользователя и ссылка для выхода в систему, если пользователь прошел аутентификацию, или ссылка для входа в систему.

    Откройте в браузере http://127.0.0.1:8000/account/login/ . Вы должны увидеть стриницу входа. Введите валидный логин и пароль. Вы увидите следующее:

    Можно увидеть, что раздел My dashboard выделен с помощью CSS, так как он имеет class selected . Поскольку пользователь прошел аутентификацию, имя пользователя отображается в правой части header-а. Щелкните ссылку Logout . Вы увидите следующую страницу:

    На этой странице можно увидеть, что пользователь вышел из системы, и поэтому больше не отображается меню веб-сайта. Ссылка в правой стороне хедера показывает теперь Log-in .

    Если вы видите страницу log out из сайта администрирования Джанго, а не собственную страницу выхода из системы, проверьте настройки INSTALLED_APPS и убедитесь, что django.contrib.admin находится после account . Оба шаблона находятся в том же относительном пути, и загрузчик шаблона Джанго будет использовать первый найденный.

    Django comes with a lot of built-in resources for the most common use cases of a Web application. The registration app is a very good example and a good thing about it is that the features can be used out-of-the-box.

    With the Django registration app you can take advantages of the following features:

    • Login
    • Logout
    • Sign up
    • Password reset

    In this tutorial we will focus in the Login and Logout features. For sign up and password reset, check the tutorials below:

    Getting started

    Before we start, make sure you have django.contrib.auth in your INSTALLED_APPS and the authentication middleware properly configured in the MIDDLEWARE_CLASSES settings.

    Both come already configured when you start a new Django project using the command startproject . So if you did not remove the initial configurations you should be all set up.

    In case you are starting a new project just to follow this tutorial, create a user using the command line just so we can test the login and logout pages.

    $ python manage.py createsuperuser

    In the end of this article I will provide the source code of the example with the minimal configuration.

    Configure the URL routes

    First import the django.contrib.auth.views module and add a URL route for the login and logout views:

    from django.conf.urls import url from django.contrib import admin from django.contrib.auth import views as auth_views urlpatterns = [ url (r"^login/$" , auth_views . login , name = "login" ), url (r"^logout/$" , auth_views . logout , name = "logout" ), url (r"^admin/" , admin . site . urls ), ]

    Create a login template

    By default, the django.contrib.auth.views.login view will try to render the registration/login.html template. So the basic configuration would be creating a folder named registration and place a login.html template inside.

    Following a minimal login template:

    {% extends "base.html" %} {% block title %}Login{% endblock %} {% block content %}

    Login

    {% csrf_token %} {{ form.as_p }}
    {% endblock %}

    This simple example already validates username and password and authenticate correctly the user.

    Customizing the login view

    There are a few parameters you can pass to the login view to make it fit your project. For example, if you want to store your login template somewhere else than registration/login.html you can pass the template name as a parameter:

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

    You can also pass a custom authentication form using the parameter authentication_form , incase you have implemented a custom user model.

    Now, a very important configuration is done in the settings.py file, which is the URL Django will redirect the user after a successful authentication.

    Inside the settings.py file add:

    LOGIN_REDIRECT_URL = "home"

    The value can be a hardcoded URL or a URL name. The default value for LOGIN_REDIRECT_URL is /accounts/profile/ .

    It is also important to note that Django will try to redirect the user to the next GET param.

    Setting up logout view

    After acessing the django.contrib.auth.views.logout view, Django will render the registration/logged_out.html template. In a similar way as we did in the login view, you can pass a different template like so:

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

    Usually I prefer to use the next_page parameter and redirect either to the homepage of my project or to the login page when it makes sense.

  • В продолжение темы:
    Интернет, Wi-Fi

    PDF Reader - это программное обеспечение, предназначенное для просмотра PDF-файлов, которое распространяется совершенно бесплатно. При этом вы сможете не только просматривать...

    Новые статьи
    /
    Популярные