วิธีการแสดงความสัมพันธ์แบบหนึ่งต่อหลายคนใน Django


167

ตอนนี้ฉันกำลังกำหนดนางแบบ Django ของฉันอยู่และฉันก็รู้ว่าไม่มีOneToManyFieldแบบจำลองในฟิลด์ ฉันแน่ใจว่ามีวิธีการทำเช่นนี้ดังนั้นฉันไม่แน่ใจว่าสิ่งที่ฉันหายไป ฉันมีบางอย่างเช่นนี้:

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

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

ในกรณีนี้แต่ละคนDudeสามารถมีหลายPhoneNumbers แต่ความสัมพันธ์ควรจะเป็นทิศทางเดียวในการที่ฉันไม่จำเป็นต้องรู้จากPhoneNumberที่Dudeเป็นเจ้าของมันต่อฉันเพราะฉันอาจมีวัตถุที่แตกต่างกันหลายอย่างPhoneNumberเช่นกรณีBusinessสำหรับ ตัวอย่าง:

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

ฉันจะแทนที่อะไรOneToManyField(ซึ่งไม่มีอยู่) ในโมเดลเพื่อเป็นตัวแทนของความสัมพันธ์แบบนี้ ฉันมาจาก Hibernate / JPA ซึ่งการประกาศความสัมพันธ์แบบหนึ่งต่อหลายคนนั้นง่ายเหมือน:

@OneToMany
private List<PhoneNumber> phoneNumbers;

ฉันจะแสดงสิ่งนี้ใน Django ได้อย่างไร

คำตอบ:


133

ที่จะจัดการกับความสัมพันธ์แบบหนึ่งต่อหลายคนใน Django ForeignKeyคุณจำเป็นต้องใช้

เอกสารเกี่ยวกับ ForeignKey นั้นครอบคลุมมากและควรตอบคำถามทุกข้อที่คุณมี:

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

โครงสร้างปัจจุบันในตัวอย่างของคุณอนุญาตให้ Dude แต่ละคนมีหนึ่งหมายเลขและแต่ละหมายเลขจะเป็นของ Dudes หลายตัว (เช่นเดียวกับธุรกิจ)

หากคุณต้องการความสัมพันธ์แบบย้อนกลับคุณจะต้องเพิ่มเขตข้อมูล ForeignKey สองฟิลด์ให้กับรุ่น PhoneNumber ของคุณหนึ่งใน Dude และอีกหนึ่งในธุรกิจ ซึ่งจะช่วยให้แต่ละหมายเลขเป็นของ Dude หนึ่งธุรกิจหรือหนึ่งธุรกิจและให้ Dudes และธุรกิจสามารถเป็นเจ้าของตัวเลขหลายตัวได้ ฉันคิดว่านี่อาจเป็นสิ่งที่คุณเป็น

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

3
คุณช่วยยกตัวอย่างให้ฉันเมื่อปัญหาข้างต้นได้ไหม ฉันอาจจะหายไปอย่างสมบูรณ์ แต่ฉันได้อ่านเอกสารของ Django มาระยะหนึ่งแล้วและยังไม่ชัดเจนว่าจะสร้างความสัมพันธ์แบบนี้ได้อย่างไร
Naftuli Kay

4
อาจต้องการทำให้ทั้งสอง ForeignKeys ไม่ต้องการ (blank = True, null = True) หรือเพิ่มการตรวจสอบแบบกำหนดเองบางประเภทเพื่อให้แน่ใจว่ามีอย่างน้อยหนึ่งรายการ สิ่งที่เกี่ยวกับกรณีของธุรกิจที่มีหมายเลขทั่วไป? หรือเพื่อนที่ว่างงาน?
j_syk

1
@j_syk จุดที่ดีเกี่ยวกับการตรวจสอบที่กำหนดเอง แต่ดูเหมือนว่าการแฮ็กจะรวมทั้ง foreignkey to dude และ foreignkey ให้กับธุรกิจจากนั้นทำการตรวจสอบความถูกต้องแบบกำหนดเอง ดูเหมือนว่าจะต้องมีวิธีที่สะอาดกว่า แต่ฉันก็ไม่สามารถเข้าใจได้
andy

ในสถานการณ์นี้เหมาะสมที่จะใช้ContentTypes Frameworkซึ่งอนุญาตให้มีความสัมพันธ์ทั่วไปกับวัตถุอื่น อย่างไรก็ตามการใช้ประเภทเนื้อหาอาจซับซ้อนมากอย่างรวดเร็วและหากคุณต้องการเพียงวิธีการที่ง่ายกว่านี้ (หากแฮ็ค) อาจเป็นที่ต้องการ
kball

