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

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

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

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

Для создания форм мы будем использовать ModelForm. Она позволяет нам создавать формы, которые взаимодействуют с определенной моделью внутри базы данных.

Добавим новые классы в наш accounts/forms.py:

from .models import Profile


class UpdateUserForm(forms.ModelForm):
    username = forms.CharField(max_length=100,
                               required=True,
                               widget=forms.TextInput())
    email = forms.EmailField(required=True,
                             widget=forms.TextInput())

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


class UpdateProfileForm(forms.ModelForm):
    avatar = forms.ImageField(widget=forms.FileInput())
    bio = forms.CharField(widget=forms.Textarea(attrs={'rows': 5}))

    class Meta:
        model = Profile
        fields = ['avatar', 'bio']

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

UpdateProfileForm взаимодействует с моделью профиля, позволяя пользователям обновлять свой профиль.

Теперь давайте обновим наше представление profile в файле accounts/views.py, чтобы добавить формы, которые мы только что создали:

from .forms import SignUpForm, LoginForm, UpdateUserForm, UpdateProfileForm

@login_required
def profile(request):
    if request.method == 'POST':
        user_form = UpdateUserForm(request.POST, instance=request.user)
        profile_form = UpdateProfileForm(request.POST, request.FILES, instance=request.user.profile)

        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
            messages.success(request, 'Your profile is updated successfully')
            return redirect(to='users-profile')
    else:
        user_form = UpdateUserForm(instance=request.user)
        profile_form = UpdateProfileForm(instance=request.user.profile)

    return render(request, 'registration/profile.html', {'user_form': user_form, 'profile_form': profile_form})

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

Если форма отправлена (запрос является POST), нам нужно передать данные поста в формы. Но для формы профиля вместе с запросом приходят данные файла/изображения. Данные этого файла помещаются в него, request.FILES поэтому нам нужно передать и его.

Затем он заполняет поля формы текущей информацией о вошедшем пользователе, т.е. форма пользователя ожидает экземпляр пользователя, поскольку она работает с моделью User, поэтому мы говорим instance=request.user, а для формы профиля мы передаем экземпляр модели профиля, говоря instance=request.user.profile.

Наконец, давайте обновим шаблон для отображения форм. Поэтому в profile.html изменим код:

{% extends "blog/base.html" %}
{% block title %}Profile Page{% endblock title %}
{% block content %}
    <div>
        <img src="{{ user.profile.avatar.url }} " style="cursor: pointer;"/>
    </div>
    {% if user_form.errors %}
        <div role="alert">
            <div>
                {% for key, value in user_form.errors.items %}
                    <strong>{{ value }}</strong>
                {% endfor %}
            </div>
            <button type="button" data-dismiss="alert" aria-label="Close">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
    {% endif %}
    <div class="form-content">
        <form method="post" enctype="multipart/form-data">
            {% csrf_token %}
            <div>
                <div>
                    <div>
                        <label>Username:</label>
                        {{ user_form.username }}
                        <hr>
                        <label>Email:</label>
                        {{ user_form.email }}
                    </div>
                    <hr>
                    <div><a href="#">Change Password</a>
                        <hr>
                        <label>Change Avatar:</label>
                        {{ profile_form.avatar }}
                    </div>
                    <hr>
                    <label>Bio:</label> {{ profile_form.bio }}
                </div>
            </div>
            <br><br>
            <button type="submit">Save Changes</button>
            <button type="reset">Reset</button>
        </form>
    </div>
{% endblock content %}

 

Создание профиля у существующего администратора или пользователей

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

$ python manage.py shell
from django.contrib.auth.models import User
from accounts.models import Profile
user = User.objects.get(username='<admin_user_name>')
profile = Profile(user=user)
profile.save()

Вместо <admin_user_name> напишите имя пользователя, которого вы создавали в разделе 5.3 данного курса с правами администратора. Для всех новых пользователей, профиль будет создаваться автоматически.

 

Изменение размера изображений в Django

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

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

Почему нам нужно переопределить этот метод? Это потому, что нам нужно настроить поведение сохранения. Откройте models.py и добавьте этот код в класс Profile:

from PIL import Image

# resizing images
def save(self, *args, **kwargs):
    super().save()

    img = Image.open(self.avatar.path)

    if img.height > 100 or img.width > 100:
        new_img = (100, 100)
        img.thumbnail(new_img)
        img.save(self.avatar.path)

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


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

вылетает ошибка 

NoReverseMatch at /accounts/profile/
Reverse for 'users-profile' not found. 'users-profile' is not a valid view function or pattern name.

пофиксил

return redirect(to="users-profile")

заменил на

return redirect(to="accounts:users-profile")

@Максим_Евланов, Пытаюсь понять откуда вылезла эта ошибка, у приложения accounts и в файле urls.py маршрут так написан?

 path('profile/', profile, name='users-profile'),

Здравствуйте! А в представлении profile когда происходит редирект на users-profile не нужно указывать пространство имен accounts? Без этого у меня появляется ошибка NoReverseMatch at /accounts/profile/

@Рустам, в файле accounts/urls.py установлено значение app_name = 'accounts'?

@Дмитрий_Селезнев, да, действительно дело было в этом. Спасибо!

Здесь так же нюанс тут нужно указать <admin_user_name> имя которое мы указали для админа иначе ошибка будет об это нигде не указано
 

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

$ python manage.py shell
from django.contrib.auth.models import User
from accounts.models import Profile
user = User.objects.get(username='<admin_user_name>')
profile = Profile(user=user)
profile.save()

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

@No_Name, Исправил по всем замечаниям.

вот тут не понятно где именно нам надо обновить profile

 

UpdateProfileForm взаимодействует с моделью профиля, позволяя пользователям обновлять свой профиль.

Теперь давайте обновим наш метод profile, чтобы добавить формы, которые мы только что создали:

from .forms import SignUpForm, LoginForm, UpdateUserForm, UpdateProfileForm

@login_required
def profile(request):
    if request.method == 'POST':
        user_form = UpdateUserForm(request.POST, instance=request.user)
        profile_form = UpdateProfileForm(request.POST, request.FILES, instance=request.user.profile)

        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
            messages.success(request, 'Your profile is updated successfully')
            return redirect(to='users-profile')
    else:
        user_form = UpdateUserForm(instance=request.user)
        profile_form = UpdateProfileForm(instance=request.user.profile)

    return render(request, 'registration/profile.html', {'user_form': user_form, 'profile_form': profile_form})

получается размер будет меняться только при изменении фото? я правильно понял? при дефолтном фото, изображение будет в оригинальном размере?

@Ilia_Boiarintsev, Да, все верно. Предполагается что дефолтное фото уже нужного размера.