Главная страница
Медицина
Финансы
Экономика
Биология
Ветеринария
Сельское хозяйство
Юриспруденция
Право
Языкознание
Языки
Логика
Философия
Религия
Этика
Политология
Социология
История
Информатика
Вычислительная техника
Физика
Математика
Промышленность
Энергетика
Искусство
Культура
Химия
Электротехника
Связь
Автоматика
Геология
Экология
Начальные классы
Строительство
образование
Механика
Воспитательная работа
Русский язык и литература
Дошкольное образование
Реферат
Урок
Отчет
Программа
Закон
Курсовая
Задача
Занятие
Решение
Лекция
Протокол
Мэтиз. Изучаем Python. Crash course2 n d e d i t i o na h a n d s o n, p r o j e c t b a s e d i n t r o d u c t i o n t o p r o g r a m m i n g
Скачать 6.2 Mb.
Название
Crash course2 n d e d i t i o na h a n d s o n, p r o j e c t b a s e d i n t r o d u c t i o n t o p r o g r a m m i n g
Дата
28.06.2022
Размер
6.2 Mb.
Формат файла
Имя файла
Мэтиз. Изучаем Python
.pdf
Тип
Документы
#618322
страница
47 из 52
1
...
44
45
46
47
48
49
50
51
52
433
views.py
from django.shortcuts import render, redirect from .models import Topic, Entry from .forms import TopicForm, EntryForm def edit_entry(request, entry_id):
"""Редактирует существующую запись."""
❶
entry = Entry.objects.get(id=entry_id)
topic = entry.topic if request.method != 'POST':
# Исходный запрос; форма заполняется данными текущей записи.
❷
form = EntryForm(instance=entry)
else:
# Отправка данных POST; обработать данные.
❸
form = EntryForm(instance=entry, data=request.POST)
if form.is_valid():
❹
form.save()
❺
return redirect('learning_logs:topic', topic_id=topic.id)
context = {'entry': entry, 'topic': topic, 'form': form}
return render(request, 'learning_logs/edit_entry.html', context)
Сначала необходимо импортировать модель
Entry
. В точке мы получаем объект записи, который пользователь хочет изменить, и тему, связанную с этой записью.
В блоке if
, который выполняется для запроса GET, создается экземпляр
EntryForm с аргументом instance=entry
. Этот аргумент приказывает Django создать форму, заранее заполненную информацией из существующего объекта записи. Пользова- тель видит свои существующие данные и может отредактировать их.
При обработке запроса POST передаются аргументы i n s t a n c e = e n t r y и data=request.POST
. Они приказывают Django создать экземпляр формы на основании информации существующего объекта записи, обновленный данными из request.POST
. Затем проверяется корректность данных формы. Если данные корректны, следует вызов save()
без аргументов . Далее происходит перена- правление на страницу темы , и пользователь видит обновленную версию от- редактированной им записи.
Если отображается исходная форма для редактирования записи или если отправ- ленная форма недействительна, создается словарь context
, а страница строится на базе шаблона edit_entry .html
Шаблон edit_entry
Шаблон edit_entry .html очень похож на new_entry .html
:
edit_entry.html
{% extends "learning_logs/base.html" %}
{% block content %}
434
Глава 19 •
Учетные записи пользователей
{{ topic }}
Edit entry:
❶
{% csrf_token %}
{{ form.as_p }}
❷
save changes
{% endblock content %}
В точке аргумент action отправляет форму функции edit_entry()
для обработ- ки. Идентификатор записи включается как аргумент в тег
{%
url
%}
, чтобы функ- ция представления могла изменить правильный объект записи. Кнопка отправки данных создается с текстом, который напоминает пользователю, что он сохраняет изменения, а не создает новую запись .
Создание ссылки на страницу edit_entry
Теперь необходимо включить ссылку на страницу edit_entry в каждую тему на странице со списком тем:
topic.html
{% for entry in entries %}
{{ entry.date_added|date:'M d, Y H:i' }}
{{ entry.text|linebreaks }}
Edit entry
После даты и текста каждой записи включается ссылка редактирования. Мы ис- пользуем шаблонный тег
{%
url
%}
для определения схемы URL из именованной схемы edit_entry и идентификатора текущей записи в цикле (
entry.id
). Текст ссылки "edit entry"
выводится после каждой записи на странице. На рис. 19.3 по- казано, как выглядит страница со списком тем с этими ссылками.
Приложение Learning Log уже сейчас содержит большую часть необходимой функциональности. Пользователи могут добавлять темы и записи, а также читать любые записи по своему усмотрению. В этом разделе мы реализуем систему реги- страции пользователей, чтобы любой желающий мог создать свою учетную запись в Learning Log и ввести собственный набор тем и записей.
Создание
учетных записей пользователей
435
Рис. 19.3.
Каждая запись снабжается ссылкой для редактирования этой записи
УПРАЖНЕНИЯ
19.1. Блог:
создайте новый проект Django с именем Blog. Создайте в проекте приложение с именем blogs и моделью
BlogPost
. Модель должна содержать такие поля, как title
, text и date_added
. Создайте суперпользователя для проекта и воспользуйтесь административ- ным сайтом для создания пары коротких сообщений. Создайте домашнюю страницу, на ко- торой выводятся все сообщения в хронологическом порядке.
Сделайте одну форму для создания новых сообщений, а другую форму для редактирования существующих сообщений. Заполните формы и убедитесь в том, что они работают.
Создание учетных записей пользователей
В этом разделе мы создадим систему регистрации и авторизации пользователей, чтобы люди могли создать учетную запись, начать и завершать сеанс работы с при- ложением. Для всей функциональности, относящейся к работе с пользователями, будет создано отдельное приложение. Мы также слегка изменим модель
Topic
, чтобы каждая тема была связана с конкретным пользователем.
436
Глава 19 • Учетные записи пользователей
Приложение users
Начнем с создания нового приложения users командой startapp
:
(ll_env)learning_log$
python manage.py startapp users
(ll_env)learning_log$
ls
❶
db.sqlite3 learning_log learning_logs ll_env manage.py users
(ll_env)learning_log$
ls users
❷
__init__.py admin.py apps.py migrations models.py tests.py views.py
Эта команда создает новый каталог с именем users
, структура которого повторяет структуру каталогов приложения learning_logs
.
Добавление пользователей в settings .py
Новое приложение необходимо добавить в settings .py
:
settings.py
INSTALLED_APPS = [
# Мои приложения 'learning_logs',
'users',
# Приложения django по умолчанию.
]
Django включает приложение users в общий проект.
Включение URL-адресов из users
Затем необходимо изменить корневой файл urls .py
, чтобы он включал URL-адреса, написанные для приложения users
:
urls.py
from django.contrib import admin from django.urls import path, include urlpatterns = [
path('admin/', admin.site.urls),
path('users/', include('users.urls')),
path('', include('learning_logs.urls')),
]
Добавим строку для включения файла urls .py из users
. Эта строка будет соответ- ствовать любому URL-адресу, начинающемуся со слова users
, например http://
localhost:8000/users/login/
Создание учетных записей пользователей
437
Страница входа
Начнем с реализации страницы входа. Мы воспользуемся стандартным представле- нием login
, которое предоставляет Django, так что шаблон URL выглядит немного иначе. Создайте новый файл urls .py в каталоге learning_log/users/
и добавьте в него следующий код:
urls.py
"""Определяет схемы URL для пользователей"""
from django.urls import path, include
❶
app_name = 'users'
urlpatterns = [
# Включить URL авторизации по умолчанию.
❷
path('', include('django.contrib.auth.urls')),
]
Сначала импортируется функция path
, а затем функция include для включения ау- тентификационных URL-адресов по умолчанию, определенных Django. Эти URL- адреса по умолчанию включают именованные схемы, такие как 'login'
и 'logout'
Переменной app_name присваивается значение 'users'
, чтобы инфраструктура
Django могла отличить эти URL-адреса от URL-адресов, принадлежащих другим приложениям . Даже URL-адреса по умолчанию, предоставляемые Djano, при включении в файл urls .py приложения users будут доступны через пространство имен users
Схема страницы входа соответствует URL http://localhost:8000/users/login/
. Когда
Django читает этот URL-адрес, слово users указывает, что следует обратиться к users/urls .py
, а login сообщает о том, что запросы должны отправляться пред- ставлению login по умолчанию.
Шаблон login
Когда пользователь запрашивает страницу входа, Django использует свое пред- ставление login по умолчанию, но мы все равно должны предоставить шаблон для этой страницы. Аутентификационные представления по умолчанию ищут шаблоны в каталоге с именем registration
, поэтому вы должны создать этот каталог. В каталоге learning_log/users/
создайте каталог с именем templates
, а внутри него — еще один каталог с именем registration
. Вот как выглядит шаблон login .html
, который должен находиться в learning_log/users/templates/registration/
:
login.html
{% extends "learning_logs/base.html" %}
{% block content %}
❶
{% if form.errors %}
438
Глава 19 • Учетные записи пользователей
Your username and password didn't match. Please try again.
{% endif %}
❷
{% csrf_token %}
❸
{{ form.as_p }}
❹
log in
❺
value="{% url 'learning_logs:index' %}" />
{% endblock content %}
Шаблон расширяет base .html
, чтобы страница входа по оформлению и поведению была похожа на другие страницы сайта. Обратите внимание: шаблон в одном при- ложении может расширять шаблон из другого приложения.
Если у формы установлен атрибут errors
, выводится сообщение об ошибке .
В нем говорится, что комбинация имени пользователя и пароля не соответствует информации, хранящейся в базе данных.
Мы хотим, чтобы представление обработало форму, поэтому аргументу action присваивается URL страницы входа . Представление отправляет форму шаблону, мы должны вывести форму и добавить кнопку отправки данных . В точке включается скрытый элемент формы 'next'
; аргумент value сообщает Django, куда перенаправить пользователя после успешно выполненного входа. В нашем случае пользователь возвращается обратно на домашнюю страницу.
Создание ссылки на страницу входа
Добавим ссылку на страницу входа в base .html
, чтобы она присутствовала на каждой странице. Ссылка не должна отображаться, если пользователь уже прошел про- цедуру входа, поэтому она вкладывается в тег
{%
if
%}
:
base.html
Learning Log -
Topics -
❶
{% if user.is_authenticated %}
❷
Hello, {{ user.username }}.
{% else %}
❸
log in
{% endif %}
{% block content %}{% endblock content %}
В системе аутентификации Django в каждом шаблоне доступна переменная user
, которая всегда имеет атрибут is_authenticated
: атрибут равен
True
, если пользо-
Создание учетных записей пользователей
439
ватель прошел проверку, и
False в противном случае. Это позволяет вам выводить разные сообщения для проверенных и непроверенных пользователей.
В данном случае мы выводим приветствие для пользователей, выполнивших вход . У проверенных пользователей устанавливается дополнительный атрибут username
, который обеспечит личную настройку приветствия и напомнит пользо- вателю о том, что вход был выполнен . В точке выводится ссылка на страницу входа для пользователей, которые еще не прошли проверку.
Использование страницы входа
Учетная запись пользователя уже создана; попробуем ввести данные и посмотрим, работает ли страница. Откройте страницу http://localhost:8000/admin/
. Если вы все еще работаете с правами администратора, найдите ссылку выхода в заголовке и щелкните на ней.
После выхода перейдите по адресу http://localhost:8000/users/login/
. На экране должна появиться страница входа, похожая на рис. 19.4. Введите имя пользо- вателя и пароль, заданные ранее, и вы снова должны оказаться на странице со списком. В заголовке страницы должно выводиться сообщение с указанием имени пользователя.
Рис. 19.4.
Страница входа
Выход
Теперь необходимо предоставить пользователям возможность выхода из прило- жения. Мы включим в base .html ссылку для выхода пользователя; при щелчке на этой ссылке открывается страница, подтверждающая, что выход был выполнен успешно.
440
Глава 19 • Учетные записи пользователей
Добавление ссылки для выхода
Теперь нужно создать ссылку для выхода. Мы добавим ее в файл base .html
, что- бы она была доступна на каждой странице, и включим в секцию
{%
if user.is_
authenticated
%}
, чтобы ссылка была видна только пользователям, уже выпол- нившим вход:
base.html
{% if user.is_authenticated %}
Hello, {{ user.username }}.
log out
{% else %}
По умолчанию схеме URL для выхода назначается имя 'logout'
Страница подтверждения выхода
Пользователь должен знать, что выход прошел успешно, поэтому представление по умолчанию для выхода строит страницу на базе шаблона logged_out .html
, который мы сейчас создадим. Он представляет простую страницу с уведомлением о том, что пользователь вышел из сеанса работы с приложением. Сохраните файл в каталоге templates/registration
— в том же каталоге, в котором был сохранен файл login .html
:
logged_out.html
{% extends "learning_logs/base.html" %}
{% block content %}
You have been logged out. Thank you for visiting!
{% endblock content %}
Рис. 19.5.
Страница выхода подтверждает, что выход был выполнен успешно
Создание учетных записей пользователей
441
Ничего другого на этой странице быть не должно, потому что base .html предостав- ляет ссылки на домашнюю страницу и страницу входа на случай, если пользователь захочет вернуться к какой-либо из этих страниц.
На рис. 19.5 изображена страница выхода так, как ее видит пользователь, выпол- нивший вход. Оформление страницы минимально, потому что сейчас нас в первую очередь интересует работа сайта. Когда необходимые функции заработают, можно переходить к стилевому оформлению сайта и приданию ему более профессиональ- ного вида.
Страница регистрации
Теперь мы построим страницу для регистрации новых пользователей. Для этой цели мы используем класс Django
UserCreationForm
, но напишем собственную функцию представления и шаблон.
URL-адрес регистрации
Следующий код предоставляет шаблон URL для страницы регистрации — также в файле users/urls .py
:
urls.py
""" Определяет схемы URL для пользователей. """
from django.urls import path, include from . import views app_name = 'users'
urlpatterns = [
# Включить URL авторизации по умолчанию.
path('', include('django.contrib.auth.urls')),
# Страница регистрации.
path('register/', views.register, name='register'),
]
Мы импортируем модуль views из users
; этот модуль необходим, потому что мы пишем собственное представление для страницы регрессии. Шаблон соответствует
URL http://localhost:8000/users/register/
и отправляет запросы функции register()
, которую мы сейчас напишем.
Функция представления register()
Функция представления register()
должна вывести пустую форму регистрации при первом запросе страницы регистрации, а затем обработать заполненную форму регистрации при отправке данных. Если регистрация прошла успешно, функция также должна выполнить вход для нового пользователя. Включите следующий код в users/views .py
:
442
Глава 19 • Учетные записи пользователей
views.py
from django.shortcuts import render, redirect from django.contrib.auth import login from django.contrib.auth.forms import UserCreationForm def register(request):
"""Регистрирует нового пользователя."""
if request.method != 'POST':
# Выводит пустую форму регистрации.
❶
form = UserCreationForm()
else:
# Обработка заполненной формы.
❷
form = UserCreationForm(data=request.POST)
❸
if form.is_valid():
❹
new_user = form.save()
# Выполнение входа и перенаправление на домашнюю страницу.
❺
login(request, new_user)
❻
return redirect('learning_logs:index')
# Вывести пустую или недействительную форму.
context = {'form': form}
return render(request, 'users/register.html', context)
Сначала импортируются функции render()
и redirect()
. Затем мы импортируем функцию login()
для выполнения входа пользователя, если регистрационная информация верна. Также импортируется класс
UserCreationForm по умолча- нию. В функции register()
мы проверяем, отвечает ли функция на запрос POST.
Если нет, создается экземпляр
UserCreationForm
, не содержащий исходных данных .
В случае ответа на запрос POST создается экземпляр
UserCreationForm
, основан- ный на отправленных данных . Мы проверяем, что данные верны ; в данном случае что имя пользователя содержит правильные символы, пароли совпадают, а пользователь не пытается вставить вредоносные конструкции в отправленные данные.
Если отправленные данные верны, мы вызываем метод save()
формы для со- хранения имени пользователя и хеша пароля в базе данных . Метод save()
возвращает только что созданный объект пользователя, который сохраняется в new_user
. После того как информация пользователя будет сохранена, мы вы- полняем вход; этот процесс состоит из двух шагов: сначала вызывается функция login()
с объектами request и new_user
, которая создает действительный сеанс для нового пользователя. Наконец, пользователь перенаправляется на домашнюю страницу , где
приветствие в заголовке сообщает о том
, что регистрация про- шла успешно.
В конце функции строится страница, которая будет либо пустой формой, либо от- правленной формой, содержащей недействительные данные.
Создание учетных записей пользователей
443
Шаблон регистрации
Шаблон страницы регистрации похож на шаблон страницы входа. Проследите за тем, чтобы он был сохранен в одном каталоге с login .html
:
register.html
{% extends "learning_logs/base.html" %}
{% block content %}
{% csrf_token %}
{{ form.as_p }}
register
{% endblock content %}
Мы снова используем метод as_p
, чтобы инфраструктура Django могла правильно отобразить все поля формы, включая все сообщения об ошибках, если форма была заполнена неправильно.
Создание ссылки на страницу регистрации
Следующий шаг — добавление кода для вывода ссылки на страницу регистрации для любого пользователя, еще не выполнившего вход:
base.html
{% if user.is_authenticated %}
Hello, {{ user.username }}.
log out
{% else %}
Register - log in
{% endif %}
Теперь пользователи, выполнившие вход, получат персональное приветствие и ссылку для выхода. Другие пользователи видят ссылку на страницу регистрации и ссылку для входа. Проверьте страницу регистрации, создав несколько учетных записей с разными именами пользователей.
В следующем разделе доступ к некоторым страницам будет ограничен, чтобы страницы были доступны только для зарегистрированных пользователей. Также необходимо позаботиться о том, чтобы каждая тема принадлежала конкретному пользователю.
444
Глава 19 • Учетные записи пользователей
ПРИМЕЧАНИЕ
Такая система регистрации позволяет любому пользователю создать сколько угодно учетных записей Learning Log . Однако некоторые системы требуют, что- бы пользователь подтвердил свою заявку, отправляя сообщение электронной почты, на которое пользователь должен ответить . При таком подходе в системе будет создано меньше спамерских учетных записей, чем в простейшей системе из нашего примера .
Но пока вы только учитесь строить приложения, вполне нормально тренироваться на упрощенной системе регистрации вроде используемой нами .
УПРАЖНЕНИЯ
19.2. Учетные записи в блоге:
добавьте систему аутентификации и регистрации в проект
Blog
, работа над которым началась в упражнении 19.1 (с. 435). Проследите за тем, чтобы пользователь, выполнивший вход, видел свое имя где-то на экране, а незарегистрирован- ные пользователи видели ссылку на страницу регистрации.
Редактирование данных
Пользователь должен иметь возможность вводить данные, принадлежащие только ему лично. Мы создадим систему, которая будет определять, какому пользователю принадлежат те или иные данные, и будет ограничивать доступ к страницам, чтобы пользователь мог работать только с принадлежащими ему данными.
В этом разделе мы изменим модель
Topic
, чтобы каждая тема принадлежала кон- кретному пользователю. При этом также автоматически решается проблема с запи- сями, так как каждая запись принадлежит конкретной теме. Начнем с ограничения доступа к страницам.
Ограничение доступа с использованием @login_required
Django позволяет легко ограничить доступ к определенным страницам для поль- зователей, выполнивших вход, с помощью декоратора
@login_required
.
Декоратор
(decorator) представляет собой директиву, размещенную непосредственно перед определением функции, применяемую к функции перед ее выполнением и влия- ющую на поведение кода. Рассмотрим пример.
Ограничение доступа к страницам тем
Каждая тема будет принадлежать пользователю, поэтому только зарегистриро- ванные пользователи смогут запрашивать страницы тем. Добавьте следующий код в learning_logs/views .py
:
views.py
from django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required from .models import Topic, Entry
Редактирование данных
445
@login_required def topics(request):
"""Выводит все темы."""
Сначала импортируется функция login_required()
. Мы применяем login_
required()
как декоратор для функции представления topics()
, для чего перед именем login_required()
ставится знак
@
; он сообщает Python, что этот код должен выполняться перед кодом topics()
Код login_required()
проверяет, вошел ли пользователь в систему, и Django за- пускает код topics()
только при выполнении этого условия. Если же пользователь не выполнил вход, он перенаправляется на страницу входа.
Чтобы перенаправление работало, необходимо внести изменения settings .py и со- общить Django, где искать страницу входа. Добавьте следующий фрагмент в самый конец settings .py
:
settings.py
# Мои настройки
LOGIN_URL = '/users/login/'
Когда пользователь, не прошедший проверку, запрашивает страницу, защищенную декоратором
@login_required
, Django отправляет пользователя на URL-адрес, определяемый
LOGIN_URL
в settings .py
Чтобы протестировать эту возможность, завершите сеанс в любой из своих учетных записей и вернитесь на домашнюю страницу. Щелкните на ссылке
Topics
, которая должна направить вас на страницу входа. Выполните вход с любой из своих учет- ных записей, на домашней странице снова щелкните на ссылке
Topics
. На этот раз вы получите доступ к странице со списком тем.
Ограничение доступа в Learning Log
Django
упрощает ограничение доступа к страницам
, но вы должны решить, какие страницы следует защищать. Лучше сначала подумать, к каким страницам можно разрешить неограниченный доступ, а затем ограничить его для всех остальных страниц. Снять излишние ограничения несложно, причем это куда менее риско- ванно, чем оставлять действительно важные страницы без ограничения доступа.
В приложении Learning Log мы оставим неограниченный доступ к домашней странице, странице регистрации и странице выхода. Доступ ко всем остальным страницам будет ограничен.
Вот как выглядит файл learning_logs/views .py с декораторами
@login_required
, при- мененными к каждому представлению, кроме index()
:
446
Глава 19 • Учетные записи пользователей
views.py
@login_required def topics(request):
@login_required def topic(request, topic_id):
@login_required def new_topic(request):
@login_required def new_entry(request, topic_id):
@login_required def edit_entry(request, entry_id):
Попробуйте обратиться к любой из этих страниц без выполнения входа: вы будете перенаправлены обратно на страницу входа. Кроме того, вы не сможете щелкать на ссылках на такие страницы, как new_topic
. Но если ввести URL http://localhost:8000/
new_topic/
, вы будете перенаправлены на страницу входа. Ограничьте доступ ко всем URL-адресам, связанным с личными данными пользователей.
Связывание данных с конкретными пользователями
Теперь данные, отправленные пользователем, необходимо связать с тем пользо- вателем, который их отправил. Связь достаточно установить только с данными, находящимися на высшем уровне иерархии, а низкоуровневые данные последуют за ними автоматически. Например, в приложении Learning Log на высшем уровне находятся темы, а каждая запись связывается с некоторой темой. Если каждая тема принадлежит конкретному пользователю, мы сможем отследить владельца каждой записи в базе данных.
Изменим модель
Topic и добавим отношение внешнего ключа к пользователю.
После этого необходимо провести миграцию базы данных. Наконец, необходимо изменить некоторые представления, чтобы в них отображались только данные, связанные с текущим пользователем.
Изменение модели Topic
В файле models .py изменяются всего две строки:
models.py
from django.db import models from django.contrib.auth.models import User
Редактирование данных
447
class Topic(models.Model):
"""Тема, которую изучает пользователь"""
text = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
"""Возвращает строковое представление модели."""
return self.text class Entry(models.Model):
Сначала модель
User импортируется из django.contrib.auth
. Затем в
Topic до- бавляется поле owner
, используемое в отношении внешнего ключа к модели
User
Если пользователь удаляется, все темы, связанные с этим пользователем, также будут удалены.
Идентификация существующих пользователей
При проведении миграции Django модифицирует базу данных, чтобы в ней хра- нилась связь между каждой темой и пользователем. Для выполнения миграции
Django необходимо знать, с каким пользователем должна быть связана каждая существующая тема. Проще всего связать все существующие темы с одним поль- зователем, например суперпользователем. Но для этого сначала необходимо узнать идентификатор этого пользователя.
Просмотрим идентификаторы всех пользователей, созданных до настоящего мо- мента. Запустите сеанс оболочки Django и введите следующие команды:
(ll_env)learning_log$
python manage.py shell
❶
>>>
from django.contrib.auth.models import User
❷
>>>
User.objects.all()
,
,
]>
❸
>>>
for user in User.objects.all():
...
print(user.username, user.id)
ll_admin 1 eric 2
willie 3
>>>
В точке в сеанс оболочки импортируется модель
User
. После этого просматрива- ются все пользователи, созданные до настоящего момента . В выходных данных перечислены три пользователя:
ll_admin
,
eric
и
willie
.
В точке перебирается список пользователей, и для каждого пользователя вы- водится его имя и идентификатор. Когда Django спросит, с каким пользователем связать существующие темы, мы используем один из этих идентификаторов.
448
Глава 19 • Учетные записи пользователей
Миграция базы данных
Зная значение идентификатора, можно провести миграцию базы данных. Когда вы это делаете, Python предлагает связать модель
Topic с конкретным владельцем временно или добавить в models .py значение по умолчанию, которое сообщит, как следует поступить. Выберите вариант 1:
❶
(ll_env)learning_log$
python manage.py makemigrations learning_logs
❷
You are trying to add a non-nullable field 'owner' to topic without a default;
we can't do that (the database needs something to populate existing rows).
❸
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit, and let me add a default in models.py
❹
Select an option:
1
❺
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
❻
>>> 1
Migrations for 'learning_logs':
learning_logs/migrations/0003_topic_owner.py
- Add field owner to topic
(ll_env)learning_log$
Сначала выдается команда makemigrations
. В ее выходных данных Django со- общает, что мы пытаемся добавить обязательное поле (значения которого отличны от null) в существующую модель (
topic
) без указания значения по умолчанию.
Django предоставляет два варианта : мы можем либо указать значение по умол- чанию прямо сейчас, либо завершить выполнение программы и добавить значение по умолчанию в models .py
. В точке выбирается первый вариант. Тогда Django запрашивает значение по умолчанию .
Чтобы связать все существующие темы с исходным административным пользо- вателем ll_admin
, я ввел в точке идентификатор пользователя 1. Вы можете использовать идентификатор любого из созданных пользователей; он не обязан быть суперпользователем. Django проводит миграцию базы данных, используя это значение, и создает файл миграции
0003_topic_owner .py
, добавляющий поле owner в модель
Topic
Теперь можно провести миграцию. Введите следующую команду в активной вир- туальной среде:
(ll_env)learning_log$
python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, learning_logs, sessions
Running migrations:
❶
Applying learning_logs.0003_topic_owner...
OK
(ll_env)learning_log$
Django применяет новую миграцию с результатом
OK
.
Редактирование данных
449
Чтобы убедиться в том, что
миграция сработала так
, как и ожидалось, можно вос- пользоваться интерактивной оболочкой:
❶
>>>
from learning_logs.models import Topic
❷
>>>
for topic in Topic.objects.all():
...
print(topic, topic.owner)
Chess ll_admin
Rock Climbing ll_admin
>>>
После импортирования
Topic из learning_logs.models
мы перебираем все су- ществующие темы, выводим каждую тему и имя пользователя, которому она при- надлежит . Как видите, сейчас каждая тема принадлежит пользователю ll_admin
(Если при выполнении кода произойдет ошибка, попробуйте выйти из оболочки и запустить ее заново.)
ПРИМЕЧАНИЕ
Вместо миграции можно просто сбросить содержимое базы данных, но это приведет к потере всех существующих данных . Полезно научиться выполнять миграцию базы данных без нарушения целостности данных пользователей . Если вы хотите начать с новой базы данных, используйте команду python manage.py flush для повторного построения структуры базы данных . Вам придется создать нового супер- пользователя, а все данные будут потеряны .
Ограничение доступа к темам
В настоящее время пользователь, выполнивший вход, будет видеть все темы не- зависимо от того, под какой учетной записью он вошел. Сейчас мы изменим при- ложение, чтобы каждый пользователь видел только принадлежащие ему темы.
Внесите следующее изменение в функцию topics()
в файле views .py
:
views.py
@login_required def topics(request):
"""Выводит список тем."""
topics = Topic.objects.filter(owner=request.user).order_by('date_added')
context = {'topics': topics}
return render(request, 'learning_logs/topics.html', context)
Если пользователь выполнил вход, в объекте запроса устанавливается атрибут request.user с информацией о пользователе. Фрагмент кода
Topic.objects.
filter(owner=request.user)
приказывает Django извлечь из базы данных только те объекты
Topic
, у которых атрибут owner соответствует текущему пользователю.
Так как способ отображения не изменяется, изменять шаблон для страницы тем вообще не нужно.
450
Глава 19 • Учетные записи пользователей
Чтобы увидеть, как работает этот способ, выполните вход в качестве пользователя, с которым связаны все существующие темы, и перейдите к странице со списком тем. На ней должны отображаться все темы. Теперь завершите сеанс и войдите снова с другой учетной записью. На этот раз страница должна быть пустой.
Защита тем пользователя
Никаких реальных ограничений на доступ к страницам еще не существует, по- этому любой зарегистрированный пользователь может опробовать разные URL
(например, http://localhost:8000/topics/1/
) и просмотреть страницы тем, которые ему удастся подобрать.
Попробуйте сделать это. После входа с учетной записью суперпользователя ско- пируйте URL или запишите идентификатор в URL темы, после чего завершите сеанс и войдите снова от имени другого пользователя. Введите URL этой темы.
Вам удастся прочитать все записи, хотя сейчас вы вошли под именем другого пользователя.
Чтобы решить эту проблему, мы будем выполнять проверку перед получением запрошенных данных в функции представления topic()
:
views.py
from django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required
❶
from django.http import Http404
@login_required def topic(request, topic_id):
"""Выводит одну тему и все ее записи."""
topic = Topic.objects.get(id=topic_id)
# Проверка того, что тема принадлежит текущему пользователю.
❷
if topic.owner != request.user:
raise Http404
entries = topic.entry_set.order_by('-date_added')
context = {'topic': topic, 'entries': entries}
return render(request, 'learning_logs/topic.html', context)
Код 404 — стандартное сообщение об ошибке, которое возвращается в тех слу- чаях, когда запрошенный ресурс не существует на сервере. В данном случае мы импортируем исключение
Http404
, которое будет выдаваться программой при запросе пользователем темы, которую ему видеть не положено. Получив запрос темы, перед отображением страницы мы убеждаемся в том, что пользователь этой темы является текущим пользователем приложения. Если тема не принадлежит текущему пользователю, выдается исключение
Http404
, а Django возвращает страницу с ошибкой 404.
Редактирование данных
451
Пока при попытке просмотреть записи другого пользователя вы получите от Django сообщение «Страница не найдена». В главе 20 проект будет настроен так, чтобы пользователь видел полноценную страницу ошибки.
Защита страницы edit_entry
Страницы edit_entry используют URL-адреса в форме http://localhost:8000/edit_
entry/entry_id/
, где entry_id
— число. Защитим эту страницу, чтобы никто не мог подобрать URL для получения доступа к чужим записям:
views.py
@login_required def edit_entry(request, entry_id):
"""Редактирует существующую запись."""
entry = Entry.objects.get(id=entry_id)
topic = entry.topic if topic.owner != request.user:
raise Http404
if request.method != 'POST':
Программа читает запись и тему, связанную с этой записью. Затем мы проверяем, совпадает ли владелец темы с текущим пользователем; при несовпадении выдается исключение
Http404
Связывание новых тем с текущим пользователем
В настоящее время страница добавления новых тем не совершенна, потому что она не связывает новые темы с конкретным пользователем. При попытке добавить новую тему выдается сообщение об ошибке
IntegrityError с уточнением
NOT
NULL
constraint failed:
learning_logs_topic.owner_id
. Django говорит, что при созда- нии новой темы обязательно должно быть задано значение поля owner
Проблема легко решается, потому что мы можем получить доступ к информации текущего пользователя через объект request
. Добавьте следующий код, связыва- ющий новую тему с текущим пользователем:
views.py
@login_required def new_topic(request):
"""Определяет новую тему."""
if request.method != 'POST':
# Данные не отправлялись; создается пустая форма.
form = TopicForm()
else:
452
Глава 19 • Учетные записи пользователей
# Отправлены данные POST; обработать данные.
form = TopicForm(data=request.POST)
if form.is_valid():
❶
new_topic = form.save(commit=False)
❷
new_topic.owner = request.user
❸
new_topic.save()
return redirect('learning_logs:topics')
# Вывести пустую или недействительную форму.
context = {'form': form}
return render(request, 'learning_logs/new_topic.html', context)
При первом вызове form.save()
передается аргумент commit=False
, потому что новая тема должна быть изменена перед сохранением в базе данных . Атрибуту owner новой темы присваивается текущий пользователь . Наконец, мы вызываем save()
для только что определенного экземпляра темы . Теперь тема содержит все обязательные данные и ее сохранение пройдет успешно.
Вы сможете добавить сколько угодно новых тем для любого количества разных пользователей. Каждому пользователю будут доступны только его собственные данные, какие бы операции он ни пытался выполнять — просмотр данных, ввод новых или изменение существующих данных.
УПРАЖНЕНИЯ
19.3. Рефакторинг:
в views .py есть два места, в которых программа проверяет, что пользова- тель, связанный с темой, является текущим пользователем. Поместите код этой проверки в функцию с именем check_topic_owner()
и вызовите эту функцию при необходимости.
19.4. Защита new_entry:
пользователь может попытаться добавить новую запись в журнал другого пользователя, вводя URL-адрес с идентификатором темы, принадлежащей друго- му пользователю. Чтобы предотвратить подобные атаки, перед сохранением новой запи- си проверьте, что текущий пользователь является владельцем темы, к которой относится запись.
19.5. Защищенный блог:
в проекте Blog примите меры к тому, чтобы каждое сообщение в блоге было связано с конкретным пользователем. Убедитесь в том, что чтение всех со- общений доступно всем пользователям, но только зарегистрированные пользователи могут создавать новые и редактировать существующие сообщения. В представлении, в котором пользователи редактируют сообщения, перед обработкой формы убедитесь в том, что ре- дактируемое сообщение принадлежит именно этому пользователю.
Итоги
В этой главе вы научились использовать формы для создания новых тем и записей, а также редактирования существующих данных. Далее мы перешли к реализации системы учетных записей. Вы предоставили существующим пользователям воз- можность начинать и завершать сеанс работы с приложением, а также научились использовать класс Django
UserCreationForm для создания новых учетных записей.
Итоги
453
После создания простой системы аутентификации и регистрации пользователей вы ограничили доступ пользователей к некоторым страницам; для этого использовался декоратор
@login_required
. Затем данные были связаны с конкретными пользова- телями при помощи отношения внешнего ключа. Вы также узнали, как выполнить миграцию базы данных, когда миграция требует ввести данные по умолчанию.
В последней части главы вы узнали, как ограничить состав данных, просматрива- емых пользователем, с использованием функций представления. Для чтения со- ответствующих данных использовался метод filter()
, а владелец запрашиваемых данных сравнивался с текущим пользователем.
Не всегда бывает сразу понятно, какие данные должны быть доступны всем поль- зователям, а какие данные следует защищать, но этот навык приходит с практикой.
Решения, принятые нами в этой главе для защиты данных пользователей, наглядно показывают, почему при построении проекта желательно работать в команде: если кто-то просматривает код вашего проекта, это повышает вероятность выявления плохо защищенных областей.
К настоящему моменту мы построили полностью функциональный проект, рабо- тающий на локальной машине. В последней главе мы доработаем оформление при- ложения Learning Log, чтобы оно выглядело более привлекательно. Также проект будет развернут на сервере, чтобы любой пользователь с доступом к интернету мог зарегистрироваться и создать учетную запись.
20
Оформление и развертывание приложения
Приложение Learning Log уже вполне работоспособно, но оно не имеет стилевого оформления и работает только на локальной машине. В этой главе мы определим для проекта простое, но профессиональное оформление, а затем развернем его на сервере, чтобы любой желающий мог создать учетную запись.
Для стилевого оформления будет использоваться библиотека Bootstrap — набор инструментов для оформления веб-приложений, с которыми они будут выглядеть профессионально на любых современных устройствах, от большого монитора с пло- ским экраном до смартфона. Для этого мы воспользуемся приложением django- bootstrap4
, а вы заодно потренируетесь в использовании приложений, созданных другими разработчиками Django.
Для развертывания Learning Log будет использоваться Heroku — сайт, позволяю- щий загрузить ваш проект на один из его серверов, чтобы сделать его доступным для любого пользователя с подключением к интернету. Также мы начнем поль- зоваться системой контроля версий Git для отслеживания изменений в проекте.
Когда работа с Learning Log будет завершена, вы будете уметь разрабатывать про- стые веб-приложения, придавать им качественный внешний вид и развертывать их на работающих серверах. Также по мере накопления опыта вы научитесь поль- зоваться ресурсами с материалами более высокого уровня.
Оформление приложения Learning Log
До сих пор мы намеренно игнорировали оформление приложения, чтобы сосредо- точиться на его функциональности. И это
вполне разумный подход к разработке
, потому что приложение приносит пользу только в том случае, если оно работает.
Конечно, когда приложение начинает работать, оформление выходит на первый план, чтобы пользователи захотели работать с ним.
В этом разделе я кратко опишу приложение django-bootstrap4
и покажу, как ин- тегрировать его в проект и подготовить к развертыванию.
Оформление приложения Learning Log
455
Приложение django-bootstrap4
Для интеграции Bootstrap в наш проект будет использоваться приложение django- bootstrap4
. Это приложение загружает необходимые файлы Bootstrap, размещает их в правильных каталогах проекта и предоставляет доступ к стилевым директивам в шаблонах проекта.
Чтобы установить django-bootstrap4
, введите следующую команду в активной виртуальной среде:
(ll_env)learning_log$
pip install django-bootstrap4
Successfully installed django-bootstrap4-0.0.7
Затем необходимо добавить следующий код для включения django-bootstrap4
в список
INSTALLED_APPS
в файле settings .py
:
settings.py
INSTALLED_APPS = [
# Мои приложения 'learning_logs',
'users',
# Сторонние приложения 'bootstrap4',
# Приложения django по умолчанию.
'django.contrib.admin',
Создайте новую секцию для приложений, созданных другими разработчиками, и включите в нее запись 'bootstrap4'
. Проследите за тем, чтобы секция распола- галась после секции
#
Мои приложения
, но перед секцией, содержащей приложения
Django по умолчанию.
Использование Bootstrap для оформления Learning Log
По сути, Bootstrap представляет собой большой набор инструментов стилевого оформления. Также библиотека содержит ряд шаблонов, которые можно приме- нить к проекту для формирования общего стиля. Пользоваться этими шаблонами намного проще, чем отдельными инструментами оформления. Чтобы просмотреть шаблоны, предоставляемые Bootstrap, перейдите по ссылке http://getbootstrap .com/
, щелкните на ссылке
Examples и найдите раздел
Navbars
. Мы воспользуемся шабло- ном N
avbar static
, который предоставляет простую панель навигации и контейнер для содержимого страницы.
На рис. 20.1 показано, как будет выглядеть домашняя страница после применения шаблона Bootstrap к base .html и незначительного изменения index .html
456
Глава 20 • Оформление и развертывание приложения
Рис. 20.1.
Домашняя страница Learning Log с использованием Bootstrap
Изменение base .html
Шаблон base .html необходимо изменить так, чтобы в нем был задействован шаблон
Bootstrap. Новая версия base .html будет представлена в несколько этапов.
Определение заголовков HTML
Первое изменение в base .html
: заголовки HTML определяются в файле, чтобы при открытии страницы Learning Log в строке заголовка браузера выводилось имя сайта. Также будут добавлены некоторые требования для использования Bootstrap в шаблонах. Удалите все содержимое base .html и замените его следующим кодом:
base.html
❶
{% load bootstrap4 %}
❷
❸
❹
Оформление приложения Learning Log
457
В точке загружается коллекция шаблонных тегов из django-bootstrap4
. Затем файл объявляется как документ HTML , написанный на английском языке .
Файл HTML состоит из двух основных частей, заголовка и тела; заголовок файла начинается в точке . Заголовок файла HTML не содержит контента: он всего лишь передает браузеру информацию, необходимую для правильного отображения страницы. В точке включается элемент title страницы; его содержимое будет выводиться в строке заголовка браузера при открытии Learning Log.
В точке используется один из шаблонных тегов django-bootstrap4
, который приказывает Django включить все стилевые файлы Bootstrap. Следующий тег активизирует все интерактивное поведение, которое может использоваться на странице, например раздвижные навигационные панели. В точке располагается закрывающий тег
Определение навигационной панели
Код, определяющий навигационную панель в верхней части страницы, получается довольно длинным, потому что он должен хорошо работать как на узких экранах смартфонов, так и на широких экранах мониторов настольных компьютеров. Мы рассмотрим код навигационной панели по частям.
Первая часть навигационной панели выглядит так:
base.html
❶
❷
❸
Learning Log
❹
data-target="#navbarCollapse" aria-controls="navbarCollapse"
aria-expanded="false" aria-label="Toggle navigation">
Первый элемент — открывающий тег
. Тело файла HTML содержит кон- тент, который будет виден пользователям на странице. В точке элемент
обозначает раздел навигационных ссылок на странице. Весь контент внутри этого элемента оформляется по правилам Bootstrap, определяемым селекторами navbar
, navbar-expand-md и другим перечисленным здесь.
Селектор
определяет, к каким элементам страницы должно применяться стилевое правило. Селекторы navbar- light и bg-light оформляют навигационную панель темой со светлым фоном.
Сокращение mb в mb-4
происходит от «margin-bottom», то есть «нижнее поле»; этот селектор гарантирует, что между навигационной панелью и остальным контентом страницы остается свободное место. Селектор border создает тонкую рамку вокруг светлого фона, чтобы немного отделить его от остального контента страницы.
458
Глава 20 • Оформление и развертывание приложения
В точке задается имя проекта, которое выводится у левого края навигационной панели, и создается ссылка на домашнюю страницу; она будет отображаться на каждой странице проекта. Селектор navbar-brand оформляет эту ссылку так, чтобы она выделялась на фоне остальных ссылок; это оформление становится одной из составляющих фирменной символики сайта.
В точке шаблон определяет кнопку, которая будет отображаться, если ширины окна браузера не хватает для горизонтального отображения всей навигационной панели. Когда пользователь нажимает кнопку, навигационные элементы выводятся в раскрывающемся списке. Атрибут collapse сворачивает навигационную панель при уменьшении размеров окна браузера и при отображении сайта на мобильных устройствах с малыми экранами.
Следующая часть кода, определяющего навигационную панель:
base.html
❶
❷
❸
Topics
В точке открывается новая секция навигационной панели. В процессе постро- ения веб-страницы разработчик делит ее на секции и определяет стили и правила поведения, применяемые к текущей секции. Все стилевые директивы и правила поведения, определяемые в открывающем теге div
, продолжают действовать до следующего, закрывающего тега div
, который записывается в виде
. Это начало той части навигационной панели, которая будет сворачиваться на узких экранах и окнах.
В точке определяется новый набор ссылок. Bootstrap определяет навигационные элементы как элементы
неупорядоченного списка со стилевым оформлением
, с ко- торым они совершенно не похожи на список. Каждая ссылка или элемент, который должен отображаться на панели, включается как элемент одного из этих списков.
В данном случае единственным элементом списка является ссылка на страницу
Topics
.
Следующая часть навигационной панели:
base.html
❶
❷
{% if user.is_authenticated %}
❸
Hello, {{ user.username }}.
Оформление приложения Learning Log
459
Log out
{% else %}
Register
Log in
{% endif %}
❹
В точке новый набор ссылок начинается при помощи другого открывающего тега
. На странице можно создать столько групп ссылок, сколько вам понадобится.
Эта группа содержит ссылки, связанные со входом и регистрацией, расположенные в правой части навигационной панели. Имя селектора ml-auto означает «margin- left-automatic», то есть «автоматическое левое поле»; этот селектор анализирует другие элементы на навигационной панели и определяет величину левого поля, которое сдвигает эту группу ссылок к правому краю экрана.
Блок if в точке уже использовался ранее для вывода сообщений для пользо- вателей в зависимости от того, выполнили они вход или нет. На этот раз блок стал немного длиннее, потому что некоторые стилевые правила находятся внутри условных тегов. В точке расположен элемент
. Элемент span оформляет фрагменты текста или элементы страницы, которые являются частью более длин- ной строки. Если элементы div создают собственный раздел страницы, элементы span непрерывно располагаются внутри большего раздела. На первый взгляд такая структура кажется запутанной, потому что многие страницы содержат элементы div с большой вложенностью. Здесь элемент span используется для оформления текста на навигационной панели — например, имени пользователя, выполнившего вход. Эта информация должна отличаться по внешнему виду от ссылок, чтобы у пользователей не возникало желания щелкать на них.
В точке закрывается элемент div с частями навигационной панели, которые сворачиваются на узких экранах, а в конце секции закрывается навигационная па- нель в целом. Если вы захотите добавить больше ссылок на навигационную панель, включите другой элемент
в любую из групп
, определенных на навигаци- онной панели; используйте стилевые директивы, идентичные приведенным выше.
Работа с файлом base .html еще не закончена. Необходимо определить два блока, которые могут использоваться отдельными страницами для размещения контента, относящегося к этим страницам.
Определение основного раздела страницы
Оставшаяся часть base .html содержит основной контент страницы:
460
Глава 20 • Оформление и развертывание приложения
base.html
❶
❷
{% block page_header %}{% endblock page_header %}
❸
{% block content %}{% endblock content %}
Оформление приложения Learning Log
461
ние. Обычно этот элемент используется на домашних страницах для размещения краткого описания проекта.
Обновленный файл index .html выглядит так:
index.html
{% extends "learning_logs/base.html" %}
❶
{% block page_header %}
❷
❸
1
...
44
45
46
47
48
49
50
51
52
написать администратору сайта