Django admin interface não Use __ __unicode da subclasse ()

votos
4

(Django 1.x, Python 2.6x)

Eu tenho modelos da ordem de:

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)

Tenho instanciado nenhum animal, porque este é suposto ser uma classe virtual. Tenho instanciado cães e gatos, mas na página de administração para AnimalHome, minhas escolhas para animais são apresentados como objeto Animal (o __ __unicode padrão (), eu acho), em oposição à __unicode__ tenho definido para as duas subclasses. Socorro.


A questão abstrato classe base é um arenque vermelho wrt a esta pergunta, eu acho. Mesmo se o animal não deveria ser abstrato, eu ainda tenho o problema que, por alguma razão, uma vez que o ForeignKey é definido no animal e não uma de suas subclasses, o método da superclasse está sendo chamado em vez da subclasse. Na programação OO quando você chamar objeto.método () que você é suposto a obter a execução do menor-subclasse, e você tem que fazer trabalho extra para obter a execução de qualquer superclasse. Então porque é que ter __unicode__ definida nas subclasses não é suficiente para --- na verdade, o problema pode ser que __unicode__ não está sendo chamado em tudo porque a introspecção na classe Animal revela que não está definido. Então, talvez se eu definir __unicode__ para Animal e tê-lo chamar __unicode__ subclasses eu poderia obter o efeito desejado.


Ok, eu acho que eu entendo os problemas ORM. Ambas as respostas me ajudaram a entender isso, obrigado. Ao experimentar com isso, eu descobri que quando o Django salva um modelo subclasse, ele faz duas coisas: (1) ele cria uma linha para o objeto subclasse na tabela da superclasse, e (2) ele faz o PK na tabela subclasse idêntico ao o PK atribuído na tabela da superclasse. Este PK na tabela de subclasse é chamado superclass_ptr. Com base nesta que eu inventou o seguinte. Eu aprecio feedback.

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!

Parece que Lawrence é mais wrt on-ponto esta questão. Gato e cão terá conjuntos PK disjuntos (e qualquer subclasse de animal terá uma PK idêntico ao registro de sua superclasse), mas infelizmente Django não executa qualquer trabalho nos bastidores a la:. Eu sou um animal Eu sei Os animais têm subclasses cão e gato. Especificamente, eu estou animal número 3, e, além disso Acabei de verificar e há um número Cat 3 também. isso significa que eu sou na verdade número Cat 3 . Mesmo que este parece perfeitamente possível e muito razoável (uma vez que um gato não vai fazer qualquer coisa que um animal não podia fazer-se) usando introspecção do Python. Obrigado a todos.

Publicado 23/11/2008 em 23:44
fonte usuário
Em outras línguas...                            


6 respostas

votos
6

Você quer uma classe base Abstract (que significa "virtual" não significa nada em Python.)

A partir da documentação:

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

    class Meta:
        abstract = True

Editar

"Na programação OO quando você chamar objeto.método () você está supostamente para obter a implementação do menor-subclasse."

Verdade. Mas não é toda a história.

Esta não é uma questão OO. Ou até mesmo uma questão de Python ou Django. Esta é uma questão ORM.

A pergunta é: "O objeto é reconstruído no final da referência FK?" E a resposta é que não há um padrão, resposta óbvia de como lidar com a transformação de valor FK se opor.

Eu tenho uma linha AnimalHomecom um animalsvalor de 42. Trata-se de Animal.objects.get(pk=42). Que subclasse de Animal? Gato? Cachorro? Como é que a camada ORM saber se deve fazer Dog.objects.get(pk=42)ou Cat.objects.get(pk=42)?

"Mas espere", você diz. "Ele deve buscar o objeto animal, não um cão ou objeto Cat." Você pode esperar por isso, mas não é assim que o Django ORM funciona. Cada classe é uma tabela diferente. Gato e cão são - por definição - mesas separadas, com consultas separadas. Você não está usando um armazenamento de objetos. Você está usando ORM para tabelas relacionais.


Editar

Primeiro, sua consulta só funciona se o cão e gato compartilhar um gerador de chave comum, e não tem um conjunto de sobreposição de PK.

Se você tem um cão com PK de 42 e um gato com PK de 42, você tem um problema. E já que você não pode facilmente controlar a geração de chaves, sua solução não pode trabalhar.

