Отношение один к одному: связывание модели пользователя с кастомной моделью профиля в Django

Автор оригинала: Aduke Adeoye
  • Перевод
Перевод статьи подготовлен в преддверии старта курса «Web-разработчик на Python».





Пфф… Снова базы данных?



В реляционной базе данных есть три основных отношения:

  • Отношение «один-к-одному»;
  • Отношение «один-ко-многим»;
  • Отношение «многие-ко-многим».

В этой статье мы будем разбираться с первым из них – отношением «один-к-одному».

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

Чтобы решить эту проблему, разработчики создают кастомную модель профиля и соединяют ее с моделью пользователя в Django по умолчанию с помощью отношения «один-к-одному». Так получается, что пользователь гарантированно подключен к одному профилю и наоборот. Кроме того, эта механика позволяет лучше управлять настройкой модели профиля.

Теперь я расскажу вам, как в Django можно сделать такую настройку.

1. Используйте модель пользователя в Django по умолчанию
В том приложении, где вы хотите создать профиль, создайте новый файл forms.py. В forms.py импортируйте следующие модули:

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

Создайте класс, который будет наследоваться от UserCreationForm. Внутри этого класса создайте другой мета-класс, у которого будут две переменные: model и fields. В переменной model будет храниться ваша модель пользователя, а в переменной fields – поля формы, которые будут созданы.

class createUserForm(UserCreationForm):
    class meta:
        model = User
        fields = ['username', 'password1', 'password2']

Код выше создаст форму с полями для имени пользователя, пароля и подтверждения пароля.

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

В файле models.py импортируйте модель пользователя по умолчанию.

from django.contrib.auth.models import User

Дальше нужно создать свою модель профиля, а также создать поле пользователя со связью «один-к-одному» с моделью пользователя по умолчанию в Django.

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, null=True,)
    name = models.CharField(max_length=200, null=True)
    email = models.CharField(max_length=200, null=True)
    address = models.CharField(max_length=200, null=True)

    def __str__(self):
        return self.name

3. Создайте форму для своей модели профиля.

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

from django import forms
from django.utils.translation import ugettext_lazy as _
from .models import Profile

Затем создайте класс, который будет наследоваться от forms.ModelForm. В этом классе создайте другой мета-класс, в котором будут две переменные: model и fields. Переменная model содержит модель профиля, а fields – поля формы, которые будут созданы.

class profileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['name', 'email', 'address']
#The labels attribute is optional. It is used to define the labels of the form fields created   
        labels = {
                "name": _("Name     "),
                "email": _("Email Address"),
                "address": _("Street Address"),
                }

Теперь, когда формы готовы, мы определим логику views.py перед тем, как рендерить его в наши шаблоны.

4. Пропишите логику в views.py.

Для работы с формами, созданными в forms.py нужно импортировать их в наш views.py, и добавить еще несколько модулей, которые пригодятся для создания логики.

from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login,
from django.contrib import messages
from .models import Profile
from .forms import createUserForm, profileForm

Теперь создадим страницу регистрации. Назовем ее просто registerPage. Создадим пустой контекстный словарь и вернем рендер.

def registerPage(request):    
    context = {}
    return render(request, 'app_name/register.html', context)

Присвоим значения из формы переменным, вызвав метод POST. Затем передадим переменные в контекстный словарь.

def registerPage(request):
    if request.method == 'POST':
        form = createUserForm(request.POST)
        profile_form = profileForm(request.POST)

    context = {'form': form, 'profile_form': profile_form}
    return render(request, 'app_name/register.html', context)

Затем мы сделаем валидацию обеих форм и сохраним их после нее.

def registerPage(request):
    if request.method == 'POST':
        form = createUserForm(request.POST)
        profile_form = profileForm(request.POST)

        if form.is_valid() and profile_form.is_valid():
            user = form.save()

            #we don't save the profile_form here because we have to first get the value of profile_form, assign the user to the OneToOneField created in models before we now save the profile_form. 

            profile = profile_form.save(commit=False)
            profile.user = user

            profile.save()

    context = {'form': form, 'profile_form': profile_form}
    return render(request, 'app_name/register.html', context)

Если значения форм валидированы и сохранены, мы выведем сообщение об успешном выполнении операции и перенаправим пользователя на страницу входа в систему.

def registerPage(request):
    if request.method == 'POST':
        form = createUserForm(request.POST)
        profile_form = profileForm(request.POST)

        if form.is_valid() and profile_form.is_valid():
            user = form.save()

            #we don't save the profile_form here because we have to first get the value of profile_form, assign the user to the OneToOneField created in models before we now save the profile_form. 

            profile = profile_form.save(commit=False)
            profile.user = user

            profile.save()

            messages.success(request,  'Your account has been successfully created')

            return redirect('login')

    context = {'form': form, 'profile_form': profile_form}
    return render(request, 'app_name/register.html', context)

5. Рендер шаблона

