Интерфейс администратора Django не использует подкласс __unicode __ () - PullRequest
4 голосов
/ 24 ноября 2008

(Django 1.x, Python 2.6.x)

У меня есть модели на мелодию:

class Animal(models.Model):
  pass

class Cat(Animal):
  def __unicode__(self):
    return "This is a cat"

class Dog(Animal):
  def __unicode__(self):
    return "This is a dog"

class AnimalHome(models.Model):
  animal = models.ForeignKey(Animal)

Я не создавал никаких животных, потому что это должен быть виртуальный класс. Я создал экземпляры Cats и Dogs, но на странице администратора для AnimalHome мой выбор для Animals отображается как «Объект Animal» (я полагаю, __unicode __ () по умолчанию), а не __unicode__, который я определил для двух подклассов. Помощь.


Я думаю, что абстрактная проблема базового класса - красная сельдь по этому вопросу. Даже если Animal не должен был быть абстрактным, у меня все еще есть проблема, заключающаяся в том, что, по какой-то причине, поскольку ForeignKey определен в Animal, а не в одном из его подклассов, вместо подкласса вызывается метод суперкласса. В ОО-программировании, когда вы вызываете object.method (), вы должны получить реализацию самого низкого подкласса, и вам нужно проделать дополнительную работу, чтобы получить реализацию любого суперкласса. Так почему же для определения __unicode__ на подклассах недостаточно - на самом деле проблема может заключаться в том, что __unicode__ вообще не вызывается, потому что самоанализ класса Animal показывает, что он не определен. Так что, возможно, если я определю __unicode__ для Animal и вызову его для подклассов __unicode__, я смогу получить желаемый эффект.


Хорошо, я думаю, что понимаю проблемы ORM. Оба эти ответа помогли мне понять это, спасибо. Экспериментируя с этим, я обнаружил, что когда Django сохраняет подклассовую модель, он делает две вещи: (1) создает строку для подклассового объекта в таблице суперкласса и (2) делает PK в таблице подкласса идентичным ПК назначен в таблице суперкласса. Этот PK в таблице подклассов называется superclass_ptr. На основании этого я придумал следующее. Буду признателен за отзыв.

Class Animal(models.Model)
  def __unicode__(self):
    if Dog.objects.filter(pk=self.pk).count() > 0:
      return unicode(Dog.objects.get(pk=self.pk))
    elif Cat.objects.filter(pk=self.pk).count() > 0:
      return unicode(Cat.objects.get(pk=self.pk))
    else:
      return "An Animal!"

Похоже, Лоуренс больше всего интересуется этим вопросом. У Cat и Dog будут непересекающиеся наборы ПК (и у любого подкласса Animal будет PK, идентичный записи его суперкласса), но, к сожалению, Django не выполняет никакой закулисной работы: «Я - Животное. Я знаю У животных есть подклассы Собака и Кошка. В частности, я - Животное № 3, и, кроме того, я только что проверил, и есть также Кошка № 3. Это означает, что я на самом деле Кошка № 3 ". Даже при том, что это кажется вполне возможным и очень разумным (поскольку Кошка не будет делать ничего, что Животное не может сделать сама), используя интроспекцию Python. Спасибо всем.

Ответы [ 6 ]

6 голосов
/ 24 ноября 2008

Вы хотите Абстрактный базовый класс («виртуальный» ничего не значит в Python.)

Из документации:

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True

Редактировать

"В OO-программировании при вызове object.method () вы должны получить реализацию самого низкого подкласса."

True. Но не вся история.

Это не проблема ОО. Или даже проблема с Python или Django. Это проблема ORM.

Вопрос: «Какой объект реконструируется в конце ссылки FK?» И ответ заключается в том, что нет стандартного, очевидного ответа о том, как обрабатывать преобразование значения FK в объект.

