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

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

7.4 Профили пользователей и пользовательские поля модели User
5 из 8 шагов пройдено
0 из 15 баллов  получено

Изменение пароля

Обычно на странице профиля пользователи должны иметь возможность изменить свой пароль. В разделе 7.2 мы с вами сделали уже восстановление забытого пароля, но теперь мы должны добавить возможность его поменять. Это будет очень просто, потому что принцип будет такой же.

В Django есть представление PasswordChangeView, которое позволяет пользователям менять свой пароль. Давайте создадим наше собственное представление, которое будет расширяться и переопределять некоторые атрибуты класса.

Поэтому в файл accounts/views.py добавим следующий класс:

from django.contrib.auth.views import PasswordChangeView
from django.contrib.messages.views import SuccessMessageMixin


class ChangePasswordView(SuccessMessageMixin, PasswordChangeView):
    template_name = 'registration/change_password.html'
    success_message = "Successfully Changed Your Password"
    success_url = reverse_lazy('users-profile')

Теперь перейдите к accounts/urls.py нашего приложения и создайте маршрут для этого представления:

from .views import SignUpView, CustomLoginView, profile, ChangePasswordView

urlpatterns = [
#......
    path('password_change/', ChangePasswordView.as_view(), name='password_change'),
]

Наконец, создайте шаблон для представления registration/change_password.html:

{% extends "blog/base.html" %}
{% block content %}
    <h3>Change Your Password</h3>
    {% if form.errors %}
        <div role="alert">
            <div id="form_errors">
                {% for key, value in form.errors.items %}
                    <strong>{{ value }}</strong>
                {% endfor %}
            </div>
            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
    {% endif %}

    <form method="POST">
        {% csrf_token %}

        <label>Old Password</label>
        <input type="password" name="old_password" autocomplete="new-password" required id="id_old_password"
               placeholder="Enter Old Password"/>
        <br>


        <label>New Password</label>
        <input type="password" name="new_password1" autocomplete="new-password"
               required id="id_new_password1"
               placeholder="Enter New Password"/>

        <br>

        <label for="id_new_password2">New Password Confirmation</label>
        <input type="password" name="new_password2" autocomplete="new-password"
               required id="id_new_password2" placeholder="Confirm New Password"/>

        <br>
        <button type="submit" id="reset">Update Password</button>
    </form>
{% endblock content %}

На странице профиля(registration/profile.html) обновите ссылку <a href="#">Change Password</a>, как показано ниже:

<a href="{% url 'password_change' %}">Change Password</a>

Запустите сервер разработки и выполните обычную команду python manage.py runserver в своем терминале.
Теперь перейдите по адресу http://127.0.0.1:8000/accounts/signup/ и создайте нового пользователя.

Далее перейдите по адресу http://127.0.0.1:8000/accounts/profile/ и мы видим:

Попробуем изменить аватар и добавить информацию в поле биографии:

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

И напоследок, давайте внесем изменения в наш базовый шаблон, для этого откроем blog/base.html и внесем изменения в нашем сайдбаре:

    {% if user.is_authenticated %}
        Hi {{ user.username }}!
        <p><a href="{% url 'users-profile' %}">My profile</a></p>
        <form action="{% url 'logout' %}" method="post">
            {% csrf_token %}
            <p><a href="#" onclick="parentNode.submit();">Log Out</a></p>
        </form>
    {% else %}
        <p>You are not logged in</p>
        <p><a href="{% url 'login' %}">Log In</a></p>
        <p><a href="{% url 'password_reset' %}">Forgot your password?</a></p>
    {% endif %}

И получим следующие вид при переходе на сайт, а именно ссылку на страницу авторизации и ссылку для восстановления пароля:

После авторизации у нас будет ссылка на наш профиль и ссылка на выход из системы:

На этом все, в следующем модуле мы подключим с вами авторизацию через социальные сети.


  • Комментариев
Будьте вежливы и соблюдайте наши принципы сообщества. Пожалуйста, не оставляйте решения и подсказки в комментариях, для этого есть отдельный форум.
Оставить комментарий
class ChangePasswordView(SuccessMessageMixin, PasswordChangeView):
    template_name = 'registration/change_password.html'
    success_message = "Successfully Changed Your Password"
    success_url = reverse_lazy('users-home')

reverse_lazy с урлом 'users-home' вызывает исключение NoReverseMatch. Заменил 'users-profile' и все заработало.

@Александр_Левкин, спасибо, поправил.

В этом пункте разве не надо передавать 'logout' через форму ? как мы делали до этого:

И напоследок, давайте внесем изменения в наш базовый шаблон, для этого откроем blog/base.html и внесем изменения в нашем сайдбаре:

    {% if user.is_authenticated %}
        Hi {{ user.username }}!
        <p><a href="{% url 'users-profile' %}">My profile</a></p>
        <p><a href="{% url 'logout' %}">Log Out</a></p>
    {% else %}
        <p>You are not logged in</p>
        <p><a href="{% url 'login' %}">Log In</a></p>
        <p><a href="{% url 'password_reset' %}">Forgot your password?</a></p>
    {% endif %}

