Bagaimana cara mengekspresikan hubungan Satu-ke-Banyak di Django


167

Saya mendefinisikan model Django saya sekarang dan saya menyadari bahwa tidak ada OneToManyFielddalam jenis bidang model. Saya yakin ada cara untuk melakukan ini, jadi saya tidak yakin apa yang saya lewatkan. Saya pada dasarnya memiliki sesuatu seperti ini:

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

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

Dalam hal ini, masing Dude- masing dapat memiliki beberapa PhoneNumbers, tetapi hubungannya harus searah, karena saya tidak perlu tahu dari PhoneNumberyang Dudememilikinya, karena saya mungkin memiliki banyak objek berbeda yang memiliki PhoneNumberinstance, seperti Businessuntuk contoh:

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

Apa yang akan saya ganti OneToManyField(yang tidak ada) dengan model untuk mewakili hubungan semacam ini? Saya berasal dari Hibernate / JPA tempat mendeklarasikan hubungan satu-ke-banyak semudah:

@OneToMany
private List<PhoneNumber> phoneNumbers;

Bagaimana saya bisa mengekspresikan ini dalam Django?

Jawaban:


134

Untuk menangani hubungan One-To-Many di Django yang perlu Anda gunakan ForeignKey.

Dokumentasi tentang ForeignKey sangat komprehensif dan harus menjawab semua pertanyaan yang Anda miliki:

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

Struktur saat ini dalam contoh Anda memungkinkan setiap Bung memiliki satu nomor, dan setiap nomor menjadi milik beberapa Dudes (sama dengan Bisnis).

Jika Anda ingin hubungan terbalik, Anda perlu menambahkan dua bidang ForeignKey ke model PhoneNumber Anda, satu ke Dude dan satu ke Bisnis. Ini akan memungkinkan setiap nomor menjadi milik salah satu Dude atau satu Bisnis, dan memiliki Dudes dan Bisnis dapat memiliki beberapa Angka. Saya pikir ini mungkin yang Anda cari.

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

3
Bisakah Anda memberi saya contoh mengingat masalah di atas? Saya mungkin hanya melewatkannya sepenuhnya, tapi saya sudah membaca dokumentasi Django untuk beberapa waktu sekarang dan masih belum jelas bagaimana cara membuat hubungan semacam ini.
Naftuli Kay

4
mungkin ingin membuat kedua ForeignKeys tidak diperlukan (blank = True, null = True), atau menambahkan semacam validasi khusus untuk memastikan bahwa setidaknya ada satu atau yang lain. bagaimana dengan kasus bisnis yang memiliki nomor generik? atau seorang pria pengangguran?
j_syk

1
@j_syk poin bagus tentang validasi khusus. tetapi tampaknya agak aneh untuk memasukkan kedua foreignkey ke dude dan foreignkey ke bisnis, dan kemudian melakukan custom (eksternal ke definisi model) validasi. Sepertinya harus ada cara yang lebih bersih, tapi aku juga tidak bisa menemukannya.
andy

Dalam situasi ini adalah tepat untuk menggunakan Kerangka Kerja ContentTypes yang memungkinkan untuk hubungan umum ke objek yang berbeda. Namun, menggunakan tipe konten bisa menjadi sangat kompleks dengan sangat cepat, dan jika ini adalah satu-satunya kebutuhan Anda, pendekatan yang lebih sederhana ini (jika diretas) mungkin diinginkan.
kball

57
Satu hal yang relevan untuk disebutkan adalah argumen "related_name" untuk ForeignKey. Jadi di kelas PhoneNumber Anda akan memiliki dude = models.ForeignKey(Dude, related_name='numbers')dan kemudian Anda dapat menggunakan some_dude_object.numbers.all()untuk mendapatkan semua nomor terkait (jika Anda tidak menentukan "related_name" itu akan default ke "number_set").
markshep

40

Di Django, hubungan satu-ke-banyak disebut ForeignKey. Namun, ini hanya bekerja dalam satu arah, daripada memiliki numberatribut kelas yang DudeAnda perlukan

class Dude(models.Model):
    ...

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

Banyak model dapat memiliki satu ForeignKeyke model lain, sehingga akan valid untuk memiliki atribut kedua PhoneNumberseperti itu

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

Anda dapat mengakses PhoneNumbers untuk Dudeobjek ddengan d.phonenumber_set.objects.all(), dan kemudian melakukan hal yang sama untuk Businessobjek.


1
Saya berada di bawah asumsi yang ForeignKeyberarti "satu-ke-satu." Menggunakan contoh Anda di atas, saya harus memiliki Dudeyang memiliki banyak PhoneNumbershak?
Naftuli Kay