57
สิ่งหนึ่งที่เกี่ยวข้องพูดถึงคืออาร์กิวเมนต์ "related_name" เพื่อ ForeignKey ดังนั้นในคลาส PhoneNumber ที่คุณมีdude = models.ForeignKey(Dude, related_name='numbers')และคุณสามารถใช้some_dude_object.numbers.all()เพื่อรับหมายเลขที่เกี่ยวข้องทั้งหมด (ถ้าคุณไม่ได้ระบุ "related_name" มันจะใช้ค่าเริ่มต้นเป็น "number_set")
markshep

40

ใน Django ความสัมพันธ์แบบหนึ่งต่อหลายคนเรียกว่า ForeignKey อย่างไรก็ตามใช้ได้ในทิศทางเดียวดังนั้นแทนที่จะมีnumberคุณลักษณะของคลาสที่Dudeคุณต้องการ

class Dude(models.Model):
    ...

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

หลายรุ่นสามารถมีForeignKeyรุ่นอื่นได้หนึ่งรุ่นดังนั้นจึงจะมีคุณสมบัติที่สองPhoneNumberเช่นนั้นได้

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

คุณสามารถเข้าถึงPhoneNumbers สำหรับDudeวัตถุdด้วยd.phonenumber_set.objects.all()แล้วทำเช่นเดียวกันสำหรับBusinessวัตถุ


1
ฉันอยู่ภายใต้สมมติฐานที่ForeignKeyหมายถึง "หนึ่งต่อหนึ่ง" จากตัวอย่างข้างต้นของคุณฉันควรมีสิ่งDudeที่PhoneNumbersใช่ไหม?
Naftuli Kay

6
ฉันแก้ไขคำตอบเพื่อสะท้อนสิ่งนี้ ใช่. ForeignKeyเป็นเพียงหนึ่งต่อหนึ่งถ้าคุณระบุForeignKey(Dude, unique=True)จึงมีโค้ดข้างต้นคุณจะได้รับDudeที่มีหลายPhoneNumbers
คงเรียนรู้

1
@ โรลลิ่งสโตน - ขอบคุณฉันเพิ่มว่าหลังจากที่ตระหนักถึงความผิดพลาดของฉันตามที่คุณแสดงความคิดเห็น Unique = True ไม่ทำงานเหมือนกับ OneToOneField ฉันหมายถึงอธิบายว่า ForeignKey ใช้ความสัมพันธ์แบบหนึ่งต่อหนึ่งเท่านั้นหากคุณระบุ Unique = True
คงเรียนรู้

8
+1 PhoneNumberสำหรับทำมันจาก ตอนนี้มันเริ่มที่จะทำให้รู้สึก ForeignKeyเป็นหลักหลายต่อหนึ่งดังนั้นคุณต้องทำมันไปข้างหลังจะได้รับอย่างใดอย่างหนึ่งต่อหลาย :)
Naftuli เคย์

2
ใครสามารถอธิบายความสำคัญของชื่อของฟิลด์ได้phonenumber_setหรือไม่? ฉันไม่เห็นมันกำหนดไว้ทุกที่ มันเป็นชื่อของรุ่นในกรณีที่ต่ำกว่าทั้งหมดผนวกด้วย "_set"?
James Wierzba

20

เพื่อให้ชัดเจนยิ่งขึ้น - ไม่มี OneToMany ใน Django เพียง ManyToOne - ซึ่งเป็น Foreignkey ที่อธิบายข้างต้น คุณสามารถอธิบายความสัมพันธ์ OneToMany โดยใช้ Foreignkey ได้ แต่นั่นไม่ได้อธิบายอย่างชัดเจน

บทความที่ดีเกี่ยวกับมัน: https://amir.rachum.com/blog/2013/06/15/a-case-for-a-onetomany-relationship-in-django/


คุณสามารถใช้ ManyToManyField
user5510975

15

คุณสามารถใช้ foreign key ในหลาย ๆ ด้านของOneToManyความสัมพันธ์ (เช่นManyToOneความสัมพันธ์) หรือใช้ManyToMany(ด้านใดด้านหนึ่ง) ด้วยข้อ จำกัด ที่ไม่ซ้ำกัน


8

djangoฉลาดพอ ที่จริงแล้วเราไม่จำเป็นต้องกำหนดoneToManyเขตข้อมูล มันจะถูกสร้างขึ้นโดยอัตโนมัติdjangoสำหรับคุณ :-) เราจำเป็นต้องกำหนดforeignKeyในตารางที่เกี่ยวข้องเท่านั้น ในคำอื่น ๆ เราจะต้องกำหนดความสัมพันธ์โดยใช้ManyToOneforeignKey

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)  

