Question

I want to store money amounts as integers in the database. For convenience I added float fields as well:

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

For convenience I overrode the save method in my ModelForm derivate. It is supposed to automatically update some dependent fields based on amount_btc and amount_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']

Now adding a new transaction works as expected with is_bid and the other dependend fields properly set. However, editing an existing entry results in the update of only one field. E.g. neither price_per_btc nor amount_eur_milicent is changed even if amount_eur is (see below):

# 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'])

I tried instance.save(update_fields=None), but it did not have any effect at all. Ideas?

Was it helpful?

Solution

The transaction instance won't get modified this way, You should call form.save() explicitly (cause the form actually contains the changed data)

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

You won't create a new object, just edit the existing one, since the objects primary key is not changed

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top