Як висловити відносини "один для багатьох" у Джанго


167

Я зараз визначаю свої моделі Джанго і зрозумів, що 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(який не існує) у моделі, щоб представити такий тип відносин? Я приїжджаю зі сплячого / JPA, де оголосити стосунки один до багатьох було так просто, як:

@OneToMany
private List<PhoneNumber> phoneNumbers;

Як я можу це висловити в Джанго?

Відповіді:


133

Для вирішення стосунків у Джанго вам потрібно скористатися ForeignKey.

Документація на ForeignKey дуже вичерпна і повинна відповідати на всі питання, які у вас є:

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

Поточна структура у вашому прикладі дозволяє кожному Dude мати одне число, а кожне число належати до кількох чуваків (те саме з Business).

Якщо ви хочете зворотне відношення, вам потрібно буде додати два моделі ForeignKey до вашої моделі PhoneNumber, одне для Dude і одне для Business. Це дозволило б кожному номеру належати або одному хлопцеві, або одному бізнесу, а люди і компанії можуть мати декілька чисел. Я думаю, що це може бути те, що ти шукаєш.

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

3
Чи можете ви надати мені приклад, враховуючи проблему вище? Я, мабуть, просто його повністю пропускаю, але я читав документацію про Джанго вже певний час і досі незрозуміло, як створити подібні відносини.
Naftuli Kay

4
можливо, потрібно зробити так, щоб обидва ForeignKeys не потрібні (blank = True, null = True) або додати якусь спеціальну перевірку, щоб переконатися, що існує принаймні одне чи інше. як щодо випадку бізнесу, який має загальний номер? чи безробітний чувак?
j_syk

1
@j_syk хороша думка щодо спеціальної перевірки. але здається неправдивим включити як іноземну клавішу до хлопця, так і іноземну ключу для бізнесу, а потім зробити власну (зовнішню для визначення моделі) перевірку. Здається, має бути більш чистий спосіб, але я не можу це зрозуміти.
Енді

У цій ситуації доцільно використовувати рамку ContentTypes, яка дозволяє загальні відносини до різних об'єктів. Однак використання типів вмісту може отримати дуже складний дуже швидко, і якщо це ваша єдина потреба, такий простіший (якщо хакітний) підхід може бути бажаним.
kball

57
Одним з важливих моментів є аргумент "related_name" для ForeignKey. Тож у класі PhoneNumber у вас є, dude = models.ForeignKey(Dude, related_name='numbers')а потім ви можете використовувати some_dude_object.numbers.all()для отримання всіх пов'язаних номерів (якщо ви не вказали "імено пов'язаного", воно буде за замовчуванням "число_набору").
Маршеп

40

У Джанго відносини одного до багатьох називаються 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
@ rolling stone - дякую, я додав, що зрозумівши свою помилку, як ви коментували. Unique = True працює не так, як OneToOneField, я мав намір пояснити, що ForeignKey використовує лише співвідношення один на один, якщо вказати Unique = True.
досіНавчання

8
+1 за те, що це зроблено з PhoneNumber. Тепер це починає мати сенс. ForeignKeyпо суті багато-на-один, тому вам потрібно зробити це назад, щоб отримати одного на багато :)
Naftuli Kay

2
Чи може хтось пояснити значення назви поля phonenumber_set? Я ніде не бачу цього визначення. Чи ім'я моделі, в усьому нижньому регістрі, додається з "_set"?
Джеймс Віцерба

20

Щоб бути більш зрозумілим - у Джанго немає OneToMany, є лише ManyToOne - який описаний вище. Ви можете описати відношення OneToMany за допомогою Foreignkey, але це дуже невиразно.

Гарна стаття про це: https://amir.rachum.com/blog/2013/06/15/a-case-for-a-onetomany-relationship-in-django/


Ви можете використовувати ManyToManyField.
користувач5510975

15

Ви можете використовувати або зовнішній ключ на багатьох сторонах OneToManyвідношення (тобто ManyToOneвідношення), або використовувати ManyToMany(з будь-якої сторони) з унікальним обмеженням.


8

djangoдосить розумний. Насправді нам не потрібно визначати oneToManyполе. Він буде генерований автоматично djangoдля вас :-). Нам потрібно лише визначитись foreignKeyу відповідній таблиці. Іншими словами, нам потрібно лише визначити ManyToOneвідношення, використовуючи 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)  

якщо ми хочемо отримати список коліс конкретного автомобіля. ми будемо використовувати python'sавтоматично створений об’єкт wheel_set. Для автомобіля cви будете користуватисяc.wheel_set.all()


5

Хоча відповідь качаючого каменю є хорошою, простою та функціональною, я думаю, що це вирішує дві речі.

  1. Якщо ОП хотів застосувати номер телефону, він не може належати як Dude, так і бізнесу
  2. Неминуче почуття смутку внаслідок визначення взаємозв'язку на моделі PhoneNumber, а не на моделях Dude / Business. Коли на Землю приходять зайві наземні ресурси, і ми хочемо додати модель Alien, нам потрібно змінити PhoneNumber (якщо припустити, що у ET є телефонні номери), а не просто додати поле "phone_numbers" до моделі Alien.

Представляємо рамки типів вмісту , яка розкриває деякі об'єкти, які дозволяють нам створити "загальний зовнішній ключ" на моделі PhoneNumber. Тоді ми можемо визначити зворотний зв’язок на 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)

Докладні відомості див. У документах і, можливо, ознайомтеся цією статтею для швидкого підручника.

Також ось стаття, яка заперечує проти використання загальних ФК.


0

Якщо модель "багато" не виправдовує створення моделі per se se (це не так, але це може принести користь іншим людям), іншою альтернативою було б покладатися на конкретні типи даних PostgreSQL за допомогою пакету Django Contrib

Postgres може мати справу з типами даних Array або JSON , і це може бути приємним рішенням для обробки One-To-Many, коли багато-багато років може бути прив’язане лише до однієї сутності один .

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

NB: Django не забезпечує жодних стосунків OneToMany. Тому ми не можемо використовувати верхній метод у Django. Але нам потрібно перетворити у реляційну модель. То що ми можемо зробити? У цій ситуації нам потрібно перетворити реляційну модель у зворотну реляційну модель.

Тут:

реляційна модель = OneToMany

Отже, зворотна реляційна модель = ManyToOne

NB: Підтримка 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)

Примітка: ДУМАЙТЕ ПРОСТО !!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.