Come esprimere una relazione uno-a-molti in Django


167

Sto definendo i miei modelli Django in questo momento e mi sono reso conto che non c'era un OneToManyFieldtipo di campo nel modello. Sono sicuro che c'è un modo per farlo, quindi non sono sicuro di cosa mi sto perdendo. Ho essenzialmente qualcosa del genere:

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

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

In questo caso, ogni Dudepuò avere più PhoneNumbers, ma il rapporto dovrebbe essere unidirezionale, nel senso che non ho bisogno di sapere dal PhoneNumberquale Dudelo possiede, di per sé, come potrei avere molti oggetti diversi che propri PhoneNumbercasi, come ad esempio una Businessper esempio:

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

Cosa sostituirò OneToManyField(che non esiste) nel modello per rappresentare questo tipo di relazione? Vengo da Hibernate / JPA dove dichiarare una relazione uno-a-molti è stato facile come:

@OneToMany
private List<PhoneNumber> phoneNumbers;

Come posso esprimerlo in Django?

Risposte:


134

Per gestire le relazioni uno-a-molti in Django devi usare ForeignKey.

La documentazione su ForeignKey è molto completa e dovrebbe rispondere a tutte le domande che hai:

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

L'attuale struttura nel tuo esempio consente a ciascun Dude di avere un numero e ciascun numero appartiene a più Dude (lo stesso con Business).

Se si desidera la relazione inversa, è necessario aggiungere due campi ForeignKey al modello PhoneNumber, uno su Dude e uno su Business. Ciò consentirebbe a ciascun numero di appartenere a un Amico o a un Business e avere Dudes e Business in grado di possedere più numeri. Penso che questo potrebbe essere quello che stai cercando.

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

3
Potresti darmi un esempio dato il problema sopra? Probabilmente mi manca completamente, ma sto leggendo la documentazione di Django da qualche tempo e non sono ancora chiaro su come creare questo tipo di relazione.
Naftuli Kay,

4
potrebbe essere necessario rendere entrambi i ForeignKeys non richiesti (vuoto = Vero, null = Vero) o aggiungere una sorta di convalida personalizzata per assicurarsi che ce ne sia almeno uno o l'altro. che dire del caso di un'azienda con un numero generico? o un tizio disoccupato?
j_syk,

1
@j_syk buon punto sulla convalida personalizzata. ma sembra un po 'confuso includere sia una chiave straniera da tatuare che una chiave straniera per le imprese, e quindi eseguire una convalida personalizzata (esterna alla definizione del modello). sembra che ci debba essere un modo più pulito, ma non riesco nemmeno a capirlo.
andy,

In questa situazione è opportuno utilizzare ContentTypes Framework che consente relazioni generiche con oggetti diversi. Tuttavia, l'utilizzo dei tipi di contenuto può diventare molto complesso molto rapidamente e, se questa è la tua unica necessità, questo approccio più semplice (se hacky) potrebbe essere desiderabile.
kball,

57
Una cosa rilevante da menzionare è l'argomento "related_name" di ForeignKey. Quindi nella classe PhoneNumber avresti dude = models.ForeignKey(Dude, related_name='numbers')e quindi puoi usare some_dude_object.numbers.all()per ottenere tutti i numeri correlati (se non specifichi un "related_name", il valore predefinito sarà "number_set").
markhep

40

In Django, una relazione uno-a-molti si chiama ForeignKey. Funziona solo in una direzione, tuttavia, piuttosto che avere un numberattributo di classe Dudeche ti servirà

class Dude(models.Model):
    ...

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

Molti modelli possono avere un ForeignKeyaltro modello, quindi sarebbe valido avere un secondo attributo di PhoneNumbertale

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

Puoi accedere a PhoneNumbers per un Dudeoggetto dcon d.phonenumber_set.objects.all(), quindi fare lo stesso per un Businessoggetto.


1
Pensavo che ForeignKeysignificasse "uno a uno". Usando il tuo esempio sopra, dovrei avere un Dudeche ha molti PhoneNumbersgiusti?
Naftuli Kay,

