Como expressar um relacionamento um-para-muitos no Django

167

Estou definindo meus modelos de Django agora e percebi que não havia nenhum OneToManyFieldtipo de campo no modelo. Tenho certeza de que há uma maneira de fazer isso, então não tenho certeza do que estou perdendo. Eu basicamente tenho algo parecido com isto:

class Dude(models.Model):
    numbers = models.OneToManyField('PhoneNumber')

class PhoneNumber(models.Model):
    number = models.CharField()

Nesse caso, cada um Dudepode ter vários PhoneNumbers, mas o relacionamento deve ser unidirecional, pois não preciso saber de PhoneNumberquem Dudeé o proprietário, por si só, pois posso ter muitos objetos diferentes que possuem PhoneNumberinstâncias, como um Businesspara exemplo:

class Business(models.Model):
    numbers = models.OneToManyField('PhoneNumber')

O que eu substituiria OneToManyField(que não existe) no modelo para representar esse tipo de relacionamento? Eu sou do Hibernate / JPA, onde declarar um relacionamento um para muitos foi tão fácil quanto:

@OneToMany
private List<PhoneNumber> phoneNumbers;

Como posso expressar isso no Django?

Naftuli Kay
fonte

Respostas:

134

Para lidar com relacionamentos um-para-muitos no Django, você precisa usar ForeignKey.

A documentação do ForeignKey é muito abrangente e deve responder a todas as perguntas que você tiver:

https://docs.djangoproject.com/en/dev/ref/models/fields/#foreignkey

A estrutura atual no seu exemplo permite que cada Cara tenha um número e cada número pertença a vários Cara (o mesmo que com Negócios).

Se você deseja o relacionamento inverso, seria necessário adicionar dois campos ForeignKey ao seu modelo PhoneNumber, um para Dude e outro para Business. Isso permitiria que cada número pertencesse a um Cara ou a um Negócio, e permitiria que o Cara e o Negócio possuíssem vários Números. Eu acho que isso pode ser o que você procura.

class Business(models.Model):
    ...
class Dude(models.Model):
    ...
class PhoneNumber(models.Model):
    dude = models.ForeignKey(Dude)
    business = models.ForeignKey(Business)
pedra rolando
fonte
3
Você poderia me dar um exemplo, considerando o problema acima? Provavelmente estou perdendo completamente, mas estou lendo a documentação do Django há algum tempo e ainda não estou claro como criar esse tipo de relacionamento.
Naftuli Kay
4
convém tornar as duas chaves estrangeiras desnecessárias (em branco = Verdadeiro, nulo = Verdadeiro) ou adicionar algum tipo de validação personalizada para garantir que haja pelo menos uma ou outra. e o caso de uma empresa com um número genérico? ou um cara desempregado?
Jsyk
1
@j_syk bom argumento sobre validação personalizada. mas parece meio hacky incluir uma chave estrangeira para negociar e uma chave estrangeira para negociar e depois fazer a validação personalizada (externa à definição do modelo). Parece que tem que haver uma maneira mais limpa, mas também não consigo descobrir.
andy
Nessa situação, é apropriado usar o ContentTypes Framework, que permite relacionamentos genéricos com diferentes objetos. No entanto, o uso de tipos de conteúdo pode se tornar muito complexo muito rapidamente e, se essa for sua única necessidade, essa abordagem mais simples (se hacky) pode ser desejável.
kball
57
Uma coisa relevante a mencionar é o argumento "related_name" para ForeignKey. Portanto, na classe PhoneNumber você teria dude = models.ForeignKey(Dude, related_name='numbers')e poderá usar some_dude_object.numbers.all()todos os números relacionados (se você não especificar um "related_name", o padrão será "number_set").
markshep
40

No Django, um relacionamento um para muitos é chamado ForeignKey. Porém, ele funciona apenas em uma direção; portanto, em vez de ter um numberatributo de classe, Dudevocê precisará

class Dude(models.Model):
    ...

class PhoneNumber(models.Model):
    dude = models.ForeignKey(Dude)

Muitos modelos podem ter um ForeignKeypara outro, portanto, seria válido ter um segundo atributo de PhoneNumbertal forma que

class Business(models.Model):
    ...
class Dude(models.Model):
    ...
class PhoneNumber(models.Model):
    dude = models.ForeignKey(Dude)
    business = models.ForeignKey(Business)

Você pode acessar os PhoneNumbers para um Dudeobjeto dcom d.phonenumber_set.objects.all()e, em seguida, fazer o mesmo para um Businessobjeto.

ainda aprendendo
fonte
1
Eu estava sob a suposição de que ForeignKeysignificava "um para um". Usando o exemplo acima, eu deveria ter um Dudeque tem muitos, PhoneNumberscerto?
Naftuli Kay
6
Eu editei minha resposta para refletir isso. Sim. ForeignKeyé apenas um para um se você especificar ForeignKey(Dude, unique=True), portanto, com o código acima, você obterá um Dudecom vários PhoneNumbers.
#
1
@ roling stone- obrigado, eu estava adicionando isso depois de perceber o meu erro como você comentou. Unique = True não funciona exatamente como o OneToOneField, pretendo explicar que ForeignKey só usa um relacionamento um para um se você especificar Unique = True.
stillLearning
8
+1 por fazê-lo a partir do PhoneNumber. Agora está começando a fazer sentido. ForeignKeyé, essencialmente, muitos-para-um, então você precisa fazer isso para trás para obter um one-to-many :)
Naftuli Kay
2
Alguém pode explicar o significado do nome do campo phonenumber_set? Não o vejo definido em lugar algum. É o nome do modelo, em minúsculas, anexado com "_set"?
James Wierzba
15

