Django 5 для начинающих

Прогресс по курсу:  9/1004

7.2 Использование системы аутентификации Django
4 из 7 шагов пройдено
0 из 11 баллов  получено

Как вы уже поняли UserCreationForm() в django.contrib.auth.forms​​​​​​ предоставляет ограниченное количество полей.

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

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

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


Сначала мы создадим файл forms.py и новый класс SignUpForm в файле forms.py приложения accounts:

from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm


class SignUpForm(UserCreationForm):
    username = forms.CharField(max_length=30)
    email = forms.EmailField(max_length=200)

    class Meta:
        model = User
        fields = ['username', 'email', 'password1', 'password2']


А в файле views.py мы изменим:

from .forms import SignUpForm
from django.urls import reverse_lazy
from django.views import generic


class SignUpView(generic.CreateView):
    form_class = SignUpForm
    success_url = reverse_lazy("login")
    template_name = "registration/signup.html"

То есть мы просто импортировали из форм новую форму и переопределили наш form_class.

Теперь протестируем, регистрируем новый аккаунт:


Попробуем войти после регистрации:

Попробуем восстановить пароль, переходим по ссылке http://127.0.0.1:8000/accounts/password_reset/ и вводим наш e-mail указанный при регистрации, нажимаем Send me instructions и видим страницу успешной отправки с текстом Check your inbox.

Далее зайдем на почту и проверим наше письмо:


Мы можем перейти по ссылке и изменить наш пароль. Также откроем админ-панель и посмотрим там:

Как вы поняли, у нас все работает, по такому же принципу мы можем добавить еще First Name и Last Name.


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

 <p><a href="{% url 'password_reset' %}">Reset Password</a></p>


И теперь мы видим новую ссылку на восстановление пароля:

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


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

В базе есть пользователи у которых совпадают email. Соответственно при сбросе пароля, рассылка уходит для каждого пользователя. Подумал мб переопределить, по аналогии с SignUpForm еще и форму сброса пароля.

Решил поковырять код для закрепления материала:

- определить форму сброса пароля ResetPasswordForUserForm, где отображать username+email

- добавить валидацию на поля перед отправкой письма по сбросу пароля, а именно:

-- "Пользователь с username не найден"

-- "Почта email не привязана к пользователю username"

# accounts/urls.py

from .views import password_reset_for_user

urlpatterns = [
    ...
    path('password_reset_for_user', password_reset_for_user, name='password_reset_for_user'),
]
# accounts/views.py

from .forms import ResetPasswordForUserForm

# на CBV пока не осилил
def password_reset_for_user(request):
    form = ResetPasswordForUserForm()
    url = 'registration/password_reset_form.html'

    if request.method == 'POST':
        form = ResetPasswordForUserForm(request.POST)
        if form.is_valid():
            # send_mail(...)
            url = 'registration/password_reset_done.html'
    context = {
        'form': form,
    }
    return render(request, url, context)
# acconts/forms.py

from django.contrib.auth.forms import PasswordResetForm


class ResetPasswordForUserForm(PasswordResetForm):
    username = forms.CharField(max_length=30)
    email = forms.EmailField(max_length=200)

    field_order = ['username', 'email']

    def is_valid(self):
        base_valid = super(ResetPasswordForUserForm, self).is_valid()
        username = self.cleaned_data['username']
        email = self.cleaned_data['email']

        user = User.objects.filter(username=username)
        if not user:
            self.add_error('username', f'Пользователь {username} не найден в системе')
            return False
        if not user.filter(email=email):
            self.add_error('email', f'Почта {email} не принадлежит пользователю {username}')
            return False

        return base_valid

    class Meta:
        model = User
        fields = ('username', 'email')

 

Изменен Антон Глухенко

@Антон_Глухенко, есть еще вариант - сделать Email уникальным.
К сожалению, в модели User он необязательный и не проверяется на уникальность.
Для этого не обязательно переопределять модель User, можно реализовать в форме SignUpForm метод clean_email():

from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
from django.core.exceptions import ValidationError


class SignUpForm(UserCreationForm):
    class Meta:
        model = get_user_model()
        fields = ['username', 'email', 'password1', 'password2', 'first_name', 'last_name']

    def clean_email(self):
        email = self.cleaned_data['email']
        users = get_user_model()
        if users.objects.filter(email=email).exists():
            raise ValidationError(f'Entered email {email} already used in the database, please use another one')
        return email

По-хорошему нужно так же предотвратить ввод неуникального email в админ панели

А совсем по-хорошему email и должен быть username, но здесь придется уже делать собственную пользовательскую модель, видимо

Изменен ilya kutaev

Отмечу пару моментов:

1. Т.к. в форме SignUpForm мы не переопределяли поведение полей username и email, их можно было бы и не определять, просто перечислив в Meta.fields. Туда же можно сразу добавить и имя/фамилию

2. Чтобы не "привязываться" к конкретной модели User (она может быть переопределена, и тогда придется ее заменять везде, где она использовалась), можно использовать специальную функцию django.contrib.auth.get_user_model() - она возвращает текущую пользовательскую модель.

С учетом этого SignUpForm будет выглядеть следующим образом:

from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm

class SignUpForm(UserCreationForm):
    class Meta:
        model = get_user_model()
        fields = ['username', 'email', 'password1', 'password2', 'first_name', 'last_name']

Все работает точно так же

Изменен ilya kutaev

Респект! Курс крутой не сбавляйте планку только!

Изменен No Name

А в файле view.py мы изменим:

Любители файла view.py на месте?

@Anonymous_450292901, спасибо, исправил))

При попытке смены пароля получил письмо, но ссылки в нем на сайт example.com.
http://example.com/accounts/reset/Mw/br2f6z-f2b48f518ba925f9018a07be0e20b224/".

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

@Сак_Валентин, В разделе 7.9 "Добавление карты сайта" мы добавили SITE_ID = 1 и в админ панели меняли адрес домена.

@Илья_Перминов, как же я пропустил этот момент? Спасибо!

@Илья_Перминов, теперь нужно убедиться - я правильно понимаю, что мы пока нигде не создавали кнопку "signup"? Ее, откровенно говоря, не хватает:)

@Сак_Валентин, Можете добавить в базовый шаблон, в раздел сайдбара, следующий код:

<a href="{% url 'signup' %}">SignUp</a>

Можете сразу после:

<a href="{% url 'login' %}">Log In</a>

При попытке перейти по ссылке в письме выдаёт 404, что может быть не так?

@Александр_Ёлшин, а какая ссылка приходит?

@Александр_Ёлшин, в шаге https://stepik.org/lesson/973398/?unit=980250 забыли изменить имя домена на localhost.