Django ORM是否具有SQLAlchemy的混合属性?

时间:2012-08-31 14:47:56

标签: python database django orm sqlalchemy

SQLAlchemy中,hybrid attribute是应用于ORM映射类的property或方法,

class Interval(Base):
    __tablename__ = 'interval'

    id = Column(Integer, primary_key=True)
    start = Column(Integer, nullable=False)
    end = Column(Integer, nullable=False)

    def __init__(self, start, end):
        self.start = start
        self.end = end

    @hybrid_property
    def length(self):
        return self.end - self.start

    @hybrid_method
    def contains(self,point):
        return (self.start <= point) & (point < self.end)

    @hybrid_method
    def intersects(self, other):
        return self.contains(other.start) | self.contains(other.end)

这允许在类和实例级别执行不同的行为,从而使使用相同代码评估SQL语句变得更加简单,

>>> i1 = Interval(5, 10)
>>> i1.length
5

>>> print Session().query(Interval).filter(Interval.length > 10)
SELECT interval.id AS interval_id, interval.start AS interval_start,
interval."end" AS interval_end
FROM interval
WHERE interval."end" - interval.start > :param_1

现在在Django,如果我在模型上有属性,

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)

    def _get_full_name(self):
        "Returns the person's full name."
        return '%s %s' % (self.first_name, self.last_name)
    full_name = property(_get_full_name)

我可以 执行以下操作understanding

Person.objects.filter(full_name="John Cadengo")

Django中是否有SQLAlchemy的混合属性?如果没有,是否有一个共同的解决方法?

2 个答案:

答案 0 :(得分:2)

你是对的,你不能应用基于python属性的django queryset过滤器,因为过滤器在数据库级别上运行。似乎在Django中没有SQLAlchemy的混合属性。

请查看herehere,可能会帮助您找到解决方法。但是,我认为没有通用的解决方案。

答案 1 :(得分:1)

一种可能的快速解决方法是实现一些描述符,这将通过注释应用表达式。 像这样:

from django.db import models
from django.db.models import functions


class hybrid_property:
    def __init__(self, func):
        self.func = func
        self.name = func.__name__
        self.exp = None

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return self.func(instance)

    def __set__(self, instance, value):
        pass

    def expression(self, exp):
        self.exp = exp
        return self


class HybridManager(models.Manager):
    def get_queryset(self):
        qs = super().get_queryset()
        for name, value in vars(qs.model).items():
            if isinstance(value, hybrid_property) and value.exp is not None:
                qs = qs.annotate(**{name: value.exp(qs.model)})
        return qs


class TestAttribute(models.Model):
    val1 = models.CharField(max_length=256)
    val2 = models.CharField(max_length=256)

    objects = HybridManager()

    @hybrid_property
    def vals(self):
        return f"{self.val1} {self.val2}"

    @vals.expression
    def vals(cls):
        return functions.Concat(models.F("val1"), models.Value(" "), models.F("val2"))


class HybridTests(TestCase):
    def setUp(self) -> None:
        self.test_attr = TestAttribute.objects.create(val1="val1", val2="val2")

    def test_access(self):
        self.assertTrue(TestAttribute.objects.exists())
        self.assertEqual(self.test_attr.vals, f"{self.test_attr.val1} {self.test_attr.val2}")
        self.assertTrue(TestAttribute.objects.filter(vals=f"{self.test_attr.val1} {self.test_attr.val2}").exists())
        self.assertTrue(TestAttribute.objects.filter(vals__iexact=f"{self.test_attr.val1} {self.test_attr.val2}").exists())