Вот слегка измененная, рабочая версия WillemVanOnsem's замечательный ответ :
class TemperatureField(models.DecimalField):
def from_db_value(self, value, expression, connection):
if value is not None:
return Temperature(value)
return None
def to_python(self, value):
if isinstance(value, Temperature):
return value
if value is None:
return value
kelvin = super().to_python(value)
return Temperature(kelvin)
def get_prep_value(self, value):
if isinstance(value, Temperature):
value = value.kelvin
return super().get_prep_value(value)
def get_db_prep_save(self, value, connection):
if isinstance(value, Temperature):
return connection.ops.adapt_decimalfield_value(value.kelvin, self.max_digits, self.decimal_places)
elif isinstance(value, (float, int)):
return connection.ops.adapt_decimalfield_value(Decimal(value), self.max_digits, self.decimal_places)
elif isinstance(value, (Decimal,)):
return connection.ops.adapt_decimalfield_value(Decimal(value), self.max_digits, self.decimal_places)
Test(models.Model):
temp = TemperatureField(max_digits=10, decimal_places=2)
Несколько замечаний:
Для сохранения пользовательских типов полей в вашемDB, вы должны переопределить get_db_prep_value
, чтобы ваша модель знала, как обрабатывать Temperature
объекты, в противном случае ваша модель будет думать, что она работает с Decimal
, что приведет к:
AttributeError: у объекта 'Temperature' нет атрибута 'quantize'
Устранить ошибку с легким исправлением ...
Теперь документы на from_db_value
:
Если присутствует для подкласса поля, from_db_value () будет вызываться при любых обстоятельствах, когда данные загружаются из базы данных, включая вызовы агрегатов и values ().
акцент на при загрузке данных из базы данных !
Это означает, что при вызове t = Test.objects.create(...)
, from_db_value
не будет оцениваться, и соответствующий пользовательский столбец для экземпляра t
будет равен любому значению, которое вы установили в create
оператор!
Например:
>>>t = Test.objects.create(temp=1)
>>>t.temp
1
>>>type(t.temp)
<class 'int'>
>>>t = Test.objects.first()
>>>t.temp
<extra_fields.fields.Temperature object at 0x10e733e50>
>>> type(t.temp)
<class 'extra_fields.fields.Temperature'>
Если вы попытались запустить исходную версию from_db_value
:
def from_db_value(self, value):
kelvin = super().from_db_value(value)
if kelvin is not None:
return Temperature(kelvin)
return None
Вы даже не получите ошибок, покаВы вызываете:
>>>t = Test.objects.get(...)
TypeError: from_db_value() takes 2 positional arguments but 4 were given
AttributeError: 'super' object has no attribute 'from_db_value'
Наконец, заметьте , что from_db_value
не является методом ни в одном из полей базовой модели Django, поэтому вызов super().from_db_value
всегда вызовет ошибку.Вместо этого базовый класс Field
проверит наличие from_db_value
:
def get_db_converters(self, connection):
if hasattr(self, 'from_db_value'):
return [self.from_db_value]
return []