#32. Forms

Forms, request, user, authorization

enter image description here

HTML форма

Форма предназначена для обмена данными между пользователем и сервером.
Веб-форма — это набор текстовых полей, списков, кнопок и других элементов управления, посредством которых посетитель страницы может предоставить ей тот или иной вид информации. Формы в Интернете повсюду — благодаря формам мы можем создавать учетные записи электронной почты, просматривать и покупать товары в интернет-магазинах, осуществлять финансовые транзакции и многое другое. Самая простая форма — это одиночное текстовое поле поисковых систем

Тег <form> устанавливает форму на веб-странице. Он “сообщает” браузеру, что данные из этого тега нужно сгруппировать и подготовить к отправке на сервер.

Принимает два параметра: action и method.

  1. action описывает куда форма должна оправить данные, в нашем случае это будет url, если не указан явно, то форма будет отправлена на тот же урл, на котором сейчас находится.

  2. method - отвечает за метод запроса, варианты это get и post. get используется по умолчанию.

Post-формы используются для передачи данных, не подлежащих огласке, например, логин и пароль. Get-формы, используются для общедоступной информации, например строка поиска.

Внутри формы мы указываем нужное кол-во тегов <input> с нужными типами. Именно эти данные будут в последствии переданы серверу.

<form>  
  <label for="fname">First name:</label><br>  
  <input type="text"  id="fname"  name="fname"><br>  
  <label for="lname">Last name:</label><br>  
  <input type="text"  id="lname"  name="lname">  
</form>

enter image description here

Основные типы инпутов

number - ввод числа

text - ввод текста

checkbox - чекбокс (выбор нескольких элементов через галочки)

radio - радиобаттон (выбор только одного элемента из списка)

button - классическая кнопка (если в форме есть один такое элемент, но нет сабмита, браузер автоматически подсчитает его сабмитом)

hidden - скрытое поле, чаще всего нужно для целей безопасности или добавления информации и данным не отображая её ( Не отображается)

submit - отправка формы

Это далеко не все типы инпутов, которые могут быть. Ссылка на типы инпутов

GET форма

Создадим простейшую форму, для этого добавим url в urlpatterns, функцию во views.py для обработки и html страницу в templates.

В urls.py

from .views import form_view

urlpatterns =  [
    path('form-url/', form_view, name='form-view'),
]

Во views.py

def form_view(request):
    return render(request, 'form.html')

В form.html

{% extends 'base.html' %}

{% block content %}
<form method="get" action="{% url 'form-view' %}">
    <label>
        <input type="text" name="my_name">
    </label>
    <button type="submit">Send name</button>
</form>
{% endblock %}

Обратите внимание, мы указали get форму, экшеном является url, на эту же страницу, который обрабатывается нашей же функцией form_view.

В форме у нас один input которому мы указали 2 аттрибута type и name.

Аттрибут name нам необходим для того, что бы мы смогли обработать данные во view.

Также у нас есть кнопка submit, необходима для того, что бы отправить запрос, на сервер.

При нажатии на кнопку формируется и отправляется request.

Примерно вот так будет выглядеть наша страница если зайти на адрес http://127.0.0.1:8000/form-url/:
enter image description here

request

Объект request, тот который принимает первым параметром функции во views.py и его же передаём первым в функцию render. Зачем он нужен, и из чего он состоит?

Зачем нужен. Нужен для того, что бы обрабатывать любые пользовательские или служебные данные которые были переданы.

Из чего состоит. Состоит из переданных данных или файлов (если были переданы), и служебной информации (информация о пользователе, метод запроса, о том на какой url был запрос, из какого браузера, другая системная информация)

enter image description here
Что будет после нажатия кнопки Send name?

Будет сформирован GET (метод формы) запрос всеми заполненными нами данными и отправлен на сервер.

Обратите внимание на новый url.
enter image description here
my_name - это предварительно указанный атрибут name на нашей форме, а user значение которое я передал в этот input.