В файле register.html создадим тег формы с помощью метода POST и action со значением пустой строки. В тег формы поместим csrf_token в формате шаблона django, а затем будем динамически визуализировать формы (форму пользователя и профиля). Также не забудем про кнопку отправки.

<form method="POST" action="">
                {% csrf_token %}
                    <h3>Register Profile</h3>

                    <div class="form-field">
                        {{profile_form.name.label_tag}}
                        {{profile_form.name}}
                    </div>
                    <div class="form-field">
                        {{form.username.errors}}
                        {{form.username.label_tag}}
                        {{form.username}}
                    </div>
                    <div class="form-field">
                        {{profile_form.email.label_tag}}
                        {{profile_form.email}}
                    </div>
                    <div class="form-field">
                        {{profile_form.address.label_tag}}
                        {{profile_form.address}}
                    </div>
                    <div class="form-field">
                        {{form.password1.errors}}
                        {{form.password1.label_tag}}
                        {{form.password1}}
                    </div>
                    <div class="form-field">
                        {{form.password2.errors}}
                        {{form.password2.label_tag}}
                        {{form.password2}}
                    </div>

                <hr>
                <input id="form-button" class="btn btn-success btn-block" type="submit" value="Create Profile">
                <br>
                    {{form.non_field_errors}}
                <p>Already have an account? <a href="{% url 'login' %}">Login</a></p>
            </form>

Поскольку после заполнения формы, ее проверки и сохранения, мы перенаправляем на страницу входа в систему, сообщение об успешности операции будет отображаться на странице входа в систему снизу прямо перед надписью «Don't have an account? Register».

 <form method="POST" action="">
                   ...
                 <hr>
                <input id="form-button" class="btn btn-success btn-block" type="submit" value="Login">
                
                {% for message in messages %}
                <p>{{message}}</p>
                {% endfor %}
                <p>Don't have an account? <a href="{% url 'store:register' %}">Register</a></p>
            </form>

Вот так можно создать модель профиля для вашего сайта, связанную с вашей моделью пользователя отношением «один-к-одному».


OTUS. Онлайн-образование
Цифровые навыки от ведущих экспертов

Похожие публикации

Комментарии 8

    +3
    Например, сайту блога может понадобиться профиль автора, который будет включать в себя фотографию пользователя, контактный адрес, хобби, нишу и т.д. А модель пользователя, поставляемая с Django, не позволяет так сделать.

    Позволяет, настройка AUTH_USER_MODEL и класс AbstractUser как раз для этого. Более того, документация Django крайне рекомендует прописать свой AUTH_USER_MODEL с самого старта проекта, чтобы в будущем потом не страдать. Для большинства стандартных задач этого вполне достаточно, и городить отдельную модель профиля обычно ни к чему (в моей личной практике это так ни разу и не понадобилось)

      0

      Делаю также шесть лет и ни разу не пожалел. Плюсы от подмены через AUTH_USER_MODEL:


      • одна таблица в базе данных со всеми колонками вместо двух (упрощаются выборки, describe);
      • одна кастомная админка вместо двух;
      • возможность переопределить встроенную команду createsuperuser. К примеру, максимально упростить её;
        Только лучше переопределять не AbstractUser, а унаследовать AbstractBaseUser и PermissionMixin. Более гибкое и настраиваемое решение получится.
        0
        Есть вариант, когда у пользователя два «лица» и они могут существовать отдельно. Я сейчас делаю сайт с БД федерации фехтовальных клубов и там есть профиль пользователя сайта и профиль собственно фехтовальщика (бойца) и возможны все три варианта:
        1. Пользователь сайта не фехтует и не имеет профиля как боец (административный персонал, учетки заводятся через админку).
        2. У бойца нет учетной записи пользователя, но профиль бойца заполнен руководителем его клуба.
        3. Пользователь регистрировался обычным образом, при этом заполнение профиля бойца обязательно (чтобы не регистрировались посторонние люди, не состоящие в федерации).

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

      docs.djangoproject.com/en/3.1/topics/auth/customizing

      Это такой авторский перевод или как?
      0
      Одна вещь, которую я почти никогда не вижу в таких статьях и которая в свое время изрядно попила мне кровушки это:
      password = form.cleaned_data['password']
      user.set_password(password)
      user.save()
        0
        Может проще создать свою модель User c родительским классом AbstractUser и прописав ее в сеттингах AUTH_USER_MODEL=«myapp.User»? Таким образом мы избавляемся от лишних джойнов или двойных запросов.
          0
          Последнее время слежу за курсами и учебными статьями. У многих авторов курсов становится модно использовать свой подход, который очень часто отличается от рекомендаций разработчиков в официальной документации.

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

          Особенно в курсах по Django из-за исторического названия файлов в MVC паттерне, когда фактически view.py ближе к контроллеру, если проводить аналогию с другими фреймворками.

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

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

          Самое читаемое