覆盖 ModelForm.save 不会更新所有受影响的字段

Posted

技术标签:

【中文标题】覆盖 ModelForm.save 不会更新所有受影响的字段【英文标题】:Overriding ModelForm.save does not update all affected fields 【发布时间】:2014-01-13 19:54:39 【问题描述】:

我想将金额作为整数存储在数据库中。为方便起见,我还添加了浮点字段:

# File: models.py 
class Transaction(models.Model):
    user = models.ForeignKey(User, related_name='transactions')
    date = models.DateTimeField(default=datetime.now)
    # transacted btc-amount in satoshi; positive when I bought btc, negative else
    amount_btc_satoshi = models.IntegerField()
    # for convenience: transacted btc-amout in units of 1 btc
    amount_btc = models.FloatField(null=True)
    # transacted fiat-amount in 1e-5 euros; positive when I sold btc, negative else
    amount_eur_milicent = models.IntegerField()
    # for convenience: transacted fiat-amout in units of 1 eur
    amount_eur = models.FloatField(null=True)
    # True if I bought bitcoins, False if I sold bitcoins
    is_bid = models.BooleanField()
    # effective fiat price per 1 BTC in EUR
    price_per_btc = models.FloatField()

为方便起见,我在我的 ModelForm 派生中覆盖了 save 方法。应该会根据amount_btcamount_eur自动更新一些依赖字段:

# File: forms.py
class TransactionForm(forms.ModelForm):
    def clean(self):
        cleaned_data = super(TransactionForm, self).clean()
        if cleaned_data['amount_btc'] > 0. and cleaned_data['amount_eur'] > 0.:
            raise forms.ValidationError('Either BTC amount or fiat amount must be negative.')
        return cleaned_data

    def save(self, commit=True, *args, **kwargs):
        instance = super(TransactionForm, self).save(commit=False, *args, **kwargs)
        # store input data in integer format
        instance.amount_btc_satoshi = int(round(self.cleaned_data['amount_btc'] * 1e8))
        instance.amount_eur_milicent = int(round(self.cleaned_data['amount_eur'] * 1e5))
        # provide convenient amounts
        instance.amount_btc = instance.amount_btc_satoshi / 1e8
        instance.amount_eur = instance.amount_eur_milicent / 1e5
        instance.is_bid = instance.amount_btc_satoshi > 0
        instance.price_per_btc = -1. * instance.amount_eur / instance.amount_btc
        if commit:
            instance.save()
        return instance

    class Meta:
        model = Transaction
        fields = ['date', 'amount_btc', 'amount_eur']

现在添加一个新的transaction 可以正常工作,is_bid 和其他依赖字段已正确设置。但是,编辑现有条目会导致仅更新一个字段。例如。即使amount_eur 是(见下文),price_per_btcamount_eur_milicent 都不会更改:

# File: views.py
@login_required
def transaction_add(request):
    form = TransactionForm(request.POST)
    if form.is_valid():
        transaction = form.save(commit=False)
        transaction.user = request.user
        transaction.save()
    else:
        messages.error(request, ';'.join(': '.format(key, value) for key, value in form.errors.items()))
    return redirect(request.POST['next'])

@login_required
def transaction_edit(request, id):
    transaction = Transaction.objects.get(id=id)
    form = TransactionForm(request.POST, instance=transaction)
    if form.is_valid():
        transaction.save()
    else:
        messages.error(request, ';'.join(': '.format(key, value) for key, value in form.errors.items()))
    return redirect(request.POST['next'])

我试过instance.save(update_fields=None),但是一点效果都没有。想法?

【问题讨论】:

【参考方案1】:

事务实例不会被这样修改,你应该显式调用 form.save() (因为表单实际上包含更改的数据)

transaction = Transaction.objects.get(id=id)
form = TransactionForm(request.POST, instance=transaction)
if form.is_valid():
    form.save()

您不会创建新对象,只需编辑现有对象,因为对象的主键没有更改

【讨论】:

我觉得自己很愚蠢。我猜这是 add 方法的复制和粘贴错误。谢谢!

以上是关于覆盖 ModelForm.save 不会更新所有受影响的字段的主要内容,如果未能解决你的问题,请参考以下文章

Java:我打算只覆盖的库方法的受保护或公共访问?

VS2019 .editorconfig 不受尊重

如何确保在 Java 中进行 ElasticSearch 更新时不会覆盖现有值?

安卓系统手机的软件更新,是直接更新会将旧版的覆盖?还是另外安装一套?

npm WARN deprecated tar@2.2.2: 此版本的 tar 不再受支持,并且不会收到安全更新。请尽快升级

有人可以解释一下啥是受保护的覆盖无效吗? [复制]