Подтвердить что ты не робот

Свойства работают над полями модели 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 без причины

4b9b3361

Ответ 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).