如何用Django自定义字段存储Decimal并返回整数及原始值?
Your approach is on the right track, but the initial code has a few gaps to meet your exact requirements. Let's fix that by building a field that stores raw Decimal values, returns a rounded integer when accessed directly, and exposes the original value via a get_raw() method.
Step-by-Step Solution
First, we'll create a wrapper class to hold both the raw Decimal value and the rounded integer. This wrapper will behave like an integer in most contexts while keeping the raw value accessible:
import decimal class RoundedDecimalWrapper: def __init__(self, raw_value): self.raw_value = raw_value self.rounded_value = int(round(raw_value)) # Make the wrapper act like an integer in common operations def __int__(self): return self.rounded_value def __float__(self): return float(self.rounded_value) def __str__(self): return str(self.rounded_value) def __repr__(self): return f"RoundedDecimalWrapper(raw={self.raw_value}, rounded={self.rounded_value})" # Method to retrieve the original Decimal value def get_raw(self): return self.raw_value
Next, we'll build the custom model field that uses this wrapper. This field inherits from DecimalField to handle database storage correctly, and overrides key methods to manage value conversion:
from django.db import models from django.core.exceptions import ValidationError class MyField(models.DecimalField): def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs): # Pass all parameters to the parent DecimalField correctly super().__init__( verbose_name=verbose_name, name=name, max_digits=max_digits, decimal_places=decimal_places, **kwargs ) def to_python(self, value): # Let the parent class handle converting input to a Decimal first decimal_value = super().to_python(value) if decimal_value is None: return None # Wrap the raw Decimal in our custom class return RoundedDecimalWrapper(decimal_value) def get_prep_value(self, value): # When saving to the database, use the raw Decimal value if isinstance(value, RoundedDecimalWrapper): return value.raw_value # For other cases (e.g., initial assignment), defer to the parent method return super().get_prep_value(value)
How It Works
- Storage: The field uses
DecimalFieldunder the hood, so your raw numeric values are stored as Decimals in the database (preserving full precision). - Accessing the Rounded Value: When you access
mymodel.myfield, you get aRoundedDecimalWrapperinstance. Thanks to the magic methods (__int__,__str__, etc.), it behaves like the rounded integer in most scenarios:obj = MyModel(myfield=12.61) obj.save() print(obj.myfield) # Output: 13 print(obj.myfield + 7) # Output: 20 (implicitly uses __int__) - Retrieving the Raw Value: Call
get_raw()on the field value to get the original Decimal:print(obj.myfield.get_raw()) # Output: Decimal('12.61')
Key Notes
- Validation: The parent
DecimalFieldhandles all standard validation (e.g., ensuring values fit withinmax_digitsanddecimal_places), so you don’t need to reimplement that logic. - Querying: If you need to filter based on the rounded value, use Django's database functions to round the field in your query (since the database stores the raw Decimal):
from django.db.models.functions import Round MyModel.objects.annotate(rounded_myfield=Round('myfield')).filter(rounded_myfield=13) - Wrapper Behavior: The current wrapper covers basic use cases, but you can add more magic methods (like
__add__,__lt__) if you need full integer-like behavior for advanced operations.
内容的提问来源于stack exchange,提问作者abhyuday

