свойства работают на полях модели Django? - PullRequest
37 голосов
/ 21 сентября 2009

Я думаю, что лучший способ задать этот вопрос с помощью некоторого кода ... я могу это сделать? ( изменить : ОТВЕТ: нет)

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 в поле модели. Это очень полезно, когда вы работаете в существующей системе и не хотите выполнять миграцию БД без какой-либо причины

Ответы [ 4 ]

20 голосов
/ 21 сентября 2009

Поле модели уже является свойством, поэтому я бы сказал, что вы должны сделать это вторым способом, чтобы избежать конфликта имен.

Когда вы определяете свойство foo = (..), оно на самом деле переопределяет строку foo = models .., так что это поле больше не будет доступно.

Вам нужно будет использовать другое имя для свойства и поля. Фактически, если вы сделаете это так, как в примере # 1, вы получите бесконечный цикл, когда попытаетесь получить доступ к свойству, так как оно теперь пытается вернуть себя.

РЕДАКТИРОВАТЬ: Возможно, вам также следует рассмотреть возможность использования _foo в качестве имени поля, а не foo, а затем определить другое имя для вашего свойства, потому что свойства не могут быть использованы в QuerySet, поэтому вам придется использовать реальные имена полей, когда вы делаете фильтр например.

16 голосов
/ 11 сентября 2012

Как уже упоминалось, правильная альтернатива реализации собственного класса django.db.models.Field. Необходимо использовать аргумент - db_column и пользовательский (или скрытый) атрибут класса. Я просто переписываю код в редакторе @Jiaaro, следуя более строгим соглашениям для ООП в 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 .

5 голосов
/ 24 ноября 2015

Предыдущие решения страдают, потому что @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 в себе .__ dict __ '. Когда модель инициализируется либо из нового, либо из гидратированного из __ dict __ !

0 голосов
/ 08 декабря 2018

Зависит от того, является ли ваш 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 ).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...