modelos abstratos django versus herança regular

86

Além da sintaxe, qual é a diferença entre usar um modelo abstrato django e usar a herança simples do Python com modelos django? Prós e contras?

ATUALIZAÇÃO: Acho que minha pergunta foi mal interpretada e recebi respostas sobre a diferença entre um modelo abstrato e uma classe que herda de django.db.models.Model. Na verdade, eu quero saber a diferença entre uma classe de modelo que herda de uma classe abstrata django (Meta: abstract = True) e uma classe Python simples que herda digamos, 'objeto' (e não modelos.Modelo).

Aqui está um exemplo:

class User(object):
   first_name = models.CharField(..

   def get_username(self):
       return self.username

class User(models.Model):
   first_name = models.CharField(...

   def get_username(self):
       return self.username

   class Meta:
       abstract = True

class Employee(User):
   title = models.CharField(...
rpq
fonte
1
Esta é uma grande visão geral dos trade-offs entre utilizando os dois herança aproxima charlesleifer.com/blog/django-patterns-model-inheritance
jpotts18

Respostas:

152

Na verdade, eu quero saber a diferença entre uma classe de modelo que herda de uma classe abstrata django (Meta: abstract = True) e uma classe Python simples que herda, digamos, de 'objeto' (e não de modelos.Modelo).

Django irá gerar apenas tabelas para subclasses de models.Model, então o anterior ...

class User(models.Model):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

   class Meta:
       abstract = True

class Employee(User):
   title = models.CharField(max_length=255)

... fará com que uma única tabela seja gerada, nos moldes de ...

CREATE TABLE myapp_employee
(
    id         INT          NOT NULL AUTO_INCREMENT,
    first_name VARCHAR(255) NOT NULL,
    title      VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);

... enquanto o último ...

class User(object):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

class Employee(User):
   title = models.CharField(max_length=255)

... não fará com que nenhuma tabela seja gerada.

Você pode usar herança múltipla para fazer algo assim ...

class User(object):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

class Employee(User, models.Model):
   title = models.CharField(max_length=255)

... que criaria uma tabela, mas irá ignorar os campos definidos na Userclasse, então você acabará com uma tabela como esta ...

CREATE TABLE myapp_employee
(
    id         INT          NOT NULL AUTO_INCREMENT,
    title      VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);
Aya
fonte
3
Obrigado, isso responde minha pergunta. Ainda não está claro por que first_name não foi criado para Employee.
rpq de
4
@rpq Você teria que verificar o código-fonte do Django, mas o IIRC ele usa alguns hackeamentos de metaclasse models.Model, então os campos definidos na superclasse não serão selecionados (a menos que também sejam subclasses de models.Model).
Aya
@rpq Sei que foi 7 anos atrás, quando você pediu, mas em ModelBase's __new__chamada, ele executa uma verificação inicial da classe' atributos, verificando se o valor do atributo tem um contribute_to_classatributo - o Fieldsque você define no Django tudo fazer, então eles estão incluídos em um dicionário especial chamado contributable_attrsque, por fim, é transmitido durante a migração para gerar o DDL.
Yu Chen
1
CADA VEZ QUE OLHO PARA DJANGO Tenho vontade de chorar, por quê ????? por que todo esse absurdo, por que o framework tem que se comportar de uma maneira completamente diferente da linguagem? E o princípio do menor espanto? Este comportamento (como quase tudo no django) não é o que alguém que entende de python poderia esperar.
E.Serra
36

Um modelo abstrato cria uma tabela com todo o conjunto de colunas para cada subchild, enquanto o uso de herança Python "simples" cria um conjunto de tabelas vinculadas (também conhecido como "herança de múltiplas tabelas"). Considere o caso em que você tem dois modelos:

class Vehicle(models.Model):
  num_wheels = models.PositiveIntegerField()


class Car(Vehicle):
  make = models.CharField(…)
  year = models.PositiveIntegerField()

Se Vehiclefor um modelo abstrato, você terá uma única tabela:

app_car:
| id | num_wheels | make | year

No entanto, se você usar a herança simples do Python, terá duas tabelas:

app_vehicle:
| id | num_wheels

app_car:
| id | vehicle_id | make | model

Onde vehicle_idestá um link para uma linha app_vehicleque também teria o número de rodas do carro.

Agora, o Django colocará isso junto na forma de objeto para que você possa acessar num_wheelscomo um atributo Car, mas a representação subjacente no banco de dados será diferente.


Atualizar

Para resolver sua questão atualizada, a diferença entre herdar de uma classe abstrata Django e herdar de Python objecté que a primeira é tratada como um objeto de banco de dados (portanto, as tabelas são sincronizadas com o banco de dados) e tem o comportamento de a Model. Herdar de um Python simples não objectdá à classe (e suas subclasses) nenhuma dessas qualidades.

mipadi
fonte
1
Fazer models.OneToOneField(Vehicle)também seria equivalente a herdar uma classe de modelo, certo? E isso resultaria em duas tabelas separadas, não é?
Rafay
1
@MohammadRafayAleem: Sim, mas ao usar herança, Django fará, por exemplo, num_wheelsum atributo em a car, enquanto com a OneToOneFieldvocê terá que fazer isso desreferenciando você mesmo.
mipadi de
Como posso forçar com a herança regular que todos os campos sejam gerados novamente para a app_cartabela de modo que ela também tenha o num_wheelscampo, em vez de ter o vehicle_idponteiro?
Don Grem
@DorinGrecu: Faça da classe base um modelo abstrato e herde disso.
mipadi de
@mipadi o problema é que não consigo fazer a classe base abstrata, ela está sendo usada em todo o projeto.
Don Grem de
13

A principal diferença é como as tabelas dos bancos de dados para os modelos são criadas. Se você usar herança sem o abstract = TrueDjango, criará uma tabela separada para o modelo pai e filho, que contém os campos definidos em cada modelo.

Se você usar abstract = Truepara a classe base, o Django criará apenas uma tabela para as classes que herdam da classe base - não importa se os campos são definidos na classe base ou na classe herdada.

Prós e contras dependem da arquitetura do seu aplicativo. Dados os seguintes modelos de exemplo:

class Publishable(models.Model):
    title = models.CharField(...)
    date = models.DateField(....)

    class Meta:
        # abstract = True

class BlogEntry(Publishable):
    text = models.TextField()


class Image(Publishable):
    image = models.ImageField(...)

Se a Publishableclasse não é abstrato Django irá criar uma tabela para publishables com as colunas titlee datee mesas separadas para BlogEntrye Image. A vantagem dessa solução seria que você pode consultar todos os publicáveis ​​para campos definidos no modelo base, não importa se eles são entradas de blog ou imagens. Mas, portanto, o Django terá que fazer joins se você, por exemplo, fizer consultas por imagens ... Se fizer Publishable abstract = TrueDjango, não criará uma tabela para Publishable, mas apenas para entradas de blog e imagens, contendo todos os campos (também os herdados). Isso seria útil porque nenhuma junção seria necessária para uma operação como get.

Veja também a documentação do Django sobre herança de modelos .

Bernhard Vallant
fonte
9

Só queria acrescentar algo que não vi em outras respostas.

Ao contrário das classes Python, a ocultação do nome do campo não é permitida com a herança do modelo.

Por exemplo, experimentei problemas com um caso de uso como segue:

Eu tinha um modelo herdado do auth do django PermissionMixin :

class PermissionsMixin(models.Model):
    """
    A mixin class that adds the fields and methods necessary to support
    Django's Group and Permission model using the ModelBackend.
    """
    is_superuser = models.BooleanField(_('superuser status'), default=False,
        help_text=_('Designates that this user has all permissions without '
                    'explicitly assigning them.'))
    groups = models.ManyToManyField(Group, verbose_name=_('groups'),
        blank=True, help_text=_('The groups this user belongs to. A user will '
                                'get all permissions granted to each of '
                                'his/her group.'))
    user_permissions = models.ManyToManyField(Permission,
        verbose_name=_('user permissions'), blank=True,
        help_text='Specific permissions for this user.')

    class Meta:
        abstract = True

    # ...

Então eu tinha meu mixin que, entre outras coisas, queria substituir o related_namedo groupscampo. Então era mais ou menos assim:

class WithManagedGroupMixin(object):
    groups = models.ManyToManyField(Group, verbose_name=_('groups'),
        related_name="%(app_label)s_%(class)s",
        blank=True, help_text=_('The groups this user belongs to. A user will '
                            'get all permissions granted to each of '
                            'his/her group.'))

Eu estava usando estes 2 mixins da seguinte forma:

class Member(PermissionMixin, WithManagedGroupMixin):
    pass

Sim, esperava que funcionasse, mas não funcionou. Mas o problema era mais sério porque o erro que eu estava recebendo não estava apontando para os modelos, eu não tinha ideia do que estava errado.

Enquanto tentava resolver isso, decidi aleatoriamente mudar meu mixin e convertê-lo em um mixin de modelo abstrato. O erro mudou para este:

django.core.exceptions.FieldError: Local field 'groups' in class 'Member' clashes with field of similar name from base class 'PermissionMixin'

Como você pode ver, esse erro explica o que está acontecendo.

Essa foi uma grande diferença, na minha opinião :)

Adrián
fonte
Eu tenho uma pergunta não relacionada à questão principal aqui, mas como você conseguiu implementar isso (para substituir o related_name do campo de grupos) no final?
kalo
@kalo IIRC Acabei de mudar o nome para algo totalmente diferente. Simplesmente não há como remover ou substituir campos herdados.
Adrián
Sim, eu estava quase pronto para desistir disso, quando encontrei uma maneira de fazer isso usando o método contrib_to_class.
kalo
1

A principal diferença é quando você herda a classe User. Uma versão se comportará como uma classe simples, e a outra se comportará como um modelo Django.

Se você herdar a versão base do "objeto", sua classe Employee será apenas uma classe padrão e first_name não se tornará parte de uma tabela de banco de dados. Você não pode criar um formulário ou usar qualquer outro recurso do Django com ele.

Se você herdar a versão models.Model, sua classe Employee terá todos os métodos de um modelo Django e herdará o campo first_name como um campo de banco de dados que pode ser usado em um formulário.

De acordo com a documentação, um modelo abstrato "fornece uma maneira de fatorar informações comuns no nível do Python, enquanto ainda cria apenas uma tabela de banco de dados por modelo filho no nível do banco de dados."

Brent Washburne
fonte
0

Vou preferir a classe abstrata na maioria dos casos porque ela não cria uma tabela separada e o ORM não precisa criar junções no banco de dados. E usar a classe abstrata é muito simples no Django

class Vehicle(models.Model):
    title = models.CharField(...)
    Name = models.CharField(....)

    class Meta:
         abstract = True

class Car(Vehicle):
    color = models.CharField()

class Bike(Vehicle):
    feul_average = models.IntegerField(...)
Kamran Hasan
fonte