В случае GET запроса данные передаются явно, прям в url в виде ключ-значение. Если бы значений было бы больше одного они были бы соединены при помощи символа & (например, если бы я добавил еще и поле с указанным атрибутом name со значением age и заполнил бы его значением 26, то url после запроса выглядел бы так /form-url/?my_name=Vlad&age=26. Никакой разницы между заполнением формы, или записью этих данных руками прям в строке браузера для GET запроса нет.

Обработка данных во view

Мы можем обработать данные во view, при помощи переменной request, данные из GET запроса, будет находиться в переменной request.GET
enter image description here

анные находятся в виде словаря, где ключами является атрибут name в каждом инпуте формы.

Эти данные можно использовать для любых целей, но чаще всего через GET передаются данные по фильтрации, или дополнительные параметры отображения, например, когда вы добавляете фильтры в интернет магазине, пишете текст в поиске, или когда на ютубе пересылаете ссылку с таймкодом, она тоже передаётся как GET параметр

POST запрос

Давайте заменим метод нашей формы с get на post:

В form.html:

{% extends 'base.html' %}

{% block content %}
<form method="post" action="{% url 'form-view' %}">{
    <label>
        <input type="text" name="my_name">
    </label>
    <button type="submit">Send name</button>
</form>
{% endblock %}

Что произойдёт при отправке такого запроса?

Произойдёт ошибка, примерно вот такая:
enter image description here

Это ошибка CSRF токена.
CSRF (англ. cross-site request forgery — «межсайтовая подделка запроса», также известна как XSRF) — вид атак на посетителей веб-сайтов, использующий недостатки протокола HTTP. Если жертва заходит на сайт, созданный злоумышленником, от её лица тайно отправляется запрос на другой сервер (например, на сервер платёжной системы), осуществляющий некую вредоносную операцию (например, перевод денег на счёт злоумышленника). Для осуществления данной атаки жертва должна быть аутентифицирована на том сервере, на который отправляется запрос, и этот запрос не должен требовать какого-либо подтверждения со стороны пользователя, которое не может быть проигнорировано или подделано атакующим скриптом.

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

GET запросы, это запросы общедоступные и информационные, открыть страницу, отфильтровать данные итд.

POST запросы, это запросы с чувствительными данными, создание записей в базе, передача пароля, отправка денег со счёта на счёт, итд.

Так вот, если GET запрос отправить 5 раз подряд, то с точки зрения сервера ничего не изменится, вы просто 5 раз подряд запросите одну и туже информацию.

Если изменить параметры, то тоже ничего страшного не произойдёт, просто запросятся другие данные.

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

Поэтому в Django изначально есть дополнительное требование к POST формам, это еще одно скрытое поле, заранее сгенерированное сервером. Оно называется CSRF токен, где он проверяется и почему мы видим ошибку, мы разберём на следующих занятиях.

Для того что бы добавить нужный токен, используется специальный темплейт тег {% csrf_token %}, его нужно добавить в любом месте внутри тега <form>

{% extends 'base.html' %}

{% block content %}
<form method="post" action="{% url 'form-view' %}">
    {% csrf_token %}
    <label>
        <input type="text" name="my_name">
    </label>
    <button type="submit">Send name</button>
</form>
{% endblock %}

Что изменится с точки зрения html:

<html lang="en"><head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div style="background-color: aqua">
    
<form method="post" action="/form-url/">
 <input type="hidden" name="csrfmiddlewaretoken" value="Lr9N7x9yZaksWqLpz5GXgqiiSOAgc4kCkRCGW0A5bas9mc50H87PMAqW1Jdu7iBy">
    <input type="text" name="my_name">
    <button type="submit">Send name</button>
</form>

</div>
</body>
</html>

Появилось поле типа hidden, это значит, что оно не будет отображаться, но эти данные все равно попадут на сервер. Это часто используется когда вам нужно передать данные которые у тебя уже есть при отрисовке, но их не видно явно, допустим есть мы пишем коментарий к коментарию, то что бы грамотно его создать, нам нужен id родителя, его обычно и передают как hidden филд.

Теперь наш запрос отправится успешно.

Обратите внимание, урл не изменятся!

Потому что данные отправленные через POST не должны быть общедоступны.

Обработка во view

Обработать данные из POST запроса, можно точно так же, данные будут находиться в переменной request.POST, если это просто данные и в request.FILES если были переданы файлы.
enter image description here
Обратите внимание, что вместе с нашими данными был передан и csrf токен. Обычно при обработке данных он не нужен, но данные были переданы, а значит они прийдут на сервер.

Джанго формы

Джанго предоставляет нам возможность генерировать html формы из кода на python!

Что для этого нужно. Создадим в нашем приложении файл forms.py

Внутри этого файла укажем.

forms.py

from django import forms

class MyForm(forms.Form):
    nickname = forms.CharField(label='My nickname', max_length=100)
    age = forms.IntegerField(label='My age')

Обработчик для урла заменим на:

Во views.py заменим нашу функцию на:

from django.shortcuts import render

from .forms import MyForm

def form_view(request):
    # if this is a POST request we need to process the form data
    if request.method == 'POST':
        # create a form instance and populate it with data from the request:
        form = MyForm(request.POST)
        # check whether it's valid:
        if form.is_valid():
            # process the data in form.cleaned_data as required
            # some actions
            return render(request, 'form_was_valid.html')

    # if a GET (or any other method) we'll create a blank form
    else:
        form = MyForm()

    return render(request, 'form.html', {'form': form})

Не забываем импортировать форму

Изменим файл form.html:

{% extends 'base.html' %}

{% block content %}
<form method="post" action="{% url 'form-view' %}">
    {% csrf_token %}
    {{ form }} {# Инпуты были заменены на переменную, в которой лежит объект класса Form #}
    <button type="submit">Send form</button>
</form>
{% endblock %}

и создадим файл form_was_valid.html:

{% extends 'base.html' %}

{% block content %}
<div style="background-color: deeppink"> FORM WAS VALID</div>
<a href="{% url 'form-view' %}">To the form page</a>
{% endblock %}

Что именно мы сделали.

Описание формы

В файле forms.py мы создали класс формы, в котором описали два атрибута nickname и age.

Они будут соответствовать двум инпутам, текстовому и числовому.

Типов существует естественно гораздо больше

Основные типы:

BooleanField - булеан

CharField - текст

ChoiceField - поле для выбора

DateTimeField - дата время

EmailField - имейл

FileField - файл

IntegerField - целое число

MultipleChoiceField - множественный выбор

И многие другие, почитать про них нужно тут

У каждого поля есть дополнительные параметры

Агрументы полей:

required - является ли поле обязательным

label - лейбл, текст к инпуту

label_suffix - символ между лейблом и инпутом

initial - значение по умолчанию

widget - описано ниже

help_text - подсказка к инпуту

error_messages - переписать стандартные тексты для ошибок типов полей

validators - дополнительные проверки поля

localize - информация о переводе формы на другие языки

disabled - сделать поле не активным (без возможности изменения)
Подробнее тут

Виджеты

У полей формы есть такое понятие как виджет, он отвечает за то, как именно будет отображаться конкретный филд, например, для текста базово это текстовое поле, а для даты и времени, это встроенный пикер (выпадающее окно с календарём и часами) итд.

Виджет можно указать отличающийся от стандартного.

Прочитать про виджеты нужно тут.

Описание view

В переменной request хранится информация о том, какой именно тип запроса к нам пришел, а это значит, что простым if мы можем разграничить логику которая будет обрабатывать разные типы запросов.

Если мы просто открываем страницу в браузере, то на самом деле мы посылаем обыкновенный GET запрос.

Взглянем на код. При GET запросе, мы не попадаем в первое условие, переменной form назначаем объект класса MyForm без каких либо данных, и после этого рендерим страницу передав на страницу пустой объект класса формы.

При рендере объекта класса формы в шаблоне, этот объект преобразуется в набор инпутов, с уже указанными атрибутами name

Если мы заполним данные, и нажмём на кнопку Send form, то мы отправим по этому же урлу запрос, но уже типа POST с заполнеными данными.

Посмотрим в код еще раз, мы попадём в первый if, и переменной form назначим объект класса MyFrom, но предварительно, передав туда данные через request.POST.

А значит на этом этапе у нас есть объект, с данными переданными нам от клиента.

Данные которые мы получили из реквеста всегда нужно валидировать (проверять)

Валидация формы

Тут вся документация по валидации.

За валидацию данных в форме отвечает встроенный метод is_valid() который применяется к объекту класса формы.

Этот метод возвращает нам булевое значение, True если данные валидны, False если нет.

После вызова этого метода у переменной к которой он был вызван (в нашем случае переменная form) появляются дополнительные атрибуты

Если форма была валидна, то появляется дополнительный аттрибут cleaned_data, это словарь, в котором будет хранятся все данные которые прислал нам пользователь (например логин и пароль)

Если форма не была валидна, то появляется дополнительные аттрибут errors, который хранит в себе информацию об ошибках конкретных полей, или общих ошибках.

Этот атрибут сразу хранит информацию как отображать эти ошибки в шаблоне, если они существуют

Валидность

Что же вообще такое валидность?

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

Некоторые распространённые виды валидаций можно указать как атрибут поля формы, например, максимальную длинну для строки, или максимальное и минимальное значение для числа.

clean_field

Если мы вызываем метод is_valid() мы проверяем все описанные валидации. Но где они описаны, и можем ли мы добавить свои?

Описаны они в классе формы, и да мы можем добавить свои.

Все базовые валидации были описаны при создании полей.

Но допустим мы считаем что для нашей формы валидным является только чётный возраст, как нам это проверить?

Для проверки конкретного поля, в форме класса нужно указать, метод который будет начинатся со слова clean_ и после этого название поля, которое мы валидируем.

Все данные будут лежать в аттрибуте self.cleaned_data.

Если значение валидно, то метод должен возвращать значение этого аттрибута.

Если значение не валидно, то метод должен рейзить ошибку ValidationError с описанием ошибки, которая позже будет отображаться на html.

В forms.py:

from django import forms
from django.core.exceptions import ValidationError

class MyForm(forms.Form):
    nickname = forms.CharField(label='My nickname', max_length=100)
    age = forms.IntegerField(label='My age')

    def clean_age(self):
        age = self.cleaned_data.get('age')
        if not age % 2:
            raise ValidationError('Age should be odd')
        return age

clean

А что делать если нужно проверить соответствие данных между собой, например, что пользователь не использовал свой возраст как часть своего никнейма?

Для этого мы можем использовать метод clean в котором можем выполнить любые необходимые нам проверки.

Для выполнения всех базовых проверок обычно используется super

В forms.py

from django import forms
from django.core.exceptions import ValidationError

class MyForm(forms.Form):
    nickname = forms.CharField(label='My nickname', max_length=100)
    age = forms.IntegerField(label='My age')

    def clean_age(self):
        age = self.cleaned_data.get('age')
        if not age % 2:
            raise ValidationError('Age should be odd')
        return age

    def clean(self):
        cleaned_data = super().clean()
        age = cleaned_data.get('age')
        nickname = cleaned_data.get('nickname')
        if str(age) in nickname:
            raise ValidationError('Age cannot be in nickname')

Метод clean ничего не возвращает, это нормально :)

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

Для этого может использоваться метод класса формы add_error, принимает два параметра, название поля к которому относится ошибка (может быть None, если ошибка не относится к какому либо полю, например неправильный юзернейм и/или пароль.

В forms.py:

from django import forms
from django.core.exceptions import ValidationError

class MyForm(forms.Form):
    nickname = forms.CharField(label='My nickname', max_length=100)
    age = forms.IntegerField(label='My age')

    def clean_age(self):
        age = self.cleaned_data.get('age')
        if not age % 2:
            raise ValidationError('Age should be odd')
        return age

    def clean(self):
        cleaned_data = super().clean()
        age = cleaned_data.get('age')
        nickname = cleaned_data.get('nickname')
        if str(age) in nickname:
            self.add_error('age', 'Age cannot be in nickname')
        self.add_error(None, 'This form always incorrect')

Отображение формы в шаблоне.

Итак, если наша форма была валидна, то мы отрендерили вообще другую страницу, но если всё таки была не валидна, то мы отрендерим форму, у которой есть атрибут errors, ошибки сразу же будут отрисованы.

Так же у нас есть способы по разному отрисовывать формы:

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

{{ form.as_table }} - рендер в виде таблицы, через теги

{{ form.as_p }} рендер каждого поля через

теги

{{ form.as_ul }} рендер в виде списка через теги

Так-же, можно рендерить форму не целиком, а например, по отдельным филдам, при помощи стандартного обращения через точку, например {{ form.name }}

У каждого поля есть аттрибут errors который хранит информацию об ошибках по этому полю, если они были обнаружены. {{ form.my_field.errors }}

Если запустить форму через for в итерируемом объекте будут поля.

    {% for field in form %}
    <div class="fieldWrapper">
        {{ field.errors }}
        {{ field.label_tag }} {{ field }}
        {% if field.help_text %}
        <p class="help">{{ field.help_text|safe }}</p>
        {% endif %}
    </div>
    {% endfor %}

И многие другие атрибуты и методы, подробно можно прочитать тут

Методы и свойства модели User

Модель User

Django предоставляет нам встроенную модель юзера, у которой уже реализовано много полей и методов

Документация про юзера тут

class User(AbstractUser):  
    """  
 Users within the Django authentication system are represented by this model.  
 
 Username and password are required. Other fields are optional. 
 """  
    class Meta(AbstractUser.Meta):  
	    swappable = 'AUTH_USER_MODEL'

Стандартный юзер содержит в себе такие полезные поля как:

username
password
email
first_name
last_name

is_active
is_staff
is_superuser

Также содержит много системных полей.

Также содержит базовый метод set_password и информацию о группах доступа.

Для использования модели пользователя, которую нам нужно расширить (а это нужно почти всегда) используется наследование от базового абстрактного юзера.

Выглядит примерно так:

from django.contrib.auth.models import AbstractUser
from django.db import models

class MyUser(AbstractUser):
    birth_date = models.DateField()
    avatar = models.ImageField(blank=True, null=True)

Для того, что бы Django оценивала эту модель как пользователя, в settings.py нужно в любом месте указать:

AUTH_USER_MODEL = 'myapp.MyUser' 

Где myapp - название приложения, MyUser - название модели.

Юзер обязательно должен быть описан до первой миграции!! Иначе джанго автоматически будет использовать базового встроенного юзера, и использовать сразу несколько у вас не получится. (Это работает потому что если не переписать модель и настройку, то юзер который уже есть в базе данных таблицу и её нельзя будет переопределить под другую модель после)

Все возможные подробности про модель юзера тут

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

is_stuff - поле для определения сотрудника (допустим сотрудник магазина, который добавляет товары)

is_superuser - поле для определения администратора (например, может изменять список сотрудников, а чаще всего обладает практически не ограниченными параметрами)

Также хранит инфо о последнем логине пользователя и дате создания пользователя.

Объект юзера содержит много полезных методов

get_username()  # получить юзернейм

get_full_name()  # получить имя и фамилию через пробел

set_password(raw_password)  # установить хешированый пароль

check_password(raw_password)  # проверить пароль на правильность

set_unusable_password()  # разрешить использовать пустую строку как пароль

email_user(subject, message, from_email=None, **kwargs)  # отправить пользователю имейл 

Например:

from django.contrib.auth.models import User

u = User.objects.get(username='blabla')
u.check_password('some_cool_password')  # True

И другие методы, отвечающие за доступы, группы итд.

Менеджер юзера

Содержит дополнительные методы

`create_user(username, email=None, password=None, **extra_fields)`

`create_superuser(username, email, password, **extra_fields)`

create_user отличается от create тем, что create_user правильно задаст пароль, через set_password

Мы не храним пароль в чистом виде, только хешированым

Логин

На самом деле логин состоит из нескольких частей, давайте их рассмотрим

Идентификация, Аутентификация, Авторизация

enter image description here

Идентификация — это процедура распознавания субъекта по его идентификатору (проще говоря, это процесс определения конкретного лица по имени, логину или номеру).
Например, получить конкретного пользователя из базы.
Идентификатором может быть:

  • номер телефона
  • номер паспорта
  • e-mail
  • номер страницы в социальной сети и т.д.

Аутентификация – это процедура проверки подлинности (пользователя проверяют с помощью пароля, письмо проверяют по электронной подписи и т.д.)
Например, проверить логин и пароль на соответствие.

  • Пароль – то, что мы знаем (слово, PIN-код, код для замка, графический ключ)
  • Устройство – то, что мы имеем (пластиковая карта, ключ от замка, USB-ключ)
  • Биометрика – то, что является частью нас (отпечаток пальца, портрет, сетчатка глаза)

Авторизация – это предоставление доступа к какому-либо ресурсу.
Например, к электронной почте.

Примеры авторизации:

  • Открытие двери после проворачивания ключа в замке
  • Доступ к электронной почте после ввода пароля
  • Разблокировка смартфона после сканирования отпечатка пальца
  • Выдача средств в банке после проверки паспорта и данных о вашем счете

Как это работает

Для того, что бы пользователь мог авторизоваться на сайте, нам нужны его входные данные и стандартные методы authenticate, login

Метод authenticate отвечает сразу за два процесса, аутентификации и идентификации, принимает, имя пользователя и пароль, и если находит совпадение то, возвращает объект пользователя(модели), если не находит, то возвращает None.

Если нам вернулся объект юзера, значит что аутентификация пройдена, и пользователь идентифицирован.

Метод login принимает, реквест, и объект модели пользователя и отвечает за процесс авторизации, после этого действия, во всех следующих запросах, в переменной request будет хранится наш текущий пользователь.

По этому стандартным способом, для авторизации является примерно такой код:

В forms.py

from django.contrib.auth import authenticate
from django import forms

class AuthenticationForm(forms.Form):
    username = forms.CharField(max_length=254)
    password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)

    def clean(self):
        cleaned_data = super().clean()
        username = cleaned_data.get('username')
        password = cleaned_data.get('password')
        if username and password:
            self.user = authenticate(username=username, password=password)
            if self.user is None:
                raise forms.ValidationError()

в view.py

from .forms import AuthenticationForm
from django.contrib.auth import login

def my_login(request):
    # if this is a POST request we need to process the form data
    if request.method == 'POST':
        # create a form instance and populate it with data from the request:
        form = AuthenticationForm(request.POST)
        # check whether it's valid:
        if form.is_valid():
            # process the data in form.cleaned_data as required
            # some actions
            login(request, form.user)
            return HttpResponseRedirect('/')

    # if a GET (or any other method) we'll create a blank form
    else:
        form = AuthenticationForm()

    return render(request, 'login.html', {'form': form})

Logout

Для вывода пользователя из системы, используется метод logout который принимает только реквест.

from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    # Redirect to a success page.

Проверка на то, что пользователь уже зашел в систему

В реквесте всегда есть поле user, у которого всегда есть аттрибут is_authenticated проверяя его, мы можем определять является ли пользователь авторизированным

request.user.is_authenticated

Закрыть страницу от не залогиненего пользователя

Для того, что бы не предоставлять доступ, для не залогиненых пользователей, существует два способа, для функционально описанных вью это декоратор @login_required

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    ...

Он так-же может принимать ссылку, на страницу логина, и автоматически отправлять на эту страницу, для не залогиненного пользователя.

from django.contrib.auth.decorators import login_required

@login_required(login_url='/accounts/login/')
def my_view(request):
	pass

В теплейтах user доступен через переменную user

{% if user.is_authenticated %}  
    {{ user.username }}  
{% else %}  
    Anonymous  
{% endif %}

Домашнее задание / Практика:

  1. Напишите форму, в которой можно указать имя, пол, возраст и уровень владения английским (выпадающим списком), если введенные данные это парень старше 20-и (включительно) и уровнем английского B2 выше, или девушка старше 22-ух и уровнем выше чем B1 то перейти на страницу где будет написано, что вы нам подходите, или что не подходит соответсвенно.

  2. Пишем страницу логина и логаута руками, проверяем, что всё работает.

  3. Написать страницу для регистрации. (не забываем про set_password)
    Следующая страница должна открываться только залогиненым пользователям

  4. Пишем страницу для смены пароля. (Запрашиваем текущий пароль 2 раза, и проверяем через check_password)

  5. Написать страницу с гет формой, для поиска по тексту ваших комментариев, отобразить все найденные частичные совпадение, без учёта регистра.

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

Литература (что почитать)

  1. HTML Forms by w3schools
  2. Межсайтовая подделка запроса
  3. Поля формы