(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)
Я не создал экземпляров Animals, потому что это должен быть виртуальный класс. У меня есть экземпляры Cats and Dogs, но на странице администратора для AnimalHome мой выбор для животных отображается как «Объект 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 будут непересекающиеся PK-наборы (и у любого подкласса Animal будет PK, идентичный записи его суперкласса), но, к сожалению, Django не выполняет никакой закулисной работы а-ля: «Я - животное. Я знаю. У животных есть подклассы «Собака» и «Кошка». В частности, я «Животное номер 3», и, кроме того, я только что проверил, есть ли еще кошка номер 3. Это означает, что на самом деле я кошка номер 3 ». Несмотря на то, что это кажется вполне возможным и очень разумным (поскольку Кот не будет делать того, что животное не может сделать само), используя самоанализ Python. Спасибо вам всем.






Django (и реляционные базы данных в целом) так не работают. Даже при использовании ORM, такого как Django, вы не работаете с подобными иерархиями классов.
Есть два возможных решения вашей проблемы:
(1) присвойте «имя» модели Animal, затем добавьте объекты с именами из ['Dog', 'Cat']. Это покажет имена животных в поле выбора внешнего ключа.
(2) Если вы действительно нужно связываете свой внешний ключ с разными моделями (что на самом деле не является обычным способом использования СУБД), вам следует прочитать о Родовые отношения в документации по фреймворку contenttypes.
Однако мой совет (1).
Если я воспользуюсь вашим первым предложением, как мне преодолеть тот факт, что Django не использует __unicode __ () подкласса? Скажем, Dog имеет свойство is_german_shepherd, а функция Dog __unicode __ () вернула: «Собака: (немецкая овчарка ?:% s)»% («Да», если is_german_shepherd else «Нет»)
Вам нужен Абстрактный базовый класс («виртуальный» ничего не значит в Python.)
Из документации:
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
Редактировать
«В объектно-ориентированном программировании, когда вы вызываете object.method (), вы должны получить реализацию самого нижнего подкласса».
Правда. Но не вся история.
Это не проблема ОО. Или даже проблема с Python или Django. Это проблема ORM.
Возникает вопрос: «Какой объект реконструируется в конце ссылки ФК?» И ответ в том, что нет стандартного, очевидного ответа о том, как обрабатывать преобразование из значения FK в объект.
У меня есть строка в AnimalHome со значением animals 42. Это относится к Animal.objects.get(pk=42). Какой подкласс животных? Кошка? Собака? Как уровень 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 не может делать то, что, по вашему мнению, должно делать. Итак, у вас есть два варианта.
Рассмотрим этот способ сделать 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 )
но это не решает проблемы. Теперь Django предлагает мне (более полезное) сообщение об ошибке, что абстрактный базовый класс нельзя использовать в качестве внешнего ключа.
Это примерно так. Абстрактные базовые классы нельзя использовать в качестве внешнего ключа.
Буду признателен за ваш вклад в мою правку выше, добавляя новое определение юникода в Animal. Он полагается на то, что Django не рассматривает таблицы суперкласса / подкласса как полностью отдельные; он хранит идентификатор суперкласса в подклассе как указатель.
Это соответствует тому, что предложил С.Лотт, но без 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') работал бы).
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'
Это также отвечает на ваш вопрос Беру о юникод (), который зависит от других атрибутов подкласса. На самом деле вы сейчас вызываете соответствующий метод в экземпляре подкласса.
Теперь это говорит о том, что, поскольку Django уже ищет экземпляры подкласса за кулисами, код может просто пройти весь путь и вернуть экземпляр Cat или Dog вместо Animal. Вам придется обсудить этот вопрос с разработчиками. :)
Что касается общих отношений, обратите внимание, что обычные запросы Django не могут охватывать отношения GenerecForeignKey. Использование наследования нескольких таблиц позволяет избежать этой проблемы за счет менее общего решения.
Из документов:
Due to the way GenericForeignKey is implemented, you cannot use such fields directly with filters (filter() and exclude(), for example) via the database API. They aren't normal field objects. These examples will not work:
# This will fail
>>> TaggedItem.objects.filter(content_object=guido)
# This will also fail
>>> TaggedItem.objects.get(content_object=guido)
Вы можете использовать фреймворк контента 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
Я не думаю, что вы должны таким образом кодировать модель Django. Постарайтесь определить свою модель данных таким образом, чтобы вы могли использовать одну таблицу (т.е. одну модель) для всех ваших животных. Не вкладывайте в свой код описания. Если вы поместите свои объекты в строку базы данных, вы получите что-то вроде хранилища объектов.