Изучаем 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.19 Mb.
|
19 Учетные записи пользователей Что является самым главным для веб-приложения? Что любой пользователь, жи- вущий в любой стране мира, сможет создать учетную запись в вашем приложении и начать работать с ним. В этой главе мы построим формы, на которых пользова- тели смогут вводить свои темы и записи, а также редактировать существующие данные. Также вы узнаете, как Django защищает приложения от распространенных атак на страницы с формами, чтобы вам не приходилось тратить много времени на продумывание средств защиты вашего приложения. Затем будет реализована система проверки пользователей. Мы создадим стра- ницу регистрации, на которой пользователи смогут создавать учетные записи, и ограничим доступ к некоторым страницам для анонимных пользователей. Затем некоторые функции представления будут изменены так, чтобы пользователь мог видеть только свои собственные данные. Вы узнаете, как обеспечить безопасность и конфиденциальность данных пользователей. Редактирование данных Прежде чем строить систему аутентификации пользователей для создания учетных записей, сначала мы добавим несколько страниц, на которых пользователи смогут вводить собственные данные. У пользователей появится возможность создавать новые темы, добавлять новые записи и редактировать записи, сделанные ранее. В настоящее время данные могут вводиться только суперпользователем на административном сайте. Однако разрешать пользователям работу на админи- стративном сайте явно нежелательно, поэтому мы воспользуемся средствами построения форм Django для создания страниц, на которых пользователи смогут вводить данные. Добавление новых тем Начнем с возможности создания новых тем. Страницы на базе форм добавляются практически так же, как и те страницы, которые мы уже строили ранее: вы опреде- ляете URL, пишете функцию представления и создаете шаблон. Принципиальное отличие — добавление нового модуля forms .py , содержащего функциональность форм. 424 Глава 19 • Учетные записи пользователей Объект ModelForm Любая страница, на которой пользователь может вводить и отправлять информа- цию, является формой, даже если на первый взгляд она на форму не похожа. Когда пользователь вводит информацию, необходимо проверить, что он ввел корректные данные, а не вредоносный код (например, код для нарушения работы сервера). За- тем проверенная информация обрабатывается и сохраняется в нужном месте базы данных. Django автоматизирует большую часть этой работы. Простейший способ построения форм в Django основан на использовании класса ModelForm , который автоматически строит форму на основании моделей, опреде- ленных в главе 18. Ваша первая форма будет создана в файле forms .py , который должен находиться в одном каталоге с models .py : forms.py from django import forms from .models import Topic ❶ class TopicForm(forms.ModelForm): class Meta: ❷ model = Topic ❸ fields = ['text'] ❹ labels = {'text': ''} Сначала импортируется модуль forms и модель, с которой мы будем работать: Topic . В точке определяется класс с именем TopicForm , наследующий от forms. ModelForm Простейшая версия ModelForm состоит из вложенного класса Meta , который со- общает Django, на какой модели должна базироваться форма и какие поля на ней должны находиться. В точке форма создается на базе модели Topic , а на ней раз- мещается только поле text . Код приказывает Django не генерировать подпись для текстового поля. URL-адрес для new_topic URL-адрес новой страницы должен быть простым и содержательным, поэтому по- сле того, как пользователь выбрал команду создания новой темы, он направляется по адресу http://localhost:8000/new_topic/ . Ниже приведена схема URL для страницы new_topic , которая добавляется в learning_logs/urls .py : urls.py urlpatterns = [ # Страница для добавления новой темы path('new_topic/', views.new_topic, name='new_topic'), ] Редактирование данных 425 Эта схема URL будет отправлять запросы функции представления new_topic() , которую мы сейчас напишем. Функция представления new_topic() Функция new_topic() должна обрабатывать две разные ситуации: исходные за- просы страницы new_topic (в этом случае должна отображаться пустая форма) и обработка данных, отправленных на форме. Затем она должна перенаправить пользователя обратно на страницу topics : views.py from django.shortcuts import render, redirect from .models import Topic from .forms import TopicForm def new_topic(request): """Определяет новую тему.""" ❶ if request.method != 'POST': # Данные не отправлялись; создается пустая форма. ❷ form = TopicForm() else: # Отправлены данные POST; обработать данные. ❸ form = TopicForm(data=request.POST) ❹ if form.is_valid(): ❺ form.save() ❻ return redirect('learning_logs:topics') # Вывести пустую или недействительную форму. ❼ context = {'form': form} return render(request, 'learning_logs/new_topic.html', context) Мы импортируем класс HttpResponseRedirect , который будет использоваться для перенаправления пользователя к странице topics после отправки введенной темы. Функция reverse() определяет URL по заданной схеме URL (то есть Django сгене- рирует URL при запросе страницы). Также импортируется только что написанная форма TopicForm Запросы GET и POST При построении веб-приложений используются два основных типа запросов — GET и POST. Запросы GET используются для страниц, которые только читают данные с сервера, а запросы POST обычно используются в тех случаях, когда пользователь должен отправить информацию на форме. Для обработки всех наших форм будет использоваться метод POST (существуют и другие разновидности за- просов, но в нашем проекте они не используются). Функция new_topic() получает в параметре объект запроса. Когда пользователь впервые запрашивает эту страницу, его браузер отправляет запрос GET. Когда 426 Глава 19 • Учетные записи пользователей пользователь уже заполнил и отправил форму, его браузер отправляет запрос POST. В зависимости от типа запроса мы определяем, запросил ли пользователь пустую форму (запрос GET) или предлагает обработать заполненную форму (за- прос POST). Метод запроса — GET или POST — проверяется в точке . Если метод запроса от- личен от POST, вероятно, используется запрос GET, поэтому необходимо вернуть пустую форму (даже если это запрос другого типа, это все равно безопасно). Мы создаем экземпляр TopicForm , сохраняем его в переменной form и отправляем форму шаблону в словаре context . Так как при создании TopicForm аргументы не передавались, Django создает пустую форму, которая заполняется пользова- телем. Если используется метод запроса POST, выполняется блок else , который об- рабатывает данные, отправленные в форме. Мы создаем экземпляр TopicForm и передаем ему данные, введенные пользователем, хранящиеся в request.POST Возвращаемый объект form содержит информацию, отправленную пользова- телем. Отправленную информацию нельзя сохранять в базе данных до тех пор, пока она не будет проверена . Функция is_valid() проверяет, что все обязательные поля были заполнены (все поля формы по умолчанию являются обязательными), а вве- денные данные соответствуют типам полей — например, что длина текста меньше 200 символов, как было указано в файле models .py в главе 18. Автоматическая про- верка избавляет нас от большого объема работы. Если все данные действительны, можно вызвать метод save() , который записывает данные из формы в базу данных. После того как данные будут сохранены, страницу можно покинуть. Мы исполь- зуем вызов redirect() для перенаправления браузера на страницу topics , на которой пользователь увидит только что введенную им тему в общем списке тем. Переменная context определяется в конце функции представления , а страница строится на базе шаблона new_topic .html , который будет создан на следующем шаге. Код размещается за пределами любых блоков if ; он выполняется при создании пустой формы, а также при определении того, что отправленная форма была не- действительной. Недействительная форма включает стандартные сообщения об ошибках, чтобы помочь пользователю передать действительные данные. Шаблон new_topic Теперь создадим новый шаблон с именем new_topic .html для отображения только что созданной формы: new_topic.html {% extends "learning_logs/base.html" %} {% block content %} Редактирование данных 427 Add a new topic: ❶ {% endblock content %} Этот шаблон расширяет base .html , поэтому он имеет такую же базовую структуру, как и остальные страницы Learning Log. В точке определяется форма HTML. Аргумент action сообщает серверу, куда передавать данные, отправленные фор- мой; в данном случае данные возвращаются функции представления new_topic() Аргумент method приказывает браузеру отправить данные в запросе типа POST. Django использует шаблонный тег {% csrf_token %} для предотвращения по- пыток получения несанкционированного доступа к серверу (атаки такого рода называются межсайтовой подделкой запросов). В точке отображается форма; это наглядный пример того, как легко в Django выполняются такие стандартные операции, как отображение формы. Чтобы автоматически создать все поля, необ- ходимые для отображения формы, достаточно включить шаблонную переменную {{ form.as_p }} . Модификатор as_p приказывает Django отобразить все элементы формы в формате абзацев — это простой способ аккуратного отображения формы. Django не создает кнопку отправки данных для форм, поэтому мы определяем ее в точке . Создание ссылки на страницу new_topic Далее ссылка на страницу new_topic создается на странице topics : topics.html {% extends "learning_logs/base.html" %} {% block content %} Topics Add a new topic: {% endblock content %} Разместите ссылку после списка существующих тем. Полученная форма изобра- жена на рис. 19.1. Воспользуйтесь ею и добавьте несколько своих тем. 428 Глава 19 • Учетные записи пользователей Рис. 19.1. Страница для добавления новой темы Добавление новых записей Теперь, когда пользователь может добавлять новые темы, он также захочет добав- лять новые записи. Мы снова определим URL, напишем новую функцию и шаблон и создадим ссылку на страницу. Но сначала нужно добавить в forms .py еще один класс. Класс EntryForm Мы должны создать форму, связанную с моделью Entry , но более специализиро- ванную по сравнению с TopicForm : forms.py from django import forms from .models import Topic, Entry class TopicForm(forms.ModelForm): class EntryForm(forms.ModelForm): class Meta: model = Entry fields = ['text'] ❶ labels = {'text': 'Entry:'} ❷ widgets = {'text': forms.Textarea(attrs={'cols': 80})} Сначала в команду import к Topic добавляется Entry . Новый класс EntryForm на- следует от forms.ModelForm и содержит вложенный класс Meta с указанием модели, Редактирование данных 429 на которой он базируется, и поле, включаемое на форму. Полю 'text' снова на- значается пустая надпись . В точке включается атрибут widgets . Виджет (widget) представляет собой эле- мент формы HTML: однострочное или многострочное текстовое поле, раскрываю- щийся список и т. д. Включая атрибут widgets , вы можете переопределить виджеты, выбранные Django по умолчанию. Приказывая Django использовать элемент forms. Textarea , мы настраиваем виджет ввода для поля 'text' , чтобы ширина текстовой области составляла 80 столбцов вместо значения по умолчанию 40. У пользователя будет достаточно места для создания содержательных записей. URL-адрес для new_entry Необходимо включить аргумент topic_id в URL-адрес для создания новой записи, потому что запись должна ассоциироваться с конкретной темой. Вот как выглядит URL, который мы добавляем в learning_logs/urls .py : urls.py urlpatterns = [ # Страница для добавления новой записи path('new_entry/ ] Эта схема URL соответствует любому URL-адресу в форме http://localhost:8000/ new_entry/id/ , где id — число, равное идентификатору темы. Код за- хватывает числовое значение и сохраняет его в переменной topic_id . При запросе URL-адреса, соответствующего этой схеме, Django передает запрос и идентифика- тор темы функции представления new_entry() Функция представления new_entry() Функция представления new_entry очень похожа на функцию добавления новой темы. Включите следующий код в файл views .py : views.py from django.shortcuts import render, redirect from .models import Topic from .forms import TopicForm, EntryForm def new_entry(request, topic_id): """Добавляет новую запись по конкретной теме.""" ❶ topic = Topic.objects.get(id=topic_id) 430 Глава 19 • Учетные записи пользователей ❷ if request.method != 'POST': # Данные не отправлялись; создается пустая форма. ❸ form = EntryForm() else: # Отправлены данные POST; обработать данные. ❹ form = EntryForm(data=request.POST) if form.is_valid(): ❺ new_entry = form.save(commit=False) ❻ new_entry.topic = topic new_entry.save() ❼ return redirect('learning_logs:topic', topic_id=topic_id) # Вывести пустую или недействительную форму. context = {'topic': topic, 'form': form} return render(request, 'learning_logs/new_entry.html', context) Мы обновляем команду import и включаем в нее только что созданный класс EntryForm . Определение new_entry() содержит параметр topic_id для сохранения полученного значения из URL. Идентификатор темы понадобится для отображе- ния страницы и обработки данных формы, поэтому мы используем topic_id для получения правильного объекта темы . В точке проверяется метод запроса: POST или GET. Блок if выполняется для запроса GET, и мы создаем пустой экземпляр EntryForm . Для метода запроса POST мы обрабатываем данные, создавая экземпляр EntryForm , заполненный данными POST из объекта request . Затем проверяется коррект- ность данных формы. Если данные корректны, необходимо задать атрибут topic объекта записи перед сохранением его в базе данных. При вызове save() мы вклю- чаем аргумент commit=False для того, чтобы создать новый объект записи и со- хранить его в new_entry , не сохраняя пока в базе данных. Мы присваиваем атрибуту topic объекта new_entry тему, прочитанную из базы данных в начале функции , после чего вызываем save() без аргументов. В результате запись сохраняется в базе данных с правильно ассоциированной темой. Вызов redirect() в точке получает два аргумента — имя представления, кото- рому передается управление, и аргумент для функции представления. В данном случае происходит перенаправление функции topic() , которой должен переда- ваться аргумент topic_id . Вызов перенаправляет пользователя на страницу темы, для которой была создана запись, и пользователь видит новую запись в списке записей. В конце функции создается словарь context , а страница строится на базе шаблона new_entry .html . Этот код выполняется для пустой формы или для отправленной формы, которая была определена как недействительная. Шаблон new_entry Как видно из следующего кода, шаблон new_entry похож на шаблон new_topic : Редактирование данных 431 new_entry.html {% extends "learning_logs/base.html" %} {% block content %} ❶ {{ topic }} Add a new entry: ❷ {% endblock content %} В начале страницы выводится тема , чтобы пользователь мог видеть, в какую тему добавляется новая запись. Тема также служит ссылкой на главную страницу этой темы. Аргумент action формы включает значение topic_id из URL, чтобы функция пред- ставления могла связать новую запись с правильной темой . В остальном этот шаблон почти не отличается от new_topic .html Создание ссылки на страницу new_entry Затем необходимо создать ссылку на страницу new_entry на каждой странице темы: topic.html {% extends "learning_logs/base.html" %} {% block content %} Topic: {{ topic }} Entries: add new entry {% endblock content %} Ссылка добавляется перед выводом записей, потому что добавление новой запи- си является самым частым действием на этой странице. На рис. 19.2 изображена страница new_entry . Теперь пользователь может добавить сколько угодно новых тем и новых записей по каждой теме. Опробуйте страницу new_entry , добавив не- сколько записей для каждой из созданных вами тем. 432 Глава 19 • Учетные записи пользователей Рис. 19.2. Страница new_entry Редактирование записей А теперь мы создадим страницу, на которой пользователи смогут редактировать ранее добавленные записи. URL-адрес для edit_entry В URL-адресе страницы должен передаваться идентификатор редактируемой записи. В файл learning_logs/urls .py для этого вносятся следующие изменения: urls.py urlpatterns = [ # Страница для редактирования записи path('edit_entry/ ] Идентификатор, переданный в URL (например, http://localhost:8000/edit_entry/1/ ), сохраняется в параметре entry_id . Схема URL отправляет запросы, соответству- ющие этому формату, функции представления edit_entry() Функция представления edit_entry() Когда страница edit_entry получает запрос GET, edit_entry() возвращает форму для редактирования записи. При получении запроса POST с отредактированной записью страница сохраняет измененный текст в базе данных: |