У меня есть строка в AnimalHome со значением animals, равным 42. Это относится к Animal.objects.get(pk=42). Какой подкласс Animal? Кошка? Собака? Как слой ORM узнает, должен ли он делать Dog.objects.get(pk=42) или Cat.objects.get(pk=42)?

«Но подожди», - говорите вы. «Он должен получить объект Animal, а не объект Dog или Cat». Вы можете на это надеяться, но это не то, как работает Django ORM. Каждый класс представляет собой отдельную таблицу. Cat и Dog - по определению - отдельные таблицы, с отдельными запросами. Вы не используете хранилище объектов. Вы используете ORM для реляционных таблиц.


Редактировать

Во-первых, ваш запрос работает, только если Dog и Cat используют общий генератор ключей и не имеют перекрывающихся наборов PK.

Если у вас есть собака с PK 42 и кошка с PK 42, у вас есть проблема. А поскольку вы не можете легко управлять генерацией ключей, ваше решение не может работать.

Ошибка идентификации типа во время выполнения. Это не объектно-ориентированное во многих отношениях. Почти все, что вы можете сделать, чтобы избежать RTTI, лучше, чем постоянно расширяющаяся последовательность операторов if для различения подклассов.

Однако модель, которую вы пытаетесь построить, является, в частности, патологической проблемой для систем ORM. На самом деле, настолько патологический, что я почти готов поспорить, что это домашнее задание. [Также существуют патологические проблемы для систем на чистом SQL. Они часто появляются как домашнее задание.]

Проблема в том, что ORM не может делать то, что, как вы думаете, он должен делать. Итак, у вас есть два варианта.

  • Прекратить использование Django.
  • Делай то, что Джанго делает напрямую.
  • Нарушить руководящие принципы проектирования ОО и прибегнуть к таким хрупким вещам, как RTTI, что делает чрезвычайно сложным добавление еще одного подкласса животных.

Рассмотрите этот способ сделать RTTI - он включает в себя как имя класса, так и PK

KIND_CHOICES = (
   ( "DOG", "Dog" ),
   ( "CAT", "Cat" ),
)

class Animal( models.Model ):
    kind = models.CharField( max_length= 1, choices=KIND_CHOICES )
    fk = models.IntegerField()
    def get_kind( self ):
        if kind == "DOG":
            return Dog.objects.get( pk = fk )
        elif kind == "CAT":
            return Cat.objects.get( pk = fk )
5 голосов
/ 24 ноября 2008

ForeignKey (Animal) - это ссылка внешнего ключа на строку в таблице Animal. В базовой схеме SQL нет ничего, что указывало бы на то, что таблица используется в качестве суперкласса, поэтому вы возвращаете объект Animal.

Чтобы обойти это:

Во-первых, вы хотите, чтобы базовый класс был неабстрактным. В любом случае это необходимо для ForeignKey, а также гарантирует, что у Dog и Cat будут разрозненные наборы первичных ключей.

Теперь Django реализует наследование с помощью OneToOneField. Из-за этого экземпляр базового класса, который имеет экземпляр подкласса, получает ссылку на этот экземпляр, названный соответствующим образом. Это означает, что вы можете сделать:

class Animal(models.Model):
    def __unicode__(self):
        if hasattr(self, 'dog'):
            return self.dog.__unicode__()
        elif hasattr(self, 'cat'):
            return self.cat.__unicode__()
        else:
            return 'Animal'

Это также отвечает на ваш вопрос Бер о Unicode (), который зависит от других атрибутов подкласса. Вы фактически вызываете соответствующий метод в экземпляре подкласса.

Теперь это говорит о том, что, поскольку Django уже ищет экземпляры подкласса за кулисами, код может просто пройти весь путь и вернуть экземпляр Cat или Dog вместо Animal. Вы должны будете поднять этот вопрос с разработчиками. :)

3 голосов
/ 24 ноября 2008

Django (и реляционные базы данных в целом) не работают таким образом. Даже при использовании ORM, такого как Django, вы не работаете с иерархиями классов, подобных этой.

