You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Django中正确反序列化django-money的MoneyField数据?

问题描述

我有一个包含djmoney中MoneyField的Model:

class Task(models.Model):
    description = models.CharField(max_length=512)
    user = models.ForeignKey("user.User", on_delete=models.CASCADE)
    price = MoneyField(
        max_digits=8,
        decimal_places=2,
        default_currency="USD",
        default=0)

对应的Serializer:

class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = "__all__"
        read_only_fields = ("user",)

Viewset的POST方法实现:

def create(self, request, *args, **kwargs):
    serializer = self.get_serializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    self.perform_create(serializer)
    headers = self.get_success_headers(serializer.data)
    return Response(serializer.data, status=201, headers=headers)

def perform_create(self, serializer):
    serializer.save(user=self.request.user)

当提交如下POST数据时:

{
    "description": "Test",
    "price": "20.00"
}

抛出错误:'decimal.Decimal' object has no attribute 'amount',错误栈如下:

Traceback (most recent call last):
  File "/home/user/.virtualenvs/my_project/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/home/user/.virtualenvs/my_project/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/user/.virtualenvs/my_project/lib/python3.10/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/user/.virtualenvs/my_project/lib/python3.10/site-packages/rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/user/.virtualenvs/my_project/lib/python3.10/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/home/user/.virtualenvs/my_project/lib/python3.10/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/home/user/.virtualenvs/my_project/lib/python3.10/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/home/user/.virtualenvs/my_project/lib/python3.10/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "/home/user/PycharmProjects/my_project/tackapp/tack/views.py", line 37, in create
    serializer.is_valid(raise_exception=True)
  File "/home/user/.virtualenvs/my_project/lib/python3.10/site-packages/rest_framework/serializers.py", line 227, in is_valid
    self._validated_data = self.run_validation(self.initial_data)
  File "/home/user/.virtualenvs/my_project/lib/python3.10/site-packages/rest_framework/serializers.py", line 426, in run_validation
    value = self.to_internal_value(data)
  File "/home/user/.virtualenvs/my_project/lib/python3.10/site-packages/rest_framework/serializers.py", line 483, in to_internal_value
    validated_value = field.run_validation(primitive_value)
  File "/home/user/.virtualenvs/my_project/lib/python3.10/site-packages/rest_framework/fields.py", line 569, in run_validation
    self.run_validators(value)
  File "/home/user/.virtualenvs/my_project/lib/python3.10/site-packages/rest_framework/fields.py", line 593, in run_validators
    validator(value)
  File "/home/user/.virtualenvs/my_project/lib/python3.10/site-packages/djmoney/models/validators.py", line 30, in __call__
    cleaned = cleaned.amount

目前有一个临时解决方法,但不够优雅:

def create(self, request, *args, **kwargs):
    price = Money(request.data["price"], "USD")
    serializer = self.get_serializer(data=request.data | {"price": price})
    # 后续原有逻辑

需要更简洁优雅的正确解决方案。

解决方案

问题根源是DRF默认的序列化字段无法正确处理djmoney的MoneyField,以下是三种规范的解决方法:

方法1:使用djmoney自带的REST序列化字段

djmoney提供了适配DRF的专用序列化字段,先确保安装了扩展依赖(执行pip install django-money[rest-framework]),然后修改Serializer:

from djmoney.contrib.django_rest_framework import MoneyField

class TaskSerializer(serializers.ModelSerializer):
    price = MoneyField(max_digits=8, decimal_places=2)
    
    class Meta:
        model = Task
        fields = "__all__"
        read_only_fields = ("user",)

该字段会自动处理字符串格式的金额输入,将其转换为Money对象,同时序列化输出时也会保持统一格式,无需手动转换。

方法2:自定义序列化字段

如果不想依赖扩展包,可以自定义DRF字段处理Money对象的转换逻辑:

from rest_framework import serializers
from djmoney.money import Money

class CustomMoneyField(serializers.Field):
    def to_representation(self, value):
        # 序列化时返回金额字符串
        return str(value.amount)
    
    def to_internal_value(self, data):
        # 反序列化时将字符串转为Money对象,使用默认货币USD
        try:
            return Money(data, "USD")
        except ValueError:
            raise serializers.ValidationError("无效的金额格式")

class TaskSerializer(serializers.ModelSerializer):
    price = CustomMoneyField()
    
    class Meta:
        model = Task
        fields = "__all__"
        read_only_fields = ("user",)

方法3:全局配置字段映射

如果项目中有多个MoneyField,可以在DRF全局配置中映射字段类型,避免逐个Serializer修改:
在项目settings.py中添加:

REST_FRAMEWORK = {
    'DEFAULT_FIELD_CLASSES': {
        'djmoney.models.fields.MoneyField': 'djmoney.contrib.django_rest_framework.MoneyField',
    },
}

这样所有Model中的MoneyField都会自动使用对应的DRF序列化字段,无需修改Serializer代码。

内容的提问来源于stack exchange,提问作者ubqm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 22:45:38