6
Saya mengedit jawaban saya untuk mencerminkan ini. Iya. ForeignKeyhanya satu-ke-satu jika Anda menentukan ForeignKey(Dude, unique=True), jadi dengan kode di atas Anda akan mendapatkan Dudedengan beberapa PhoneNumbers.
stillLearning

1
@rolling stone- terima kasih, saya menambahkan itu setelah menyadari kesalahan saya ketika Anda berkomentar. Unique = True tidak bekerja persis seperti OneToOneField, saya bermaksud menjelaskan bahwa ForeignKey hanya menggunakan hubungan satu-ke-satu jika Anda menetapkan Unique = True.
stillLearning

8
+1 untuk melakukannya dari PhoneNumber. Sekarang mulai masuk akal. ForeignKeypada dasarnya banyak-ke-satu, jadi Anda harus melakukannya mundur untuk mendapatkan satu-ke-banyak :)
Naftuli Kay

2
Adakah yang bisa menjelaskan pentingnya nama bidang ini phonenumber_set? Saya tidak melihatnya didefinisikan di mana pun. Apakah itu nama model, dalam semua huruf kecil, ditambahkan dengan "_set"?
James Wierzba


15

Anda dapat menggunakan kunci asing di banyak sisi OneToManyhubungan (yaitu ManyToOnehubungan) atau menggunakan ManyToMany(di sisi mana pun) dengan batasan unik.


8

djangocukup pintar. Sebenarnya kita tidak perlu mendefinisikan oneToManybidang. Ini akan secara otomatis dihasilkan oleh djangountuk Anda :-). Kita hanya perlu mendefinisikan foreignKeydalam tabel terkait. Dengan kata lain, kita hanya perlu mendefinisikan ManyToOnerelasi dengan menggunakan 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)  

jika kita ingin mendapatkan daftar roda mobil tertentu. kita akan menggunakan python'sobjek yang dibuat secara otomatis wheel_set. Untuk mobil cAnda akan menggunakanc.wheel_set.all()


5

Sementara jawaban batu bergulir itu baik, langsung dan fungsional, saya pikir ada dua hal yang tidak dipecahkan.

  1. Jika OP ingin memberlakukan nomor telepon tidak boleh milik Bung dan Bisnis
  2. Perasaan sedih yang tak terhindarkan sebagai akibat dari mendefinisikan hubungan pada model PhoneNumber dan bukan pada model Bung / Bisnis. Ketika makhluk luar angkasa tambahan datang ke Bumi, dan kami ingin menambahkan model Alien, kami perlu memodifikasi PhoneNumber (dengan asumsi ET memiliki nomor telepon) alih-alih hanya menambahkan bidang "nomor telepon" ke model Alien.

Memperkenalkan kerangka jenis konten , yang memperlihatkan beberapa objek yang memungkinkan kita membuat "kunci asing generik" pada model PhoneNumber. Kemudian, kita dapat mendefinisikan hubungan terbalik pada Bung dan Bisnis

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)

Lihat dokumen untuk detailnya, dan mungkin lihat artikel ini untuk tutorial cepat.

Juga, berikut adalah artikel yang menentang penggunaan FK Generik.


0

Jika model "banyak" tidak membenarkan pembuatan model per-se (tidak terjadi di sini, tetapi mungkin menguntungkan orang lain), alternatif lain akan bergantung pada tipe data PostgreSQL tertentu, melalui paket Django Contrib

Postgres dapat menangani tipe data Array atau JSON , dan ini mungkin merupakan solusi yang bagus untuk menangani One-To-Many ketika banyak- hanya dapat diikat ke satu entitas dari satu .

Postgres memungkinkan Anda untuk mengakses elemen tunggal dari array, yang berarti bahwa kueri bisa sangat cepat, dan menghindari overhead tingkat aplikasi. Dan tentu saja, Django mengimplementasikan API keren untuk meningkatkan fitur ini.

Ini jelas memiliki kelemahan karena tidak portabel untuk backend database orang lain, tapi saya pikir itu masih layak disebut.

Semoga ini bisa membantu beberapa orang mencari ide.


0

Pertama-tama, kami melakukan tur:

01) hubungan satu-ke-banyak:

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 tidak menyediakan hubungan OneToMany. Jadi kita tidak bisa menggunakan metode teratas di Django. Tetapi kita perlu mengkonversi dalam model relasional. Jadi apa yang bisa kita lakukan? Dalam situasi ini kita perlu mengubah model relasional menjadi model relasional terbalik.

Sini:

model relasional = OneToMany

Jadi, balikkan model relasional = ManyToOne

NB: Django mendukung hubungan ManyToOne & di Django ManyToOne diwakili oleh ForeignKey.

02) hubungan banyak-ke-satu:

SOLVE:

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

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

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

NB: PIKIR SIMPLY !!

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.