Существует два возможных решения вашей проблемы:

(1) дать «имя» для модели «Животное», затем добавить сущности с именами из «[Dog», «Cat»]. Это покажет имена животных в поле выбора внешнего ключа.

(2) Если вам действительно нужно связать ваш внешний ключ с различными моделями (что на самом деле не является обычным способом использования СУБД), вам следует прочитать о Общих отношениях в документы по структуре типов содержимого.

Однако мой совет (1).

2 голосов
/ 24 ноября 2008

Это похоже на то, что предложил С.Лотт, но без if / elif / ..., которое может становиться все более неловким и трудным в обслуживании по мере роста числа подклассов, которые вам нужно поддерживать.

class Cat(models.Model):
    def __unicode__(self):
        return u'A Cat!'

class Dog(models.Model):
    def __unicode__(self):
        return u'A Dog!'        

class Eel(models.Model):
    def __unicode__(self):
        return u'An Eel!'        

ANIMALS = {
    'CAT': {'model': Cat, 'name': 'Cat'},
    'DOG': {'model': Dog, 'name': 'Dog'},
    'EEL': {'model': Eel, 'name': 'Eel'},
}
KIND_CHOICES = tuple((key, ANIMALS[key]['name']) for key in ANIMALS)

class Animal(models.Model):
    kind = models.CharField(max_length=3, choices=KIND_CHOICES)
    fk = models.IntegerField()
    def get_kind(self):
        return ANIMALS[self.kind]['model'].objects.get(pk=self.fk)
    def __unicode__(self):
        return unicode(self.get_kind())

Нечто очень похожее можно сделать и с многостольным наследованием Django (поищите его в документации Django). Например:

ANIMALS = {
    'CAT': {'model_name': 'Cat', 'name': 'Cat'},
    'DOG': {'model_name': 'Dog', 'name': 'Dog'},
    'EEL': {'model_name': 'Eel', 'name': 'Eel'},
}
KIND_CHOICES = tuple((key, ANIMALS[key]['name']) for key in ANIMALS)

class Animal(models.Model):
    kind = models.CharField(max_length=3, choices=KIND_CHOICES)
    def get_kind(self):
        return getattr(self, ANIMALS[self.kind]['model_name'].lower())
    def __unicode__(self):
        return unicode(self.get_kind())

class Cat(Animal):
    def __unicode__(self):
        return u'A Cat!'

class Dog(Animal):
    def __unicode__(self):
        return u'A Dog!'        

class Eel(Animal):
    def __unicode__(self):
        return u'An Eel!'        

Лично я предпочитаю второй вариант, поскольку экземпляры подклассов будут автоматически иметь все поля, определенные в родительском классе, что позволяет получить более четкий и краткий код. (Например, если у класса Animal есть поле «пол», то будет работать Cat.objects.filter (гендер = «MALE»)).

1 голос
/ 15 ноября 2013

Вы можете использовать инфраструктуру контента django

Я сделал пример того, как реализовать ваши модели здесь ->

https://github.com/jmg/django_content_types_example/blob/master/generic_models/models.py

И здесь вы можете увидеть, как использовать orm ->

https://github.com/jmg/django_content_types_example/blob/master/generic_models/tests.py

1 голос
/ 25 ноября 2008

Что касается общих отношений, обратите внимание, что обычные запросы Django не могут охватывать отношения GenerecForeignKey. Использование многотабличного наследования позволяет избежать этой проблемы за счет того, что она является менее универсальным решением.

Из документов:

Из-за способа реализации GenericForeignKey такие поля нельзя напрямую использовать с фильтрами (например, filter () и exclude ()) через API базы данных. Они не являются обычными полевыми объектами. Эти примеры не будут работать:

# This will fail
>>> TaggedItem.objects.filter(content_object=guido)
# This will also fail
>>> TaggedItem.objects.get(content_object=guido)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...