2👍
Since django-money
0.7.7 all basic F
objects manipulations are supported.
It works for Django 1.4+.
0👍
Have you tried giving it a float instead of an integer? Try: wallet.balance.amount = F("balance__amount") + float(3)
I am not sure why their code doesn’t automatically try to convert integers to float’s if it that is what it requires.
- [Django]-Add a static html page in Django
- [Django]-Wrong results when using two annotate expressions concurrently
- [Django]-What is the difference between django html_message and message in send mail
- [Django]-How to add multiple many to many data in django shell
0👍
For anyone using a serializer class, a possible source of problem (like for my case) wasn’t due to MoneyField
but rather due to the failure in serializing F()
expressions.
File "/home/nponcian/Documents/GitHub/venv/lib/python3.8/site-packages/djmoney/contrib/django_rest_framework/fields.py", line 56, in to_representation
return super().to_representation(obj)
File "/home/nponcian/Documents/GitHub/venv/lib/python3.8/site-packages/rest_framework/fields.py", line 1148, in to_representation
value = decimal.Decimal(str(value).strip())
decimal.InvalidOperation: [<class 'decimal.ConversionSyntax'>]
You can perform the F increment expression through this:
>>> obj = Wallet.objects.filter(balance_currency="EUR").first()
>>> obj.balance = F('balance') + Money(Decimal("12.24"), "EUR")
>>> obj.save()
So when using e.g. an update
in a serializer class:
class WalletModelSerializer(serializers.ModelSerializer):
class Meta:
model = Wallet
fields = "__all__"
def update(self, instance: Wallet, validated_data: dict) -> Wallet:
# Create a copy to not modify the original validation result
validated_data = deepcopy(validated_data)
if "balance" in validated_data:
validated_data["balance"] += models.F("balance")
# When the update is performed, the result is equivalent to performing:
# instance.balance = models.F("balance") + validated_data["balance"]
# Or more specifically:
# instance.balance = models.F("balance") + djmoney.money.Money("12.34", "USD")
return super().update(instance, validated_data)
But, calling serializer.save()
and then accessing the serialized data via serializer.data
which in turn calls serializer.to_representation()
which in turn calls each of the field.to_representation()
would fail for the field balance
because its value is of type CombinedExpression
e.g. <CombinedExpression: F(balance) + Value(12.34)>
which can’t be serialized. Note that the expression is what we initially set as the value of the balance = F("balance") + Money("12.34", "USD")
. As documented, F() assignments persist after Model.save()
F() objects assigned to model fields persist after saving the model instance and will be applied on each
save()
… This persistence can be avoided by reloading the model object after saving it, for example, by usingrefresh_from_db()
.
>>> # Access the balance after the obj.save()
>>> type(obj.balance)
<class 'django.db.models.expressions.CombinedExpression'>
>>> obj.balance
<CombinedExpression: F(balance) + Value(1.2)>
To solve this, depending on your requirements, you can just refresh it to fetch its latest value. As documented, the refresh would replace the CombinedExpression
value into its actual value, which then could be successfully serialized.
>>> obj.refresh_from_db()
>>> type(obj.balance)
<class 'djmoney.money.Money'>
>>> obj.balance
Money('10.28', 'EUR')
Which is translated to this on a serializer class:
class WalletModelSerializer(serializers.ModelSerializer):
...
def update(self, instance: Wallet, validated_data: dict) -> Wallet:
...
instance = super().update(instance, validated_data)
if any(isinstance(value, models.Expression) for value in validated_data.values()):
instance.refresh_from_db()
return instance
After this change, accessing serializer.data
would be successful.