Cat

Django 35.2. Расширенный профиль пользователя - форма и шаблон

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

Все статьи

Icon Link

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

Icon Link

Реклама

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

Мы написали модель и сделали сигнал о регистрации.
Теперь займёмся расширенной формой и доработкой шаблона.

 

Форма модели профиля.

В форме пропишем следующие поля: gender, dob, site_link, telegram_link, user_avatar, show_last_name, show_email, show_telegram.

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

Опишу только новые или отличающиеся поля:

  • gender - поле выбора ChoiceField. В аргументах указываем название поля, доступные выборы параметром choices и виджет Select.
  • dob - поле даты DateField. Указываем название поля и виджет DateInput. В аргументах к виджету передаём параметр format. С этим связана забавная особенность. В БД дата хранится в виде ГГГГ-ММ-ДД, однако в нашей локали принята запись ДД.ММ.ГГГГ. В шаблон оно передаётся в нашем виде, но HTML-форма даты принимает только в формате ГГГГ-ММ-ДД, при этом отображает в формате пользовательской локали. Сложно и не понятно. Для решения этой ситуации указываем параметр format='%Y-%m-%d'. Также в параметр attrs, помимо класса стиля, передаём тип формы 'type': 'date', т.к. по умолчанию DateInput это просто текстовое поле.
  • user_avatar - поле загрузки файлов FileField. В атрибутах название поля и виджет FileInput. В параметре attrs виджета, кроме класса, указываем тип поля 'type': 'file'.
  • show_last_name, show_email, show_telegram - логические поля BooleanField. Виджет поля CheckboxInput.

Ниже класс Meta с указанием модели ProfileModel и перечислением полей.

 

Код формы:

class ProfileForm(forms.ModelForm):  
    gender = forms.ChoiceField(  
        label='Пол',  
        choices=models.ProfileModel.Genders.choices,  
        required=False,  
        widget=forms.Select(  
            attrs={  
                'class': 'form-select',  
            }  
        )  
    )  
    dob = forms.DateField(  
        label='Дата рождения',  
        required=False,  
        widget=forms.DateInput(  
            format='%Y-%m-%d',  
            attrs={  
                'type': 'date',  
                'class': 'form-control',  
            }  
        )  
    )  
    site_link = forms.CharField(  
        label='Личный сайт',  
        required=False,  
        widget=forms.TextInput(  
            attrs={  
                'class': 'form-control',  
                'placeholder': 'https://...'  
            }  
        )  
    )  
    telegram_link = forms.CharField(  
        label='Профиль в Telegram',  
        required=False,  
        widget=forms.TextInput(  
            attrs={  
                'class': 'form-control',  
                'placeholder': 'https://t.me/...'  
            }  
        )  
    )  
    user_avatar = forms.FileField(  
        label='Аватарка',  
        required=False,  
        widget=forms.FileInput(  
            attrs={  
                'type': 'file',  
                'class': 'form-control',  
            }  
        )  
    )  

    show_last_name = forms.BooleanField(  
        required=False,  
        widget=forms.CheckboxInput(  
            attrs={  
                'class': 'form-check-input mt-0',  
            }  
        )  
    )  
    show_email = forms.BooleanField(  
        required=False,  
        widget=forms.CheckboxInput(  
            attrs={  
                'class': 'form-check-input mt-0',  
            }  
        )  
    )  
    show_telegram = forms.BooleanField(  
        required=False,  
        widget=forms.CheckboxInput(  
            attrs={  
                'class': 'form-check-input mt-0'  
            }  
        )  
    )  

    class Meta:  
        model = models.ProfileModel  
        fields = ['gender', 'dob', 'site_link', 'telegram_link', 'user_avatar', 'show_last_name', 'show_email',  
                  'show_telegram']

 

Изменение представления страницы настроек профиля.

Для добавления новой формы нам надо добавить несколько строк.

В метод get_context_data добавляем строку с передачей новой формы в шаблон, передавая в качестве аргумента объект профиля:

context['user_profile_form'] = forms.ProfileForm(instance=self.request.user.profile)

 

В методе post, в первом условии if 'user_info_form' in request.POST:, добавляем новую переменную формы, передавая в неё данные из POST-запроса, файл из POST-запроса и объект профиля.

user_profile_form = forms.ProfileForm(request.POST, request.FILES, instance=self.request.user.profile)

 

Далее в проверку на валидность, добавляем проверку и второй формы:

if user_info_form.is_valid() and user_profile_form.is_valid():

 

После чего, если обе формы валидны, сохраняем объекты с новыми данными:

user_info_form.save()  
user_profile_form.save()

 

Если одна или обе формы не валидны, возвращаем формы с введёнными данными:

context['user_info_form'] = user_info_form  
context['user_profile_form'] = user_profile_form

 

Код изменённых методов:

def get_context_data(self, **kwargs):  
    context = super().get_context_data(**kwargs)  
    context['user_info_form'] = forms.UserInfoForm(instance=self.request.user)  
    context['user_profile_form'] = forms.ProfileForm(instance=self.request.user.profile)  
    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:  
        user_info_form = forms.UserInfoForm(request.POST, instance=request.user)  
        user_profile_form = forms.ProfileForm(request.POST, request.FILES, instance=self.request.user.profile)  
        if user_info_form.is_valid() and user_profile_form.is_valid():  
            user_info_form.save()  
            user_profile_form.save()  
            messages.success(request, 'Данные успешно изменены.')  
            return redirect('user_app:user_profile_settings', user_info_form.cleaned_data.get('username'))  
        else:  
            context = self.get_context_data(**kwargs)  
            context['user_info_form'] = user_info_form  
            context['user_profile_form'] = user_profile_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)

 

Шаблон страницы настроек:

