Cat

Django 34.2. Простой профиль пользователя - страница настроек

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

Все статьи

Icon Link

Дополнительные материалы

Icon Link

Реклама

Icon Link
Сайт на Django proDream 25 Октябрь 2023 Просмотров: 1105

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

На этой странице пользователь сможет изменить свои данные и поменять пароль.

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

В процессе напишем две формы, два представления и два шаблона.
Работать будем с файлами в директории приложения user_app. Приступим.

 

Формы для страницы настроек.

Откроем файл forms.py в директории приложения user_app.

У нас будет две формы:

  • Форма изменения данных пользователя - это имя пользователя (username), email, имя и фамилия.
  • Форма смены пароля.

 

Форма изменения данных.

Это типовая форма модели.

Создадим класс UserInfoForm, унаследованный от forms.ModelForm.

Описываем четыре поля, добавляя названия полей и стили: username, email, first_name, last_name.
В подклассе Meta указываем модель User и вышеупомянутые поля.

 

Код формы:

class UserInfoForm(forms.ModelForm):  
    username = forms.CharField(  
        max_length=150,  
        label='Имя пользователя (username)',  
        widget=forms.TextInput(attrs={  
            'class': 'form-control',  
            'placeholder': 'Введите имя пользователя'  
        })  
    )  
    email = forms.EmailField(  
        widget=forms.EmailInput(attrs={  
            'class': 'form-control',  
            'placeholder': 'Введите email'  
        })  
    )  
    first_name = forms.CharField(  
        max_length=150,  
        label='Имя',  
        required=False,  
        widget=forms.TextInput(attrs={  
            'class': 'form-control',  
            'placeholder': 'Введите ваше имя'  
        })  
    )  
    last_name = forms.CharField(  
        max_length=150,  
        label='Фамилия',  
        required=False,  
        widget=forms.TextInput(attrs={  
            'class': 'form-control',  
            'placeholder': 'Введите вашу фамилию'  
        })  
    )  

    class Meta:  
        model = User  
        fields = ['username', 'email', 'first_name', 'last_name']

 

Форма изменения пароля.

Форму смены пароля писать с нуля не будем, т.к. Django предоставляет встроенную форму PasswordChangeForm.
Мы всего лишь переопределим поля, добавив стили форме.

Создадим класс UserPasswordForm, унаследованный от PasswordChangeForm и пропишем три поля: old_password, new_password1, new_password2.

 

Код формы:

class UserPasswordForm(PasswordChangeForm):  
    old_password = forms.CharField(  
        max_length=128,  
        label='Старый пароль',  
        widget=forms.PasswordInput(attrs={  
            'class': 'form-control',  
            'placeholder': 'Введите старый пароль'  
        })  
    )  
    new_password1 = forms.CharField(  
        max_length=128,  
        label='Новый пароль',  
        help_text=password_validation.password_validators_help_text_html(),  
        widget=forms.PasswordInput(attrs={  
            'class': 'form-control',  
            'placeholder': 'Введите новый пароль'  
        })  
    )  
    new_password2 = forms.CharField(  
        max_length=128,  
        label='Подтверждение нового пароля',  
        widget=forms.PasswordInput(attrs={  
            'class': 'form-control',  
            'placeholder': 'Повторите новый пароль'  
        })  
    )

 

Представление страницы настроек.

Достаточно сложное и объёмно представление.

Нам надо передать на страницу две формы, а затем обрабатывать их независимо друг от друга.

Помимо того, что страница должна быть доступна только авторизованному пользователю, она также должна быть доступна только тому пользователю, чья страница настроек открыта.
Например, у нас есть маршрут /user/testuser/settings, где testuser это имя пользователя. И если по этому адресу перейдёт testuser, то он увидит настройки, но если другой пользователь откроет эту страницу, то должна быть ошибка 403 Forbidden.

Откроем файл views.py.
Создадим класс UserSettingsView, в этот раз у нас будет два наследования:

  • От класса LoginRequiredMixin, проверяющего авторизован пользователь или нет.
  • От уже знакомого нам класса TemplateView.

Прописываем единственное поле template_name с указанием файла шаблона.

 

Далее у нас будет переопределено три метода:

Первым мы переопределим метод dispatch(). С ним мы ещё не сталкивались.

Метод dispatch() - это один из ключевых методов в классовых представлениях Django. Он является частью жизненного цикла обработки запросов и играет важную роль в определении того, какой метод (GET, POST, PUT, DELETE и т. д.) должен обрабатывать входящий HTTP-запрос.

