Свойства работают над полями модели django?
Я думаю, что лучший способ задать этот вопрос с помощью некоторого кода... могу ли я это сделать? (изменить: ОТВЕТ: нет)
class MyModel(models.Model):
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
def get_foo(self):
if self.bar:
return self.bar
else:
return self.foo
def set_foo(self, input):
self.foo = input
foo = property(get_foo, set_foo)
или я должен сделать это следующим образом:
Да, вы должны сделать это вот так:
class MyModel(models.Model):
_foo = models.CharField(max_length = 20, db_column='foo')
bar = models.CharField(max_length = 20)
def get_foo(self):
if self.bar:
return self.bar
else:
return self._foo
def set_foo(self, input):
self._foo = input
foo = property(get_foo, set_foo)
note: вы можете сохранить имя столбца как "foo" в базе данных, передав поле db_column в поле модели. Это очень полезно, когда вы работаете над существующей системой, и вы не хотите выполнять миграцию db без причины
Ответы
Ответ 1
Поле модели уже является свойством, поэтому я бы сказал, что вы должны сделать это вторым способом избежать столкновения имен.
Когда вы определяете foo = property (..), он фактически переопределяет строку foo = models.., поэтому поле больше не будет доступно.
Вам нужно будет использовать другое имя для свойства и поля. Фактически, если вы это сделаете так, как вы это делаете в примере №1, вы получите бесконечный цикл, когда вы попытаетесь получить доступ к этому свойству, поскольку оно теперь пытается вернуть себя.
EDIT: Возможно, вам также следует подумать о том, чтобы не использовать _foo в качестве имени поля, а скорее foo, а затем определить другое имя для вашего свойства, поскольку свойства не могут использоваться в QuerySet, поэтому вам нужно будет использовать фактические имена полей, когда например, вы делаете фильтр.
Ответ 2
Как уже упоминалось, правильной альтернативой реализации собственного класса django.db.models.Field следует использовать аргумент - db_column и пользовательский (или скрытый) атрибут класса. Я просто переписываю код в редакции @Jiaaro, следуя более строгим соглашениям для OOP в python (например, если _foo должен быть фактически скрыт):
class MyModel(models.Model):
__foo = models.CharField(max_length = 20, db_column='foo')
bar = models.CharField(max_length = 20)
@property
def foo(self):
if self.bar:
return self.bar
else:
return self.__foo
@foo.setter
def foo(self, value):
self.__foo = value
__ foo будет разрешен в _MyModel__foo (как видно из dir (..)), таким образом, скрытый (private). Обратите внимание, что эта форма также позволяет использовать @property decorator, что в конечном итоге станет более удобным способом записи читаемого кода.
Опять же, django создаст таблицу * _MyModel с двумя полями foo и bar.
Ответ 3
Предыдущие решения страдают из-за того, что @property вызывает проблемы в admin и .filter(_foo).
Лучшим решением было бы переопределить setattr, за исключением того, что это может вызвать проблемы с инициализацией объекта ORM из БД. Тем не менее, есть трюк, чтобы обойти это, и он универсален.
class MyModel(models.Model):
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
def __setattr__(self, attrname, val):
setter_func = 'setter_' + attrname
if attrname in self.__dict__ and callable(getattr(self, setter_func, None)):
super(MyModel, self).__setattr__(attrname, getattr(self, setter_func)(val))
else:
super(MyModel, self).__setattr__(attrname, val)
def setter_foo(self, val):
return val.upper()
Секрет " attrname in self.__ dict __". Когда модель инициализируется либо новой, либо гидратированной из __ dict __!
Ответ 4
Это зависит от того, является ли ваша property
средством для достижения цели или самоцелью.
Если вы хотите, чтобы этот тип поведения "переопределять" (или "резервировать") при фильтрации наборов запросов (без предварительной оценки), я не думаю, что свойства могут сработать. Насколько я знаю, свойства Python не работают на уровне базы данных, поэтому их нельзя использовать в фильтрах наборов запросов. Обратите внимание, что вы можете использовать _foo
в фильтре (вместо foo
), так как он представляет фактический столбец таблицы, но тогда логика переопределения из вашего get_foo()
не будет применяться.
Однако, если ваш django.db.models.functions
использования позволяет это, класс 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
имеют магический атрибут bar_otherwise_foo
, по которому можно фильтровать, например, queryset.filter(bar_otherwise_foo='what я 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-реализации, например, написание пользовательского поиска или пользовательского (определенного) менеджера.
Пользовательский менеджер легко реализуется следующим образом (см. Пример в документации):
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
для MyModel
автоматически будет иметь аннотацию bar_otherwise_foo
, которую можно использовать, как описано выше.
Обратите внимание, однако, что, например, обновление bar
в экземпляре не будет обновлять аннотацию, потому что это было сделано в наборе запросов. Сначала необходимо будет повторно оценить набор запросов, например, получив обновленный экземпляр из набора запросов.
Возможно, можно использовать комбинацию собственного менеджера с аннотацией и property
Python, чтобы получить лучшее из обоих миров (пример на CodeReview).