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():