6
Ho modificato la mia risposta per riflettere questo. Sì. ForeignKeyè solo uno a uno se specifichi ForeignKey(Dude, unique=True), quindi con il codice sopra otterrai una Dudecon più PhoneNumbers.
stillLearning

1
@rolling stone- grazie, lo stavo aggiungendo dopo aver realizzato il mio errore mentre commentavi. Unique = True non funziona esattamente come OneToOneField, intendevo spiegare che ForeignKey utilizza una relazione uno a uno solo se si specifica Unique = True.
stillLearning

8
+1 per averlo fatto dal PhoneNumber. Ora sta iniziando a dare un senso. ForeignKeyè essenzialmente molti-a-uno, quindi devi farlo all'indietro per ottenere uno-a-molti :)
Naftuli Kay,

2
Qualcuno può spiegare il significato del nome del campo phonenumber_set? Non lo vedo definito da nessuna parte. È il nome del modello, in tutte le lettere minuscole, aggiunto con "_set"?
James Wierzba,


15

È possibile utilizzare una chiave esterna su molti lati della OneToManyrelazione (ad es. ManyToOneRelazione) o utilizzare ManyToMany(su qualsiasi lato) con vincolo univoco.


8

djangoè abbastanza intelligente. In realtà non è necessario definire il oneToManycampo. Verrà generato automaticamente da djangoper te :-). Dobbiamo solo definire foreignKeynella tabella correlata. In altre parole, dobbiamo solo definire la ManyToOnerelazione 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 vogliamo ottenere l'elenco delle ruote di un'auto particolare. useremo python'sl'oggetto generato automaticamente wheel_set. Per l'auto cche useretec.wheel_set.all()


5

Mentre la risposta di rolling stone è buona, diretta e funzionale, penso che ci siano due cose che non risolve.

  1. Se OP voleva far rispettare un numero di telefono non può appartenere sia a un Amico che a un Business
  2. L'inevitabile sensazione di tristezza derivante dalla definizione della relazione sul modello PhoneNumber e non sui modelli Dude / Business. Quando altri terrestri arrivano sulla Terra e vogliamo aggiungere un modello Alien, dobbiamo modificare il PhoneNumber (supponendo che gli ET abbiano numeri di telefono) invece di aggiungere semplicemente un campo "phone_numbers" al modello Alien.

Introdurre il framework dei tipi di contenuto , che espone alcuni oggetti che ci consentono di creare una "chiave esterna generica" ​​sul modello PhoneNumber. Quindi, possiamo definire la relazione inversa su Dude and Business

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)

Consulta i documenti per i dettagli e forse consulta questo articolo per un breve tutorial.

Inoltre, ecco un articolo che contesta l'uso di FK generici.


0

Se il modello "molti" non giustifica la creazione di un modello di per sé (non è il caso qui, ma potrebbe andare a beneficio di altre persone), un'altra alternativa sarebbe affidarsi a tipi di dati PostgreSQL specifici, tramite il pacchetto Django Contrib

Postgres può trattare con Array o JSON tipi di dati , e questa può essere una bella soluzione per gestire One-To-Many quando i molti possono essere collegati a un'unica entità di quello .

Postgres ti consente di accedere a singoli elementi dell'array, il che significa che le query possono essere molto veloci ed evitare le spese generali a livello di applicazione. E, naturalmente, Django implementa una fantastica API per sfruttare questa funzionalità.

Ovviamente ha lo svantaggio di non essere portabile sul backend di altri database, ma ho pensato che valesse la pena menzionarlo.

Spero che possa aiutare alcune persone a cercare idee.


0

Prima di tutto facciamo un tour:

01) relazione uno-a-molti:

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

NB: Django non fornisce alcuna relazione OneToMany. Quindi non possiamo usare il metodo superiore in Django. Ma dobbiamo convertirci in modello relazionale. Quindi cosa possiamo fare? In questa situazione dobbiamo convertire il modello relazionale in modello relazionale inverso.

Qui:

modello relazionale = OneToMany

Quindi, modello relazionale inverso = ManyToOne

NB: Django supporta la relazione ManyToOne e in Django ManyToOne è rappresentato da ForeignKey.

02) relazione molti-a-uno:

SOLVE:

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

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

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

NB: PENSI SEMPLICEMENTE !!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.