Django при отправке двух форм не может назначить ошибку экземпляра

У меня есть следующая модель/форма/вид, в которой мне удалось отправить в две разные модели следующим образом:

Модели

class Account(models.Model):
    username = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    name = models.CharField(max_length=150)
    actflag = models.CharField(max_length=1, blank=True)
    acttime = models.DateTimeField(blank=True, null=True)
    comments = models.TextField(_('comments'), max_length=500, blank=True)

    def __str__(self):
        return self.name

class ISIN(models.Model):
    code = models.CharField(max_length=12)
    account_name = models.ForeignKey(Account, on_delete=models.SET_NULL, null=True)
    actflag = models.CharField(max_length=1, blank=True)
    acttime = models.DateTimeField(blank=True, null=True)

    def __str__(self):
        return self.code

Формы

from apps.portfolio.models import Account, ISIN

class PortfolioForm(forms.ModelForm):

    class Meta:
        model = Account
        fields = ['name', 'comments']

class IdentifierForm(forms.ModelForm):

    class Meta:
        model = ISIN
        fields = ['code']       

Вид

def portfolios(request):

    if request.user.is_authenticated:

        if request.POST:
            fm = PortfolioForm(request.POST)
            fm2 = IdentifierForm(request.POST)

            if fm.is_valid():
                messages.success(request, 'Portfolio has been created.')

                account = fm.save(commit=False)
                account.username = request.user
                account.acttime = timezone.now()
                account.actflag = 'I'
                account.save()

                isin = fm2.save(commit=False)
                #isin.account_name = account.name
                isin.acttime = timezone.now()
                isin.actflag = 'I'
                isin.save()

                return redirect('portfolios')
        else:
            fm = PortfolioForm()
            fm2 = IdentifierForm()

        context = {"name": request.user, "form": fm, "form2": fm2}

        return render(request, 'portfolios.html', context)

    else:
        return redirect('login')

Однако, вы заметите закомментированную строку в моем представлении: isin.account_name = account.name, когда я разкомментирую эту строку и попытаюсь отправить формы снова, я получу следующую ошибку: Cannot assign "'test'": "ISIN.account_name" должен быть экземпляром "Account".

Я полагаю, что это связано с ForeignKey, но все еще не знаю, как хранить имя вновь созданной учетной записи, которое пользователь представил в модели isin.

Помощь будет очень признательна.

Что это означает, так это то, что вы должны создать экземпляр модели счета, получив имя, чтобы сохранить форму следующим образом:

def portfolios(request):
    if request.user.is_authenticated:
        if request.POST:
            fm = PortfolioForm(request.POST)
            fm2 = IdentifierForm(request.POST)

            if fm.is_valid():
                messages.success(request, 'Portfolio has been created.')

                account = fm.save(commit=False)
                account.username = request.user
                account.acttime = timezone.now()
                account.actflag = 'I'
                account.save()
                # Here is where we get the instance of account
                account = Account.objects.get(name=account.name)
                isin = fm2.save(commit=False)
                isin.account_name = account
                isin.acttime = timezone.now()
                isin.actflag = 'I'
                isin.save()

                return redirect('portfolios')
        else:
            fm = PortfolioForm()
            fm2 = IdentifierForm()

        context = {"name": request.user, "form": fm, "form2": fm2}

        return render(request, 'portfolios.html', context)

    else:
        return redirect('login')

Поле account_name является ForeignKey для Account, но вы присваиваете string. Вы должны присвоить Account.

Изменение:

isin.account_name = account.name

To:

isin.account_name = account

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

Улучшить именование и исправить первоначальную ошибку

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

isin.account_name = account.name

В действительности, у вас есть ForeignKey к модели Account, поэтому вы должны фактически сохранить ссылку на объект счета:

isin.account_name = account

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

Проблема здесь заключается в названии поля, account_name. Если вы позже захотите получить доступ к имени учетной записи, вам придется написать что-то вроде isis.account_name.name. Звучит неправильно, не так ли?

Вы можете решить эту проблему, переименовав ваше поле следующим образом:

class ISIN(models.Model):
    code = models.CharField(max_length=12)
    account = models.ForeignKey(Account, on_delete=models.SET_NULL, null=True)
    actflag = models.CharField(max_length=1, blank=True)
    acttime = models.DateTimeField(blank=True, null=True)

    def __str__(self):
        return self.code

Тогда, по вашему мнению, вы просто isin.account = account, а позже, если бы вы хотели получить доступ к имени, вы бы использовали isin.account.name.


Еще один незначительный момент заключается в том, что в некоторых местах учетная запись называется Account, а в других местах - Portfolio. Это создает иллюзию, что это несвязанные сущности, и делает ваш код более трудным для чтения и сопровождения.

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

Использовать встроенный механизм временных меток

Похоже, что вы используете поле acttime для ручного хранения времени создания счетов и ISIN.

Вы можете использовать свойство Django auto_now_add для автоматического выполнения этого действия, например, так:

class Account(models.Model):
    acttime = models.DateTimeField(auto_now_add=True)

Если вы также хотите хранить информацию о последнем обновлении аккаунта, вы можете использовать auto_now (здесь также переименованы поля для ясности):

class Account(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

И чтобы оставаться DRY, вы можете сделать миксин для этого и использовать его в Account и ISIN:

class TimeStampMixin(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

class Account(TimeStampMixin, models.Model):
    username = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    name = models.CharField(max_length=150)
    actflag = models.CharField(max_length=1, blank=True)
    comments = models.TextField(_('comments'), max_length=500, blank=True)

    def __str__(self):
        return self.name

class ISIN(TimeStampMixin, models.Model):
    code = models.CharField(max_length=12)
    account = models.ForeignKey(Account, on_delete=models.SET_NULL, null=True)
    actflag = models.CharField(max_length=1, blank=True)

    def __str__(self):
        return self.code

Таким образом, время создания и время последнего обновления автоматически сохраняются в ваших моделях (тех, которые наследуются от TimeStampMixin).

Проверить обе формы

Похоже, что вы проверяете на валидность только одну из форм, а не другую: if fm.is_valid():

Вы, вероятно, должны проверить оба, в случае если ISIN.код недействителен:

if fm.is_valid() and fm2.is_valid():

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