Тут мы объединяем обе формы в одном теге form. Дополнительным аргументом указываем enctype="multipart/form-data", сообщая, что эта форма может принимать файлы.

Далее, можно по стандарту вывести обе формы:

{% csrf_token %}
{{ user_info_form.as_p }}
{{ user_profile_form.as_p }}

 

Но я для лучшего отображения применил классы Bootstrap. Для этого пришлось каждое поле выводить по отдельности.

 

Код шаблона:

{% 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-8 col-sm-12">
                <form method="post" enctype="multipart/form-data">
                    <div class="row">
                        <div class="col-lg-6 col-sm-12">
                            <h3 class="mb-3 mt-3">Основная информация:</h3>
                            {% csrf_token %}
                            {% for error in user_info_form.errors.values %}
                                {{ error }}
                            {% endfor %}

                            {{ user_info_form.username.label }}
                            {{ user_info_form.username }}

                            <br>
                            {{ user_info_form.email.label }}
                            <div class="input-group">
                                <div class="input-group-text">
                                    {{ user_profile_form.show_email }}
                                    <span class="ms-2">Отображать?</span>
                                </div>
                                {{ user_info_form.email }}
                            </div>

                            <br>
                            {{ user_info_form.first_name.label }}
                            {{ user_info_form.first_name }}

                            <br>
                            {{ user_info_form.last_name.label }}
                            <div class="input-group">
                                <div class="input-group-text">
                                    {{ user_profile_form.show_last_name }}
                                    <span class="ms-2">Отображать?</span>
                                </div>
                                {{ user_info_form.last_name }}
                            </div>
                        </div>
                        <div class="col-lg-6 col-sm-12">
                            <h3 class="mb-3 mt-3">Дополнительная информация:</h3>
                            {% for error in user_profile_form.errors.values %}
                                {{ error }}
                            {% endfor %}

                            {{ user_profile_form.gender.label }}
                            {{ user_profile_form.gender }}

                            <br>
                            {{ user_profile_form.dob.label }}
                            {{ user_profile_form.dob }}

                            <br>
                            {{ user_profile_form.site_link.label }}
                            {{ user_profile_form.site_link }}

                            <br>
                            {{ user_profile_form.telegram_link.label }}
                            <div class="input-group">
                                <div class="input-group-text">
                                    {{ user_profile_form.show_telegram }}
                                    <span class="ms-2">Отображать?</span>
                                </div>
                                {{ user_profile_form.telegram_link }}
                            </div>

                            <br>
                            {{ user_profile_form.user_avatar.label }}
                            {{ user_profile_form.user_avatar }}<br>
                        </div>
                    </div>
                    <div class="row">
                        <div class="col-12 text-center">
                            <button class="btn btn-primary my-btn text-center mt-1" type="submit" name="user_info_form">
                                Отправить
                            </button>
                        </div>
                    </div>
                </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 }}
                    <div class="row">
                        <div class="col-12 text-center">
                            <button class="btn btn-primary my-btn mt-3" type="submit" name="user_password_form">
                                Отправить
                            </button>
                        </div>
                    </div>
                </form>
            </div>
        </div>
    </div>
{% endblock %}

 

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

Тут почти стандартно, отображаем нужные поля профиля с проверкой на наличие данных в них:

<div class="col-xl-4 col-sm-12 border border-start border-0Z">
                <h3 class="mb-3 text-center">Информация:</h3>
                <div class="col-12 text-center">
                    <img src="{{ user_profile.profile.user_avatar.url }}" alt="{{ user_profile }}" width="300"
                         height="300" class="img-fluid"><br>
                    {% for group in user_profile.groups.all %}
                        <span class="badge border mt-3 lead">{{ group.name }}</span>
                    {% endfor %}
                </div>
                <div class="col-12 ms-3 mt-4">
                    <b>Имя пользователя:</b> {{ user_profile }}
                    <hr class="mb-0">
                    {% if user_profile.first_name %}
                        <br>
                        <b>Имя:</b> {{ user_profile.first_name }}
                    {% endif %}
                    {% if user_profile.last_name and user_profile.profile.show_last_name %}
                        <br>
                        <b>Фамилия:</b> {{ user_profile.last_name }}
                    {% endif %}
                    <br>
                    <b>Пол:</b> {{ user_profile.profile.get_gender_display }}
                    {% if user_profile.profile.dob %}
                        <br>
                        <b>Возраст:</b> {{ user_profile.profile.get_age }}
                    {% endif %}
                    <br>
                    <b>На сайте:</b> {{ user_profile.profile.get_on_site }}
                    <hr class="mb-0">
                    {% if user_profile.profile.show_email %}
                        <br>
                        <b>Email:</b> {{ user_profile.email }}
                    {% endif %}
                    {% if user_profile.profile.show_telegram %}
                        <br>
                        <b>Ссылка на Telegram:</b> <a
                            href="{{ user_profile.profile.telegram_link }}">{{ user_profile.profile.get_telegram_username }}</a>
                    {% endif %}
                    {% if user_profile.profile.site_link %}
                        <br>
                        <b>Личный сайт:</b> {{ user_profile.profile.site_link }}
                    {% endif %}
                </div>
                <div class="col-12 text-center mt-5">
                    {% if user_profile  user %}
                        <a class="btn btn-primary my-btn"
                           href="{% url 'user_app:user_profile_settings' username=user_profile.username %}">Настройки
                            профиля</a>
                    {% endif %}
                </div>
            </div>

 

На этом всё. У нас теперь есть расширенный профиль пользователя с необходимыми полями. Вероятно, в будущем он будет изменяться или расширяться. Следующим шагом будет создание своего расширенного пользователя, но этим мы займёмся в одном из следующих постов.

 

Автор

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

    Реклама