dispatch() работает следующим образом:

  1. Когда клиентский запрос поступает на представление, Django вызывает метод dispatch() этого представления.
  2. dispatch() анализирует метод HTTP запроса (GET, POST, PUT, DELETE и т. д.), который пришел от клиента.
  3. В зависимости от метода запроса, dispatch() вызывает соответствующий метод класса представления. Например, если это GET-запрос, dispatch() вызовет метод get(), если это POST-запрос - метод post(), и так далее.
  4. После обработки запроса соответствующим методом, dispatch() возвращает HTTP-ответ.

В нашем методе, мы проверяем, является ли пользователь переданный в адресной строке текущим авторизованным пользователем.
Если является - вызываем метод dispatch() из родительского класса, в противном случае возвращаем 403 Forbidden.

 

Вторым переопределённым методом будет get_context_data. С ним мы уже сталкивались ранее и он вполне должен быть знаком, но и тут есть нюансы.
В этом методе мы передаём две формы и т.к. у нас формы моделей, нам при их сохранении в переменную, в качестве аргумента необходимо передать объект модели, с которым будет работать форма. Если этого не сделать, при сохранении данных будет создаваться новый объект модели, а не обновляться необходимый.

В качестве аргумента для формы UserInfoForm мы передаём ключевой аргумент instance с объектом пользователя.
А для формы UserPasswordForm, мы передаём объект пользователя в качестве позиционного аргумента.

 

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

В теле метода у нас находится if-elif-else конструкция.

  • if проверяет отправку формы с изменением данных.
  • elif проверяет отправку формы изменения пароля.
  • else возвращает эту же страницу без изменений.

Структура внутри if и elif в целом одинаковая, различается только действие в случае успеха.

В переменную form помещаем нашу форму, передав в неё объект пользователя и данные отправленные с формы на сайте. 
Напомню, что для UserInfoForm, объект пользователя передаётся как ключевой аргумент instance, т.е. он будет вторым аргументом, а данные формы как позиционный, т.е. первым.
В случае с формой UserPasswordForm, оба аргумента передаются как позиционные и первым идёт объект пользователя, а вторым данные формы.

Затем проверяем валидность формы.

Если форма валидна и данные корректны:
Сохраняем форму, тем самым внеся изменения в связанный с ней объект пользователя.
Записываем сообщение об успешном изменении, используя функцию messages.
Возвращаем результат.

В этом моменте происходят различия кода. 
При обработке формы изменения данных, для возврата результата мы используем функцию redirect, перенаправляющую пользователя на новую страницу. Она нужна, поскольку в адресной строке прописывается имя пользователя и при изменении имени пользователя и вызове стандартного метода get, происходит перенаправление, но путь в адресной строке не меняется, что влечёт за собой ошибки, а при использовании redirect, пользователь переходит на новый URL-адрес, включающий в себя новое имя пользователя.

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

Если форма не валидна:
Получаем контекст представления и при помощи метода render обновляем страницу, включающую в себя введённые ранее пользователем данные.

 

Код представления:

class UserSettingsView(LoginRequiredMixin, TemplateView):  
    template_name = 'user_app/profile_settings_page.html'  

    def dispatch(self, request, *args, **kwargs):  
        if request.user  get_object_or_404(User, username=self.kwargs.get('username')):  
            return super().dispatch(request, *args, **kwargs)  
        else:  
            raise HttpResponseForbidden("Вы не имеете доступа к этой странице.")  

    def get_context_data(self, **kwargs):  
        context = super().get_context_data(**kwargs)  
        context['user_info_form'] = forms.UserInfoForm(instance=self.request.user)  
        context['user_password_form'] = forms.UserPasswordForm(self.request.user)  
        context['title'] = f'Настройки профиля {self.request.user}'  
        return context  

    def post(self, request, *args, **kwargs):  
        if 'user_info_form' in request.POST:  
            form = forms.UserInfoForm(request.POST, instance=request.user)  
            if form.is_valid():  
                form.save()  
                messages.success(request, 'Данные успешно изменены.')  
                return redirect('user_app:user_profile_settings', form.cleaned_data.get('username'))  
            else:  
                context = self.get_context_data(**kwargs)  
                context['user_info_form'] = form  
                return render(request, self.template_name, context)  
        elif 'user_password_form' in request.POST:  
            form = forms.UserPasswordForm(request.user, request.POST)  
            if form.is_valid():  
                form.save()  
                messages.success(request, 'Пароль успешно изменён.')  
                return self.get(request, *args, **kwargs)  
            else:  
                context = self.get_context_data(**kwargs)  
                context['user_password_form'] = form  
                return render(request, self.template_name, context)  
        else:  
            return self.get(request, *args, **kwargs)

 

