Django如何在给定模型实例的情况下从自定义字段调用方法? [英] Django how to call a method from a custom field given a model instance?

查看:54
本文介绍了Django如何在给定模型实例的情况下从自定义字段调用方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下模型:

class CustomField(models.CharField):
    def foo(self):
        return 'foo'

class Test(models.Model):
    col1 = models.CharField(max_length=45)
    col2 = CustomField(max_length=45)

如果给我 Test 的实例,如何从 CustomField 调用 foo 方法?

How can I call the foo method from CustomField, if I'm given an instance of Test?

例如:

>>> t = Test.objects.create(col1='bar', col2='blah')
>>> t.col2
'blah'
>>> t.col2.foo() # 'str' object has not attribute 'foo'
'foo'

这当然会抛出:

'str'对象没有属性'foo'

'str' object has not attribute 'foo'

因为调用 model_instance.column 返回该列的 value ,而不是 column 的实例.

because calling model_instance.column returns the value of that column, not an instance of column.

但是为什么呢?似乎Django的ORM神奇地将字段类的实例转换为值.我花了数小时来研究源代码,但似乎找不到转换发生的地方.

But why exactly? It seems Django's ORM magically transforms an instance of a field class into a value. I've spent hours digging through source code and can't seem to find where the transformation takes place.

TLDR;

是否可以在给定模型实例的情况下返回字段类的实例?

Is it possible to return an instance of a field class given a model instance?

有什么想法在Django的源代码中发生吗?我假设这发生在 django/db/models/base.py 中,但是该文件超过1800行代码,因此真的很难分辨.

Any idea where this happens in Django's source code? I assume this takes place in django/db/models/base.py, but that file is over 1800 lines of code, so it's really hard to tell.

这是一个为什么有用的实际示例:

Here is a practical example of why this would be useful:

class TempField(models.DecimalField):
    def __init__(self, initial_unit='C', **kwargs):
        self.initial_unit = initial_unit
        self.units = ['F', 'C', 'K']

    def convert(self, unit):
        if self.initial_unit == unit:
            return self.value

        if unit not in self.units:
            raise

        attr = getattr(self, f'_{initial_unit}_to_{unit}', None)
        if attr is None:
            raise

        return attr(unit)

        def _C_to_F(self, unit):
            ...

现在,您可以方便地将此字段转换为所需的单位:

Now you can conveniently convert this field to the desired unit:

class Test(models.Model):
    temperature = TempField(...)

>>>t = Test.objects.create(temperature=100)
>>>t.temperature
100
>>>t.temperature.convert('F')
212

这都是未经测试的伪代码.另外,我可以想到几种具有此功能的方法,而不必担心以这种方式使用自定义字段.所以这个问题实际上是关于了解Django的ORM的工作原理,而不一定是如何解决任何现实问题.

This is all just untested pseudo code. Also, I can think of several ways of having this functionality without the headache of using custom fields in this manner; so this question is really about understanding how Django's ORM works, and not necessarily how to solve any real world problems.

推荐答案

David Wheeler 在计算机科学中有一句名言:"计算机科学中的所有问题都可以通过另一个层次来解决.(除了太多的间接层)".

There is a saying in computer science by David Wheeler that "All problems in computer science can be solved by another level of indirection (except too many layers of indirection)".

因此,我们可以定义一个 Temperature 类来存储温度:

We thus can define a class Temperature for example to store the temperature:

from enum import Enum
from decimal import Decimal
NINE_FIFTHS = Decimal(9)/Decimal(5)

class TemperatureUnit(Enum):
    KELVIN = (1,0, 'K')
    FAHRENHEIT = (NINE_FIFTHS, Decimal('-459.67'), '°F')
    CELSIUS = (1, Decimal('-273.15'), '°C')
    RANKINE = (NINE_FIFTHS, 0, '°R')

class Temperature:

    def __init__(self, kelvin, unit=TemperatureUnit.CELSIUS):
        self.kelvin = Decimal(kelvin)
        self.unit = unit

    @staticmethod
    def from_unit(value, unit=TemperatureUnit.CELSIUS):
        a, b, *__ = unit.value
        return Temperature((value-b)/a, unit)

    @property
    def value(self):
        a, b, *__ = self.unit.value
        return a * self.kelvin + b

    def convert(self, unit):
        return Temperature(self.kelvin, unit)

    def __str__(self):
        return '{} {}'.format(self.value, self.unit.value[2])

例如,我们可以在此处创建温度:

For example we can here create tempratures:

>>> str(Temperature(15, unit=TemperatureUnit.FAHRENHEIT))
'-432.67 °F'
>>> str(Temperature(0, unit=TemperatureUnit.FAHRENHEIT))
'-459.67 °F'
>>> str(Temperature(1, unit=TemperatureUnit.FAHRENHEIT))
'-457.87 °F'
>>> str(Temperature(0, unit=TemperatureUnit.FAHRENHEIT))
'-459.67 °F'
>>> str(Temperature(0, unit=TemperatureUnit.CELSIUS))
'-273.15 °C'

现在,我们可以创建一个Django模型字段来存储和检索 Temperature ,例如将它们保存在数据库端的十进制形式中(例如Kelvin):

Now we can make a Django model field that stores and retrieves Temperatures, by saving these for example in a decimal on the database side, in Kelvin:

class TemperatureField(models.DecimalField):

    def from_db_value(self, value):
        kelvin = super().from_db_value(value)
        if kelvin is not None:
            return Temperature(kelvin)
        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)

以上当然是原始草图.有关详细信息,请参见有关编写自定义模型字段的文档.您可以添加一个表单字段,小部件,用于查询数据库的查找等.因此,您可以为 TemperatureField 定义一个额外的逻辑层.

The above is of course a raw sketch. See the documentation on writing custom model fields for more information. You can add a form field, widget, lookups to query the database, etc. So you can define an extra layer of logic to your TemperatureField.

这篇关于Django如何在给定模型实例的情况下从自定义字段调用方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