Você pode usar uma chave estrangeira em muitos lados da OneToManyrelação (ou seja, ManyToOnerelação) ou usar ManyToMany(em qualquer lado) com restrição exclusiva.

Кирилл Лавров
fonte
8

djangoé inteligente o suficiente. Na verdade, não precisamos definir oneToManycampo. Será gerado automaticamente por djangopara você :-). Só precisamos definir foreignKeyna tabela relacionada. Em outras palavras, precisamos apenas definir a ManyToOnerelação usando foreignKey.

class Car(models.Model):
    // wheels = models.oneToMany() to get wheels of this car [**it is not required to define**].


class Wheel(models.Model):
    car = models.ForeignKey(Car, on_delete=models.CASCADE)  

se queremos obter a lista de rodas de um carro em particular. usaremos o python'sobjeto gerado automaticamente wheel_set. Para carro cvocê usarác.wheel_set.all()

Sheikh Abdul Wahid
fonte
5

Embora a resposta da rolling stone seja boa, direta e funcional, acho que há duas coisas que ela não resolve.

  1. Se o OP quis impor um número de telefone não pode pertencer a um Cara e a um Negócio
  2. O inevitável sentimento de tristeza como resultado da definição do relacionamento no modelo PhoneNumber e não nos modelos Cara / Negócio. Quando terrestres extras chegam à Terra e queremos adicionar um modelo Alien, precisamos modificar o PhoneNumber (assumindo que os ETs tenham números de telefone) em vez de simplesmente adicionar um campo "phone_numbers" ao modelo Alien.

Introduzir a estrutura de tipos de conteúdo , que expõe alguns objetos que nos permitem criar uma "chave estrangeira genérica" ​​no modelo PhoneNumber. Então, podemos definir o relacionamento inverso em Cara e Negócios

from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models

class PhoneNumber(models.Model):
    number = models.CharField()

    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    owner = GenericForeignKey()

class Dude(models.Model):
    numbers = GenericRelation(PhoneNumber)

class Business(models.Model):
    numbers = GenericRelation(PhoneNumber)

Consulte os documentos para obter detalhes e, talvez, confira este artigo para obter um tutorial rápido.

Além disso, aqui está um artigo que argumenta contra o uso de FKs genéricos.

rabeye
fonte
0

Se o modelo "many" não justificar a criação de um modelo em si (não é o caso aqui, mas pode beneficiar outras pessoas), outra alternativa seria confiar em tipos de dados específicos do PostgreSQL, através do pacote Django Contrib

Postgres pode lidar com matriz ou JSON tipos de dados, e isso pode ser uma boa solução para lidar com um-para-muitos, quando os muitos-s só pode ser vinculado a uma única entidade do um .

O Postgres permite acessar elementos únicos da matriz, o que significa que as consultas podem ser muito rápidas e evitar sobrecargas no nível do aplicativo. E, claro, o Django implementa uma API legal para alavancar esse recurso.

Obviamente, tem a desvantagem de não ser portátil para outros back-end de banco de dados, mas acho que ainda vale a pena mencionar.

Espero que ajude algumas pessoas a procurar idéias.

Edouardtheron
fonte
0

Antes de tudo, fazemos um tour:

01) relação um-para-muitos:

ASSUME:

class Business(models.Model):
    name = models.CharField(max_length=200)
    .........
    .........
    phone_number = models.OneToMany(PhoneNumber) (NB: Django do not support OneToMany relationship)

class Dude(models.Model):
    name = models.CharField(max_length=200)
    .........
    .........
    phone_number = models.OneToMany(PhoneNumber) (NB: Django do not support OneToMany relationship)

class PhoneNumber(models.Model):
    number = models.CharField(max_length=20)
    ........
    ........

Nota: O Django não fornece nenhum relacionamento OneToMany. Portanto, não podemos usar o método superior no Django. Mas precisamos converter em modelo relacional. Então o que nós podemos fazer? Nesta situação, precisamos converter o modelo relacional em modelo relacional reverso.

Aqui:

modelo relacional = OneToMany

Então, modelo relacional reverso = ManyToOne

Nota: O Django suporta o relacionamento ManyToOne e no Django ManyToOne é representado pelo ForeignKey.

02) relacionamento muitos-para-um:

SOLVE:

class Business(models.Model):
    .........
    .........

class Dude(models.Model):
    .........
    .........

class PhoneNumber(models.Model):
    ........
    ........
    business = models.ForeignKey(Business)
    dude = models.ForeignKey(Dude)

NB: PENSE SIMPLESMENTE !!

renascido
fonte