В этом разделе мы создадим систему регистрации и авторизации пользователей, чтобы люди могли создать учетную запись, начать и завершать сеанс работы с приложением. Для всей функциональности, относящейся к работе с пользователями, будет создано отдельное приложение. Мы также слегка изменим модель Topic, чтобы каждая тема была связана с конкретным пользователем.
Приложение users
Начнем с создания нового приложения users командой startapp:
(ll_env)learning_log$ python manage.py startapp users
(ll_env)learning_log$ ls
(1) db.sqlite3 learning_log learning_logs ll_env manage.py users
(ll_env)learning_log$ ls users
(2)admin.py __init__.py migrations models.py tests.py views.py
Эта команда создает новый каталог с именем users (1) , структура которого повторяет структуру каталогов приложения learning_logs (2).
Добавление пользователей в settings.py
Новое приложение необходимо добавить в settings.py:
settings.py
...
INSTALLED_APPS = (
...
# My apps
'learning_logs',
. .'users',
)
...
Django включает приложение users в общий проект.
Включение URL-адресов из users
Затем необходимо изменить корневой файл urls.py, чтобы он включал URL-адреса, написанные для приложения users:
urls.py
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
. .url(r'^users/', include('users.urls', namespace='users')),
url(r'', include('learning_logs.urls', namespace='learning_logs')),
]
Добавим строку для включения файла urls.py из users. Эта строка будет соответствовать любому URL-адресу, начинающемуся со слова users, — например, http://localhost:8000/users/login/. Также будет создано пространство имен 'users', чтобы мы могли отличать URL-адреса, принадлежащие приложению learning_logs, от URL, принадлежащих приложению users.
Страница входа
Начнем с реализации страницы входа. Мы воспользуемся стандартным представлением login, которое предоставляет Django, так что шаблон URL выглядит немного иначе. Создайте новый файл urls.py в каталоге learning_log/users/ и добавьте в него следующий код:
urls.py
"""Определяет схемы URL для пользователей"""
from django.conf.urls import url
(1) from django.contrib.auth.views import login
from . import views
urlpatterns = [
. .# Страница входа
(2) . .url(r'^login/$', login, {'template_name': 'users/login.html'},
. . . .name='login'),
]
Сначала импортируется представление login по умолчанию (1) . Схема страницы входа соответствует URL http://localhost:8000/users/login/ (2). Когда Django читает этот URL-адрес, слово users указывает, что следует обратиться к users/urls.py, а login сообщает о том, что запросы должны отправляться представлению login по умолчанию (обратите внимание: в аргументе представления используется login, а не views.login). Так как мы не пишем собственную функцию представления, мы передаем словарь, который сообщает Django, где искать шаблон (сейчас мы его напишем). Этот шаблон будет частью приложения users, а не приложения learning_logs.
Шаблон login
Когда пользователь запрашивает страницу входа, Django использует свое представление login по умолчанию, но мы все равно должны предоставить шаблон для этой страницы. В каталоге learning_log/users/ создайте каталог с именем templates, а внутри него — еще один каталог с именем users. Вот как выглядит шаблон login.html, который должен находиться в learning_log/users/templates/users/:
login.html
{% extends "learning_logs/base.html" %}
{% block content %}
(1) {% if form.errors %}
Your username and password didn't match. Please try again.
{% endif %}
. .
(2)
. .
{% endblock content %}
Шаблон расширяет base.html, чтобы страница входа по оформлению и поведению была похожа на другие страницы сайта. Обратите внимание: шаблон в одном приложении может расширять шаблон из другого приложения.
Если у формы установлен атрибут errors, выводится сообщение об ошибке (1) . В нем говорится, что комбинация имени пользователя и пароля не соответствует информации, хранящейся в базе данных.
Мы хотим, чтобы представление обработало форму, поэтому аргументу action присваивается URL страницы входа (2). Представление отправляет форму шаблону, мы должны вывести форму (3) и добавить кнопку отправки данных (4). В точке (5) включается скрытый элемент формы 'next'; аргумент value сообщает Django, куда перенаправить пользователя после успешно выполненного входа. В нашем случае пользователь возвращается обратно на домашнюю страницу.
Создание ссылки на страницу входа
Добавим ссылку на страницу входа в base.html, чтобы она присутствовала на каждой странице. Ссылка не должна отображаться, если пользователь уже прошел процедуру входа, поэтому она вкладывается в тег {% if %}:
base.html
Topics -
(1) {% if user.is_authenticated %}
(2) . .Hello, {{ user.username }}.
{% else %}
(3) . .log in
{% endif %}
{% block content %}{% endblock content %}
В системе аутентификации Django в каждом шаблоне доступна переменная user, в которой всегда присутствует атрибут is_authenticated: атрибут равен True, если пользователь прошел проверку, и False в противном случае. Это позволяет вам выводить разные сообщения для проверенных и непроверенных пользователей.
В данном случае мы выводим приветствие для пользователей, выполнивших вход. У проверенных пользователей устанавливается дополнительный атрибут username, который обеспечит личную настройку приветствия и напомнит пользователю о том, что вход был выполнен. В точке (3) выводится ссылка на страницу входа для пользователей, которые еще не прошли проверку.
Использование страницы входа
Учетная запись пользователя уже создана; попробуем ввести данные и посмотрим, работает ли страница. Откройте страницу http://localhost:8000/admin/. Если вы все еще работаете с правами администратора, найдите ссылку выхода в заголовке и щелкните на ней.
После выхода перейдите по адресу http://localhost:8000/users/login/. На экране должна появиться страница входа, похожая на рис. 19.4. Введите имя пользователя и пароль, заданные ранее, и вы снова должны оказаться на странице со списком.
Рис. 19.4. Страница входа
В заголовке страницы должно выводиться сообщение с указанием имени пользователя.
Выход
Теперь необходимо предоставить пользователям возможность выхода из приложения. Мы не будем строить отдельную страницу для выхода; пользователь просто щелкает на ссылке и возвращается к домашней странице. Мы определяем схему URL для ссылки выхода, пишем функцию представления и предоставляем ссылку выхода в base.html.
URL-адрес выхода
Следующий код определяет схему URL для выхода, соответствующую URL http://localhost:8000/users/logout/. Файл users/urls.py выглядит так:
urls.py
...
urlpatterns = [
# Страница входа
...
. .# Страница выхода
. .url(r'^logout/$', views.logout_view, name='logout'),
]
Схема URL отправляет запрос функции logout_view(), имя которой выбрано так, чтобы оно отличалось от имени функции logout(), вызываемой из представления. (Проследите за тем, чтобы изменения вносились в файл users/urls.py, а не в файл learning_log/urls.py.)
Функция представления logout_view()
Функция logout_view() тривиальна: мы просто импортируем функцию Django logout(), вызываем ее, а затем возвращаем пользователя на домашнюю страницу. Откройте файл users/views.py и введите следующий код:
views.py
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
(1) from django.contrib.auth import logout
def logout_view(request):
. ."""Завершает сеанс работы с приложением."""
(2) . .logout(request)
(3) . .return HttpResponseRedirect(reverse('learning_logs:index'))
Мы импортируем функцию logout() из django.contrib.auth (1) . В функции вызывается функция logout() (2), в аргументе которой должен передаваться объект запроса. Затем происходит возврат к домашней странице (3).
Ссылка на представление выхода
Теперь нужно создать ссылку для выхода. Мы включим ее в base.html, чтобы она была доступна на каждой странице, и включим в секцию {% if user.is_authenticated %}, чтобы ссылка была видна только пользователям, уже выполнившим вход:
base.html
...
{% if user.is_authenticated %}
Hello, {{ user.username }}.
. .log out
{% else %}
{% endif %}
...
На рис. 19.5 изображена текущая домашняя страница так, как ее видит пользователь, выполнивший вход. Оформление страницы минимально, потому что сейчас нас в первую очередь интересует работа сайта. Когда необходимые функции заработают, можно переходить к стилевому оформлению сайта и приданию ему более профессионального вида.
Рис. 19.5. Домашняя страница с персональным приветствием и ссылкой для выхода
Страница регистрации
Теперь мы построим страницу для регистрации новых пользователей. Для этой цели мы используем класс Django UserCreationForm, но напишем собственную функцию представления и шаблон.
URL-адрес регистрации
Следующий код предоставляет шаблон URL для страницы регистрации — также в файле users/urls.py:
urls.py
...
urlpatterns = [
# Страница входа
...
. .# Страница регистрации
. .url(r'^register/$', views.register, name='register'),
]
Шаблон соответствует URL http://localhost:8000/users/register/ и отправляет запросы функции register(), которую мы сейчас напишем.
Функция представления register()
Функция представления register() должна вывести пустую форму регистрации при первом запросе страницы регистрации, а затем обрабатывает заполненную форму регистрации при отправке данных. Если регистрация прошла успешно, функция также должна выполнить вход для нового пользователя. Включите следующий код в users/views.py:
views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.contrib.auth import login, logout, authenticate
from django.contrib.auth.forms import UserCreationForm
def logout_view(request):
...
def register(request):
. ."""Регистрирует нового пользователя."""
. .if request.method != 'POST':
. . . .# Display blank registration form.
(1) . . . .form = UserCreationForm()
. .else:
. . . .# Обработка заполненной формы.
(2) . . . .form = UserCreationForm(data=request.POST)
. . . .
(3) . . . .if form.is_valid():
(4) . . . . . .new_user = form.save()
. . . . . .# Выполнение входа и перенаправление на домашнюю страницу.
(5) . . . . . .authenticated_user = authenticate(username=new_user.username,
. . . . . . . .password=request.POST['password1'])
? . . . . . .login(request, authenticated_user)
? . . . . . .return HttpResponseRedirect(reverse('learning_logs:index'))
. .context = {'form': form}
. .return render(request, 'users/register.html', context)
Сначала импортируется функция render(), после чего импортируются функции login() и authenticate() для выполнения входа пользователя, если регистрационная информация верна. Также импортируется класс UserCreationForm по умолчанию. В функции register() мы проверяем, отвечает ли функция на запрос POST. Если нет, создается экземпляр UserCreationForm, не содержащий исходных данных (1) . В случае ответа на запрос POST создается экземпляр UserCreationForm, основанный на отправленных данных (2). Мы проверяем, что данные верны (3); в данном случае что имя пользователя содержит правильные символы, пароли совпадают, а пользователь не пытается вставить вредоносные конструкции в отправленные данные.
Если отправленные данные верны, мы вызываем метод save() формы для сохранения имени пользователя и хеша пароля в базе данных (4). Метод save() возвращает только что созданный объект пользователя, который сохраняется в new_user.
После того как информация пользователя будет сохранена, мы выполняем вход; этот процесс состоит из двух шагов: сначала вызывается функция authenticate() с аргументом new_user.username и паролем (5). При регистрации пользователю предлагается ввести два совпадающих пароля; поскольку данные формы верны, мы знаем, что пароли совпадают, и можем использовать любой из них. В данном случае используется значение, связанное с ключом 'password1' в данных POST формы. Если имя пользователя и пароль верны, метод возвращает проверенный объект пользователя, который сохраняется в authenticated_user. Затем вызывается функция login() с объектами request и authenticated_user ?, которая создает действительный сеанс для нового пользователя.
Наконец, пользователь перенаправляется на домашнюю страницу ?, где приветствие в заголовке сообщает о том, что регистрация прошла успешно.
Шаблон регистрации
Шаблон страницы регистрации похож на шаблон страницы входа. Проследите за тем, чтобы он был сохранен в одном каталоге с login.html:
register.html
{% extends "learning_logs/base.html" %}
{% block content %}
. .
{% endblock content %}
Мы снова используем метод as_p, чтобы инфраструктура Django могла правильно отобразить все поля формы, включая все сообщения об ошибках, если форма была заполнена неправильно.
Создание ссылки на страницу регистрации
Следующий шаг — добавление кода для вывода ссылки на страницу регистрации для любого пользователя, еще не выполнившего вход:
base.html
...
{% if user.is_authenticated %}
Hello, {{ user.username }}.
{% else %}
. .register -
{% endif %}
...
Теперь пользователи, выполнившие вход, получат персональное приветствие и ссылку для выхода. Другие пользователи видят ссылку на страницу регистрации и ссылку для входа. Проверьте страницу регистрации, создав несколько учетных записей с разными именами пользователей.
В следующем разделе доступ к некоторым страницам будет ограничен, чтобы страницы были доступны только для зарегистрированных пользователей. Также необходимо позаботиться о том, чтобы каждая тема принадлежала конкретному пользователю.
Примечание
Такая система регистрации позволяет любому пользователю создать сколько угодно учетных записей Learning Log. Однако некоторые системы требуют, чтобы пользователь подтвердил свою заявку, отправляя сообщение электронной почты, на которое пользователь должен ответить. При таком подходе в системе будет создано меньше спамерских учетных записей, чем в простейшей системе из нашего примера. Но пока вы только учитесь строить приложения, вполне нормально тренироваться на упрощенной системе регистрации вроде используемой нами.
Упражнения
19-2. Учетные записи в блоге: добавьте систему аутентификации и регистрации в проект Blog, работа над которым началась в упражнении 19-1 (с. 419). Проследите за тем, чтобы пользователь, выполнивший вход, видел свое имя где-то на экране, а незарегистрированные пользователи видели ссылку на страницу регистрации.