Зависит от того, является ли ваш property
средством для достижения цели или самоцелью.
Если вы хотите, чтобы этот тип поведения "переопределения" (или "отката") при фильтрации наборов запросов (без предварительной оценки их), я не думаю, что свойства могут сработать. Насколько мне известно , свойства Python не работают на уровне базы данных, поэтому их нельзя использовать в фильтрах наборов запросов. Обратите внимание, что вы можете использовать _foo
в фильтре (вместо foo
), так как он представляет фактический столбец таблицы, но тогда логика переопределения из get_foo()
не будет применяться.
Однако, если ваш сценарий использования позволяет, класс Coalesce()
из django.db.models.functions
( docs ) может помочь.
Coalesce()
... Принимает список как минимум из двух имен полей или
выражения и возвращает первое ненулевое значение (обратите внимание, что пустое
строка не считается нулевым значением). ...
Это означает, что вы можете указать bar
в качестве переопределения для foo
, используя Coalesce('bar','foo')
. Возвращает bar
, если bar
не равно null
, и в этом случае возвращается foo
. То же самое, что ваш get_foo()
(за исключением того, что он не работает для пустых строк), но на уровне базы данных.
Остается вопрос, как это реализовать.
Если вы не используете его во многих местах, просто аннотируя , набор запросов может быть самым простым. Используя ваш пример, без свойства вещи:
class MyModel(models.Model):
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
Затем сделайте ваш запрос следующим образом:
from django.db.models.functions import Coalesce
queryset = MyModel.objects.annotate(bar_otherwise_foo=Coalesce('bar', 'foo'))
Теперь элементы в вашем наборе запросов имеют магический атрибут bar_otherwise_foo
, который можно фильтровать, например, queryset.filter(bar_otherwise_foo='what I want')
, или его можно использовать непосредственно в экземпляре, например, print(queryset.all()[0].bar_otherwise_foo)
Полученный SQL-запрос из queryset.query
показывает, что Coalesce()
действительно работает на уровне базы данных:
SELECT "myapp_mymodel"."id", "myapp_mymodel"."foo", "myapp_mymodel"."bar",
COALESCE("myapp_mymodel"."bar", "myapp_mymodel"."foo") AS "bar_otherwise_foo"
FROM "myapp_mymodel"
Примечание: вы также можете назвать поле вашей модели _foo
, затем foo=Coalesce('bar', '_foo')
и т. Д. Было бы заманчиво использовать foo=Coalesce('bar', 'foo')
, но это подняло бы ValueError: The annotation 'foo' conflicts with a field on the model.
Должно быть несколько способов создания DRY-реализации, например, написание настраиваемого поиска или настраиваемого (ized) Manager .
Пользовательский менеджер легко реализуется следующим образом (см. пример в документации ):
class MyModelManager(models.Manager):
""" standard manager with customized initial queryset """
def get_queryset(self):
return super(MyModelManager, self).get_queryset().annotate(
bar_otherwise_foo=Coalesce('bar', 'foo'))
class MyModel(models.Model):
objects = MyModelManager()
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
Теперь каждый набор запросов для MyModel
автоматически будет иметь аннотацию bar_otherwise_foo
, которую можно использовать, как описано выше.
Обратите внимание, однако, что, например, обновление bar
в экземпляре не приведет к обновлению аннотации, поскольку это было сделано в наборе запросов. Сначала необходимо будет повторно оценить набор запросов, например, получая обновленный экземпляр из набора запросов.
Возможно, комбинация пользовательского менеджера с аннотацией и Python property
может быть использована для получения лучшего из обоих миров ( пример на CodeReview ).