ถ้าเราต้องการได้รับรายชื่อล้อรถโดยเฉพาะ เราจะใช้วัตถุที่สร้างขึ้นอัตโนมัติpython's wheel_setสำหรับรถยนต์cคุณจะใช้c.wheel_set.all()


5

ในขณะที่คำตอบของหินกลิ้งนั้นดีตรงไปตรงมาและใช้งานได้ฉันคิดว่ามีสองสิ่งที่ไม่สามารถแก้ไขได้

  1. หาก OP ต้องการบังคับใช้หมายเลขโทรศัพท์ต้องไม่เป็นของทั้ง Dude และ Business
  2. ความรู้สึกที่หลบหนีไม่พ้นของความเศร้าเป็นผลมาจากการกำหนดความสัมพันธ์ในรูปแบบ PhoneNumber และไม่ได้อยู่ในรูปแบบ Dude / Business เมื่อโลกพิเศษเข้ามาในโลกและเราต้องการเพิ่มแบบจำลองเอเลี่ยนเราต้องแก้ไข PhoneNumber (สมมติว่า ET มีหมายเลขโทรศัพท์) แทนที่จะเพิ่มฟิลด์ "phone_numbers" ลงในโมเดลเอเลี่ยน

แนะนำเฟรมเวิร์กชนิดเนื้อหาซึ่งจะเผยออบเจกต์บางอย่างที่ทำให้เราสามารถสร้าง "คีย์ต่างประเทศทั่วไป" ในรุ่น PhoneNumber จากนั้นเราสามารถกำหนดความสัมพันธ์ย้อนกลับใน Dude และธุรกิจ

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)

ดูเอกสารสำหรับรายละเอียดและอาจตรวจสอบบทความนี้สำหรับบทช่วยสอนด่วน

นอกจากนี้ที่นี่เป็นบทความที่ระบุกับการใช้งานของทั่วไป FKs


0

หากโมเดล "หลายคน" ไม่ได้แสดงให้เห็นถึงการสร้างแบบจำลองต่อเนื่อง (ไม่ใช่กรณีที่นี่ แต่มันอาจเป็นประโยชน์ต่อคนอื่น) ทางเลือกอื่นจะขึ้นอยู่กับชนิดข้อมูล PostgreSQL ที่เฉพาะเจาะจงผ่านแพ็คเกจ Django Contrib

Postgres สามารถจัดการกับ อาร์เรย์หรือJSONข้อมูลประเภทและนี่อาจจะเป็นวิธีแก้ปัญหาที่ดีในการจัดการแบบหนึ่งต่อหลายคนเมื่อโอบอุ้มหลายเท่านั้นที่สามารถจะเชื่อมโยงกับองค์กรเดียวของหนึ่ง

Postgres อนุญาตให้คุณเข้าถึงองค์ประกอบเดียวของอาร์เรย์ซึ่งหมายความว่าการสืบค้นสามารถทำได้อย่างรวดเร็วและหลีกเลี่ยงค่าโสหุ้ยในระดับแอปพลิเคชัน และแน่นอนว่าDjango ได้ใช้ API ที่ยอดเยี่ยมเพื่อใช้ประโยชน์จากฟีเจอร์นี้

เห็นได้ชัดว่ามันมีข้อเสียที่ไม่สามารถพกพาไปที่แบ็กเอนด์ฐานข้อมูลอื่น ๆ ได้ แต่ฉันคิดว่ามันก็คุ้มค่าที่จะกล่าวถึง

หวังว่ามันอาจช่วยให้บางคนมองหาแนวคิด


0

ก่อนอื่นเราทัวร์กัน:

01) ความสัมพันธ์แบบหนึ่งต่อหลายคน:

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

หมายเหตุ: Django ไม่ได้ให้ความสัมพันธ์ OneToMany ใด ๆ ดังนั้นเราจึงไม่สามารถใช้วิธีส่วนบนใน Django ได้ แต่เราต้องแปลงเป็นโมเดลเชิงสัมพันธ์ แล้วเราจะทำอย่างไร ในสถานการณ์นี้เราต้องแปลงโมเดลเชิงสัมพันธ์เป็นโมเดลเชิงสัมพันธ์ย้อนกลับ

ที่นี่:

โมเดลเชิงสัมพันธ์ = OneToMany

ดังนั้นแบบจำลองเชิงสัมพันธ์ย้อนกลับ = ManyToOne

หมายเหตุ: Django สนับสนุนความสัมพันธ์ ManyToOne & ใน Django ManyToOne จะแสดงโดย ForeignKey

02) ความสัมพันธ์แบบตัวต่อตัว:

SOLVE:

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

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

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

NB: คิดง่าย ๆ !!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.