Шаблон страницы настроек профиля.

В директории с шаблонами приложения user_app создадим файл profile_settings_page.html.

В этом файле прописываем две формы: отображение сообщения об успешном изменении и кнопку возврата в профиль.

Обратите внимание на код кнопок отправки формы. В них мы указываем атрибут name. Именно по этому атрибуту мы отслеживаем в представлении то, какая форма была отправлена.

 

Код шаблона:

{% extends 'blog/base.html' %}
{% block title %}{{ title }}{% endblock %}

{% block content %}
    <div class="container mt-3">
        <div class="row d-flex justify-content-between align-items-center">
            <div class="col-lg-10 col-sm-12">
                <h2>Настройки профиля {{ user }}</h2>
            </div>
            <div class="col-lg-2 col-sm-12 text-right">
                <a class="btn btn-primary my-btn h-100"
                   href="{% url 'user_app:user_profile' username=user.username %}">Вернуться в профиль</a>
            </div>
        </div>
        <hr>
        <div class="row d-flex justify-content-evenly">
            <div class="row text-center">
                {% if messages %}
                    <p class="alert alert-success">
                        {% for message in messages %}
                            {{ message }}<br>
                        {% endfor %}
                    </p>
                {% endif %}
            </div>
            <div class="col-lg-4 col-sm-12">
                <h3 class="mb-3 mt-3">Изменить данные:</h3>
                <form method="post">
                    {% csrf_token %}
                    {{ user_info_form.as_p }}
                    <button class="btn btn-primary my-btn mt-2" type="submit" name="user_info_form">Отправить</button>
                </form>
            </div>
            <div class="col-lg-4 col-sm-12">
                <h3 class="mb-3 mt-3">Изменить пароль:</h3>
                <form method="post">
                    {% csrf_token %}
                    {{ user_password_form.as_p }}
                    <button class="btn btn-primary my-btn mt-2" type="submit" name="user_password_form">Отправить
                    </button>
                </form>
            </div>
        </div>
    </div>
{% endblock %}

 

Страница с постами пользователя.

Представление.

В файле views.py создадим класс UserPostsView, унаследованный от ListView.

Пропишем три поля:

  • template_name - файл шаблона.
  • context_object_name - имя переменной в шаблоне
  • paginate_by - количество элементов на странице

Переопределим метод get_queryset. В нём вернём список постов, отфильтрованных по переданному в URL-адресе пользователю.

В переопределённом методе get_context_data, в блоке try-except получим объект пользователя по имени пользователя из URL-адреса и запишем его в контекст.

 

Код представления:

class UserPostsView(ListView):  
    template_name = 'user_app/user_posts_page.html'  
    context_object_name = 'posts'  
    paginate_by = 10  

    def get_queryset(self):  
        return PostModel.post_manager.filter(author__username=self.kwargs.get('username'))  

    def get_context_data(self, **kwargs):  
        context = super().get_context_data(**kwargs)  
        try:  
            author = get_object_or_404(User, username=self.kwargs.get("username"))  
        except User.DoesNotExist:  
            raise Http404("Пользователь не найден")  
        context['author'] = author  
        context['title'] = f'Посты пользователя {author}'  
        return context

 

Шаблон.

В директории с шаблонами приложения user_app создадим файл user_posts_page.html.

В качестве шаблона я использовал код из файла tag_page.html.

 

URL-паттерны страницы настроек и всех постов пользователя.

Откроем файл urls.py в директории приложения user_app и добавим следующие строки в список urlpatterns:

path('<str:username>/settings/', views.UserSettingsView.as_view(), name='user_profile_settings'),  
path('<str:username>/posts/', views.UserPostsView.as_view(), name='user_posts'),

 

Завершение.

Осталось изменить шаблон страницы профиля, описанный в прошлом посте, а именно, исправить код кнопок.

Кнопка все посты:

<a class="btn btn-primary my-btn mb-3"  
   href="{% url 'user_app:user_posts' username=user_profile.username %}">Все посты {{ user_profile }}
</a>

 

Кнопка настроек профиля:

<a class="btn btn-primary my-btn me-1"  
   href="{% url 'user_app:user_profile_settings' username=user_profile.username %}">Настройки профиля
</a>

 

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

 

Автор

    Нет комментариев

    Реклама