Невозможно соединить две модели Django

Я создаю сайт форума и у меня есть следующие модели:

class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,     related_name='profile', null=True, blank=True)
    avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
    football_club = models.CharField(max_length=100)
    location = models.CharField(max_length=100, blank=True)


class CustomUser(AbstractBaseUser, PermissionsMixin):
    userprofile = models.OneToOneField(Profile, on_delete=models.CASCADE, related_name='user_profile',    null=True, blank=True)
    username = models.CharField(max_length=150, unique=True)
    email = models.EmailField(unique=True, null=False)
    created_at = models.DateTimeField(default=timezone.now)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    post_count = models.PositiveIntegerField(default=0)

   
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = []

    objects = CustomUserManager()

    def __str__(self):
        return self.username

Поля CustomUser вводятся при регистрации, в то время как поля профиля должны опционально редактироваться на странице /profile сайта.

Формы для обеих моделей представлены в виде

{{ form.as_p }} 

внутри соответствующих html-файлов.

Обе формы настроены в файле forms.py:

from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import CustomUser, Profile

class SignUpForm(UserCreationForm):
    class Meta:
        model = CustomUser
        fields = ['username', 'email']


class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['avatar', 'football_club', 'location']

И у меня есть следующая функция/класс в моем файле views.py:

class SignUpView(CreateView):
    form_class = SignUpForm
    template_name = 'accounts/signup.html'
    success_url = reverse_lazy('login'))

@login_required
def profile(request):
    profile = request.user.userprofile
    print("Profile: ", profile)
    if request.method == 'POST':
        form = ProfileForm(request.POST, request.FILES, instance=profile)
        if form.is_valid():
            form.save()
            return redirect('profile')
    else:
        form = ProfileForm(instance=profile)
    return render(request, 'accounts/profile.html', {'form': form})

Но на самом деле эти две модели не связаны друг с другом. Я попробовал сделать следующее в одном из своих html-шаблонов:

<ul>
    <li><h3>{{ post.author.username }}</h3></li>
    <li>Posts: {{ post.author.post_count }}</li>
    <li>Club: {{ post.author.userprofile.football_club }}</li>
</ul>

И у меня правильно отображаются имя пользователя и количество сообщений, но поле "Клуб" остается пустым, хотя футбольный_клуб и местоположение сохраняются в базе данных.

Я попытался отредактировать уже существующие поля football_club и location внутри profile.html, но вместо редактирования он просто создает еще один экземпляр модели в базе данных.

Кроме того, в оболочке python я пробовал следующее:

>>> from accounts.models import Profile, CustomUser
>>> lastprofile = Profile.objects.last()
>>> print(lastprofile.football_club)
Inter
>>> print(lastprofile.location)
Milan
>>> print(lastprofile.user)
None

Странно иметь одновременно CustomUser и Profile. Обычно, если вы хотите настроить пользовательское моделирование, вы либо сами создаете пользовательскую модель со всеми (необязательными и обязательными) данными, либо, если вы не хотите подключать пользовательскую User модель, вы работаете с Profile для хранения дополнительных (часто необязательных) данных, но обычно вы не делаете ни того, ни другого: Это часто является худшим из двух миров, поскольку тогда вам придется реализовать пользовательскую логику для создания и регистрации пользователей, но также придется просматривать ForeignKey или OneToOneField для других данных. Еще хуже то, что эти две модели, похоже, имеют каждую связь друг с другом OneToOneField. Обычно пишут one OneToOneField, и таким образом используют обратную связь.

Я думаю, что в данном конкретном случае лучше придерживаться одной модели, и поэтому перенести поля из Profile в CustomUser, и сделать их blank=True [Django-doc], это сделает поля необязательными:

class CustomUser(AbstractBaseUser, PermissionsMixin):
    username = models.CharField(max_length=150, unique=True)
    email = models.EmailField(unique=True, null=False)
    created_at = models.DateTimeField(auto_now_add=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    post_count = models.PositiveIntegerField(default=0)
    avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
    football_club = models.CharField(max_length=100, null=True, blank=True)
    location = models.CharField(max_length=100, blank=True)

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = []

    objects = CustomUserManager()

    def __str__(self):
        return self.username

Тогда форма упрощается до:

from django.contrib.auth.forms import UserCreationForm

from django import forms


class SignUpForm(UserCreationForm):
    class Meta:
        model = CustomUser
        fields = ['username', 'email', 'avatar', 'football_club', 'location']

а Profile можно просто сделать подмножество полей:

class ProfileForm(forms.ModelForm):
    class Meta:
        model = CustomUser
        fields = ['avatar', 'football_club', 'location']


@login_required
def profile(request):
    profile = request.user
    if request.method == 'POST':
        form = ProfileForm(request.POST, request.FILES, instance=profile)
        if form.is_valid():
            form.save()
            return redirect('profile')
    else:
        form = ProfileForm(instance=profile)
    return render(request, 'accounts/profile.html', {'form': form})

Таким образом, нам не нужно определять значения для OneToOneField, и уж точно не нужно связывать их двунаправленно. Производительность, как правило, также будет выше, так как не требуется дополнительных запросов для получения .location, .avatar и т. д. данных пользователя.


Примечание: В Django DateTimeField [Django-doc] есть параметр auto_now_add=… [Django-doc] для работы с временными метками. Он автоматически присваивает текущее время при создании объекта, и пометит его как нередактируемый (editable=False), так что что оно не будет отображаться в ModelForm по умолчанию.


Примечание: Пожалуйста, не храните агрегаты в модели: определяйте агрегаты по мере необходимости: хранение агрегатов в модели усложняет обновление и синхронизацию данных.

Вернуться на верх