Связи «многие ко одному»
Для определения связи «многие ко одному» используйте ForeignKey:
from django.db import models
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField()
def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)
def __str__(self):
return self.headline
class Meta:
ordering = ['headline']
Ниже приведены примеры операций, которые можно выполнить с помощью возможностей Python API.
Создайте несколько репортеров:
>>> r = Reporter(first_name='John', last_name='Smith', email='john@example.com') >>> r.save() >>> r2 = Reporter(first_name='Paul', last_name='Jones', email='paul@example.com') >>> r2.save()
Создайте статью:
>>> from datetime import date >>> a = Article(id=None, headline="This is a test", pub_date=date(2005, 7, 27), reporter=r) >>> a.save() >>> a.reporter.id 1 >>> a.reporter <Reporter: John Smith>
Обратите внимание, что вы должны сохранить объект, прежде чем присвоить его к связи с внешним ключом. Например, создание Article с несохранённым Reporter вызывает ValueError:
>>> r3 = Reporter(first_name='John', last_name='Smith', email='john@example.com') >>> Article.objects.create(headline="This is a test", pub_date=date(2005, 7, 27), reporter=r3) Traceback (most recent call last): ... ValueError: save() prohibited to prevent data loss due to unsaved related object 'reporter'.
Объекты Article имеют доступ к связанным объектам Reporter:
>>> r = a.reporter
Создайте статью через объект Reporter:
>>> new_article = r.article_set.create(headline="John's second story", pub_date=date(2005, 7, 29)) >>> new_article <Article: John's second story> >>> new_article.reporter <Reporter: John Smith> >>> new_article.reporter.id 1
Создайте новую статью:
>>> new_article2 = Article.objects.create(headline="Paul's story", pub_date=date(2006, 1, 17), reporter=r) >>> new_article2.reporter <Reporter: John Smith> >>> new_article2.reporter.id 1 >>> r.article_set.all() <QuerySet [<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>]>
Добавьте ту же статью в другой набор статей — проверьте, что она перемещается:
>>> r2.article_set.add(new_article2) >>> new_article2.reporter.id 2 >>> new_article2.reporter <Reporter: Paul Jones>
Добавление объекта неправильного типа вызывает TypeError:
>>> r.article_set.add(r2) Traceback (most recent call last): ... TypeError: 'Article' instance expected, got <Reporter: Paul Jones> >>> r.article_set.all() <QuerySet [<Article: John's second story>, <Article: This is a test>]> >>> r2.article_set.all() <QuerySet [<Article: Paul's story>]> >>> r.article_set.count() 2 >>> r2.article_set.count() 1
Обратите внимание, что в последнем примере статья перемещена с Джона на Пола.
Связанные менеджеры также поддерживают поиски по полям. API автоматически отслеживает отношения до тех пор, пока вам это нужно. Используйте двойное подчеркивание для разделения отношений. Это работает на любой глубине вложенности. Ограничений нет. Например:
>>> r.article_set.filter(headline__startswith='This') <QuerySet [<Article: This is a test>]> # Find all Articles for any Reporter whose first name is "John". >>> Article.objects.filter(reporter__first_name='John') <QuerySet [<Article: John's second story>, <Article: This is a test>]>
Здесь подразумевается точное совпадение:
>>> Article.objects.filter(reporter__first_name='John') <QuerySet [<Article: John's second story>, <Article: This is a test>]>
Двойной запрос по связанному полю. Это переводится в условие AND в предложении WHERE:
>>> Article.objects.filter(reporter__first_name='John', reporter__last_name='Smith') <QuerySet [<Article: John's second story>, <Article: This is a test>]>
Для связанного поиска вы можете указать значение первичного ключа или явно передать связанный объект:
>>> Article.objects.filter(reporter__pk=1) <QuerySet [<Article: John's second story>, <Article: This is a test>]> >>> Article.objects.filter(reporter=1) <QuerySet [<Article: John's second story>, <Article: This is a test>]> >>> Article.objects.filter(reporter=r) <QuerySet [<Article: John's second story>, <Article: This is a test>]> >>> Article.objects.filter(reporter__in=[1,2]).distinct() <QuerySet [<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>]> >>> Article.objects.filter(reporter__in=[r,r2]).distinct() <QuerySet [<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>]>
Вы также можете использовать набор запросов вместо буквального списка экземпляров:
>>> Article.objects.filter(reporter__in=Reporter.objects.filter(first_name='John')).distinct() <QuerySet [<Article: John's second story>, <Article: This is a test>]>
Запрос в противоположном направлении:
>>> Reporter.objects.filter(article__pk=1) <QuerySet [<Reporter: John Smith>]> >>> Reporter.objects.filter(article=1) <QuerySet [<Reporter: John Smith>]> >>> Reporter.objects.filter(article=a) <QuerySet [<Reporter: John Smith>]> >>> Reporter.objects.filter(article__headline__startswith='This') <QuerySet [<Reporter: John Smith>, <Reporter: John Smith>, <Reporter: John Smith>]> >>> Reporter.objects.filter(article__headline__startswith='This').distinct() <QuerySet [<Reporter: John Smith>]>
Подсчет в противоположном направлении работает совместно с distinct():
>>> Reporter.objects.filter(article__headline__startswith='This').count() 3 >>> Reporter.objects.filter(article__headline__startswith='This').distinct().count() 1
Запросы могут идти по кругу:
>>> Reporter.objects.filter(article__reporter__first_name__startswith='John') <QuerySet [<Reporter: John Smith>, <Reporter: John Smith>, <Reporter: John Smith>, <Reporter: John Smith>]> >>> Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct() <QuerySet [<Reporter: John Smith>]> >>> Reporter.objects.filter(article__reporter=r).distinct() <QuerySet [<Reporter: John Smith>]>
Если вы удалите репортера, его статьи будут удалены (при условии, что ForeignKey был определен с django.db.models.ForeignKey.on_delete установленным в значение CASCADE, что является значением по умолчанию):
>>> Article.objects.all()
<QuerySet [<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>]>
>>> Reporter.objects.order_by('first_name')
<QuerySet [<Reporter: John Smith>, <Reporter: Paul Jones>]>
>>> r2.delete()
>>> Article.objects.all()
<QuerySet [<Article: John's second story>, <Article: This is a test>]>
>>> Reporter.objects.order_by('first_name')
<QuerySet [<Reporter: John Smith>]>
Вы можете удалить с помощью JOIN в запросе:
>>> Reporter.objects.filter(article__headline__startswith='This').delete() >>> Reporter.objects.all() <QuerySet []> >>> Article.objects.all() <QuerySet []>
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/3.1/topics/db/examples/many_to_one/