Как выразить отношения «один ко многим» в Джанго


167

Я определяю свои модели Django прямо сейчас, и я понял, что OneToManyFieldв типовых полях модели не было. Я уверен, что есть способ сделать это, поэтому я не уверен, что мне не хватает. По сути, у меня есть что-то вроде этого:

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

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

В этом случае у каждого Dudeможет быть несколько PhoneNumbers, но отношение должно быть однонаправленным, так как мне не нужно знать о PhoneNumberтом, Dudeкому оно принадлежит, само по себе, поскольку у меня может быть много разных объектов, которые имеют PhoneNumberэкземпляры, такие как Businessfor пример:

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

Чем бы я заменил OneToManyField(чего не существует) в модели для представления такого рода отношений? Я пришел из Hibernate / JPA, где объявить отношения «один ко многим» было так же просто, как:

@OneToMany
private List<PhoneNumber> phoneNumbers;

Как я могу выразить это в Джанго?

Ответы:


135

Для обработки отношений «один ко многим» в Django вам нужно использовать ForeignKey.

Документация по ForeignKey очень полная и должна ответить на все ваши вопросы:

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

Текущая структура в вашем примере позволяет каждому Чувству иметь один номер, и каждый номер должен принадлежать нескольким Чувакам (то же самое с Бизнесом).

Если вы хотите установить обратную связь, вам нужно добавить два поля 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
Не могли бы вы привести пример с приведенной выше проблемой? Я, вероятно, просто упускаю это полностью, но я уже некоторое время читаю документацию по Django и до сих пор неясно, как создать такие отношения.
Нафтули Кей

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

1
@j_syk Хорошее замечание по поводу пользовательской проверки. но кажется довольно хакерским включить и чужой ключ для чувака, и чужой ключ для бизнеса, а затем выполнить пользовательскую (внешнюю по отношению к определению модели) проверку. кажется, что должен быть более чистый путь, но я не могу понять это также.
Энди

В этой ситуации целесообразно использовать ContentTypes Framework, который допускает общие отношения с различными объектами. Однако использование типов контента может очень быстро стать очень сложным, и, если это ваша единственная потребность, этот более простой (хотя и хакерский) подход может быть желательным.
kball

57
Одна важная вещь для упоминания - это аргумент «related_name» для ForeignKey. Таким образом, в классе PhoneNumber, который у вас есть, dude = models.ForeignKey(Dude, related_name='numbers')вы можете использовать его some_dude_object.numbers.all()для получения всех связанных номеров (если вы не укажете «related_name», по умолчанию будет «number_set»).
отмечается

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прав?
Нафтули Кей

6
Я отредактировал свой ответ, чтобы отразить это. Да. ForeignKeyтолько один к одному, если вы укажете ForeignKey(Dude, unique=True), так что с помощью приведенного выше кода вы получите Dudeс несколькими PhoneNumbers.
прежнему

1
@ Роллинг Стоун - спасибо, я добавил это после того, как осознал свою ошибку, как вы прокомментировали. Unique = True не работает точно так же, как OneToOneField, я хотел объяснить, что ForeignKey использует отношение один-к-одному, только если вы укажете Unique = True.
сих пор учатся

8
+1 за это из PhoneNumber. Теперь это начинает иметь смысл. ForeignKeyпо сути, многие-к-одному, так что вам нужно сделать это задом наперед, чтобы получить один-ко-многим :)
Naftuli Kay

2
Может кто-нибудь объяснить значение названия поля phonenumber_set? Я не вижу, что это определено где-либо Это название модели, в нижнем регистре, с добавлением "_set"?
Джеймс Вежба

20

Чтобы быть более понятным - в Django нет OneToMany, только ManyToOne - это Foreignkey, описанный выше. Вы можете описать отношение OneToMany с помощью Foreignkey, но это очень невыразительно.

Хорошая статья об этом: https://amir.rachum.com/blog/2013/06/15/a-case-for-a-onetomany-relationship-in-django/


Вы можете использовать ManyToManyField.
user5510975

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

В то время как перекати ответ «s хороший, простой и функциональный, я думаю , что есть две вещи , которые он не решает.

  1. Если ОП хотел ввести в исполнение номер телефона, он не может принадлежать как Чуваку, так и Бизнесу.
  2. Неизбежное чувство грусти в результате определения отношений в модели PhoneNumber, а не в моделях Dude / Business. Когда на Землю приходят внеземные цивилизации, и мы хотим добавить модель Alien, нам нужно изменить PhoneNumber (при условии, что у ET есть номера телефонов) вместо простого добавления поля «phone_numbers» в модель Alien.

Представьте структуру типов контента , которая предоставляет некоторые объекты, которые позволяют нам создать «универсальный внешний ключ» в модели PhoneNumber. Затем мы можем определить обратные отношения на чувак и бизнес

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 типов данных, и это может быть хороший обходной путь для обработки 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

Примечание: 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: ДУМАЙТЕ ПРОСТО !!

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.