Tempo Run Tipo de Identificação é ruim. Não é orientada a objetos em um número de maneiras. Quase qualquer coisa que você pode fazer para evitar RTTI é melhor do que uma seqüência cada vez maior de se-declarações para distinguir subclasses.

No entanto, o modelo que você está tentando construir é - especificamente - um problema patológico para sistemas ORM. Na verdade, tão especificamente patológico que estou quase disposto a apostar que é dever de casa. [Há problemas patológicos para sistemas SQL puros, também. Eles muitas vezes aparecem como lição de casa.]

A questão é que o ORM não pode fazer o que você acha que deveria fazer. Então você tem duas escolhas.

  • Pare de usar Django.
  • Faça algo Django faz diretamente.
  • diretrizes de quebra OO design e resort para coisas frágeis como RTTI, o que torna extremamente difícil para adicionar outra subclasse de animais.

Considere esta maneira de fazer RTTI - que inclui o nome da classe, bem como o 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 )
Respondeu 24/11/2008 em 00:23
fonte usuário

votos
5

ForeignKey (Animal) é apenas isso, uma referência de chave estrangeira a uma linha na tabela de Animal. Não há nada no esquema SQL subjacente que indica que a tabela está sendo usado como uma superclasse, assim que você receber de volta um objeto Animal.

Para contornar este:

Primeiro, você quer a classe base para ser não-abstrato. Isso é necessário para o ForeignKey qualquer maneira, e também garante que o cão e gato terá conjuntos de chave primária disjuntos.

Agora, Django implementa herança usando um OneToOneField. Devido a isto, um exemplo de uma classe de base que tem um exemplo subclasse obtém uma referência a essa instância, chamado apropriadamente. Isto significa que você pode fazer:

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'

Isso também responde a sua pergunta a Ber sobre um unicode () que é dependente de outros atributos da subclasse. Você está realmente chamando o método apropriado na instância subclasse agora.

Agora, isso sugere que, uma vez de Django já olhando para instâncias de subclasse por trás das cenas, o código só poderia percorrer todo o caminho e devolver um gato ou instância do cão em vez de um animal. Você vai ter que assumir essa pergunta com os devs. :)

Respondeu 24/11/2008 em 17:40
fonte usuário

votos
3

Django (e bases de dados relacionais em geral) não funcionam dessa maneira. Mesmo quando se usa um ORM como Django você não trabalhar com hierarquias de classe como este.

Há duas soluções possíveis para o problema:

(1) dar um "nome" attibute o modelo animal, em seguida, adicione as entidades com nomes de [ 'Dog', 'Cat']. Isto irá mostrar os nomes dos animais na caixa de seleção de chave estrangeira.

(2) Se você realmente precisa para ligar a sua chave estrangeira para diferentes modelos (o que realmente não é a maneira usual de usar um RDBMS) você deve ler sobre relações genéricas nos docs sobre o quadro contenttypes.

Meu conselho é (1), no entanto.

Respondeu 24/11/2008 em 00:22
fonte usuário

votos
2

Este é ao longo das linhas do que S.Lott sugeriu, mas sem a if / elif / ..., o que pode tornar-se cada vez mais estranho e difícil de manter como o número de subclasses que você precisa para apoiar cresce.

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())

Algo muito semelhante também pode ser feito com a herança multi-mesa de Django (busca docs do Django para ele). Por exemplo:

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!'        

Eu pessoalmente prefiro a segunda opção, uma vez que as instâncias dos subclasses terá todos os campos definidos na classe pai auto-magicamente, o que permite que o código mais claro e conciso. (Para instace, se a classe animal tinha um campo de 'gênero', em seguida, Cat.objects.filter (gender = 'masculino') iria funcionar).

Respondeu 24/11/2008 em 13:28
fonte usuário

Respondeu 14/11/2013 em 21:41
fonte usuário

votos
1

Em relação relações genéricas, note que consultas normais Django não pode abranger as relações GenerecForeignKey. Usando herança multi-mesa evita esta questão no custo de ser uma solução menos genérico.

De docs:

Devido à forma como GenericForeignKey é implementado, você não pode usar esses campos diretamente com filtros (filter () e excluir (), por exemplo) por meio da API de banco de dados. Eles não são objetos de campo normais. Estes exemplos não vai funcionar:

# This will fail
>>> TaggedItem.objects.filter(content_object=guido)
# This will also fail
>>> TaggedItem.objects.get(content_object=guido)
Respondeu 25/11/2008 em 09:44
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more