@Владислав_Финогенов, Присоедияюсь к вопросу

@Владислав_Финогенов@ilya_kutaev, в Django 5 выход только через POST запрос, поправил код. Спасибо!

Ребят ещё бы хотел рассказать о своих выкладках при передачи данных из бд в форму, используя связи, возможно все уже обладают этим знанием, но всё равно напишу. строка: instance=request.user.profile  , вообще это может кого-то смутить, что мы обращаемся через .profile а не .profile_set  , ибо мы не указывали related_name при создании связанного поля, но насколько я понял при связях OneToOne и ManyToMany мы можем это не указывать, ибо в этом случае Django магически создает realted_name на основе названия модели в нижнем регистре, что не работает для ForeignKey, если создатели курса увидят этот коммент буду рад увидеть доп-пояснения, чтобы понять механизм глубже.

@Шамбер_Егор, не совсем понял вопроса, если честно. Давайте попробуем разобрать следующую модель:

class Contact(models.Model):
    phone_number = models.CharField(max_length=10)
    email = models.EmailField()

class Company(models.Model):
    name = models.CharField(max_length=30)
    contact = models.OneToOneField(Contact, on_delete=models.CASCADE)

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

>>> contact = Contact.objects.get(id=1)
>>> contact.company
<Company: Company object (1)>

>>> company = Company.objects.get(id=1)
>>> company.contact
<Contact: Contact object (1)>

Но если мы возьмем отношения один ко многим, тут немного ситуация иная.

class Company(models.Model):
    name = models.CharField(max_length=30)


class Product(models.Model):
    company = models.ForeignKey(Company, on_delete=models.CASCADE)
    name = models.CharField(max_length=30)
    price = models.IntegerField()

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

>>> product = Product.objects.get(id=1)
>>> product.company
<Company: Company object (1)>

company = Company.objects.get(id=1)
>>> company.product
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Company' object has no attribute 'product'

Чтобы получить данные в "обратном направлении", нам приходится использовать специальное свойство _set

>>> company.product_set.all()
<QuerySet [<Product: Product object (1)>]>

 

Теперь для отношения многие ко многим, там ситуация такая же, как и у отношений один ко многим.

class Category(models.Model):
    name = models.CharField(max_length=100)


class Product(models.Model):
    category = models.ManyToManyField(Category)
    name = models.CharField(max_length=30)
    price = models.IntegerField()

Получаем продукт, и получаем его категории. Но если мы попробуем у категории получить продукты, то получим ошибку.

>>> product = Product.objects.get(id=1)
>>> product.category.all()
<QuerySet [<Category: Category object (1)>]>


>>> category = Category.objects.get(id=1)
>>> category.product
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Category' object has no attribute 'product'

И также в обратном направлении используем _set

>>> category.product_set.all()
<QuerySet [<Product: Product object (1)>]>

Надеюсь понятно написал))

Кажется это уже сделано в прошлом шаге:

@Марат_Асылбаев, Спасибо, поправили.

Наверно тут должно быть  "перейдите к urls.py приложения"?

@Марат_Асылбаев, Спасибо, поправили.

У меня с кнопочкой "Понравился урок" непонятное. После некоторых уроков она прибавляет единичку, а иногда отнимает. Как так. Если отнимает, я возвращаю как было и снова нажимать боюсь. Сначала глазам не поверила, думала померещилось, но она действительно себя так ведет. На этом уроке опять -1

@Ольга_Миронова, Степик убрал оценку отдельных шагов где-то полгода назад, теперь можно оценить только урок - только один раз  поставить оценку на любой шаг и она автоматически установится на все шаги урока. Соответственно если уже ставили лайк в уроке, то следующий лайк в уроке его снимет.

@Дмитрий_Селезнев, спасибо. Это называется - интуитивно понятный интерфейс.

<label>Old Password</label> <input type="password" name="old_password" autocomplete="new-password" required id="id_old_password" placeholder="Enter Old Password"/> может я что-то не понимаю, но может autocomplete надо сделать old-password

@Шамбер_Егор, только не old-password, а current-password, это текущий сохранённый пароль.

https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete

это тоже нужно подправить, указать что в файле registration/profile.html

в этом месте 
 

<hr>
<div><a href="{% url 'password_change' %}">Change Password</a>
    <hr>
    <label>Change Avatar:</label>
    {{ profile_form.avatar }}
</div>
<hr>


иначе сложно воспринимать эту инфу


На странице профиля обновите ссылку <a href="#">Change Password</a>, как показано ниже:

<a href="{% url 'password_change' %}">Change Password</a>
 

Перехожу на профиль http://127.0.0.1:8000/accounts/profile/  и вот такая ошибка:

@Maxim_Lapshin, это у всех пользователей так? Или только у администратора?

@Илья_Перминов, в вашем уточнении я нашел ответ) админ у меня заходит нормально, админ2(уже созданный) заходил с ошибкой, я создал админ3, который тоже нормально открывал профиль. Проблема была только во втором

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