ManyToManyField

Поле ManyToManyField предназначено для организации связи "многие ко многим" между двумя моделями. Оно создает промежуточную таблицу в базе данных, которая хранит связи между экземплярами моделей. Поле принимает класс модели, с которой устанавливается связь, и предоставляет API для работы с связанными объектами через менеджер отношений.

Синтаксис

pythonfrom django.db import models

class ModelName(models.Model):
  field_name = models.ManyToManyField(
    OtherModel,
    **options
  )

Пример

Давайте создадим модели для блога, где статья может иметь множество тегов, а один тег может принадлежать множеству статей:

pythonfrom django.db import models

class Article(models.Model):
  title = models.CharField(max_length=100)
  content = models.TextField()
  created_at = models.DateTimeField(auto_now_add=True)
  tags = models.ManyToManyField('Tag', related_name='articles')
  
  def __str__(self):
    return self.title

class Tag(models.Model):
  name = models.CharField(max_length=50, unique=True)
  created_at = models.DateTimeField(auto_now_add=True)
  
  def __str__(self):
    return self.name

Пример

Добавим теги к статьям и получим все статьи для определенного тега:

pythonfrom myapp.models import Article, Tag

# Create tags
python_tag = Tag.objects.create(name='python')
django_tag = Tag.objects.create(name='django')
api_tag = Tag.objects.create(name='api')

# Create article and add tags
article = Article.objects.create(
  title='Django REST Framework',
  content='Creating REST API with Django'
)
article.tags.add(python_tag, django_tag, api_tag)

# Get all articles for python tag
res = python_tag.articles.all()
print(res)

Результат выполнения кода:

python[<Article: Django REST Framework>]

Параметры

Поле ManyToManyField принимает несколько важных параметров, влияющих на поведение связи:

Пример

Используем параметр related_name для получения статей через тег:

pythonfrom myapp.models import Tag

# Get tag by name
tag = Tag.objects.get(name='python')

# Get all articles with this tag
res = tag.articles.all()
for article in res:
  print(article.title)

Результат выполнения кода:

python"Django REST Framework"
"Python Basics"

Пример

Добавим несколько тегов к статье и удалим один из них:

pythonfrom myapp.models import Article, Tag

# Get article and tags
article = Article.objects.get(title='Django REST Framework')
tag1 = Tag.objects.get(name='python')
tag2 = Tag.objects.get(name='django')
tag3 = Tag.objects.get(name='api')

# Add multiple tags
article.tags.add(tag1, tag2, tag3)

# Remove one tag
article.tags.remove(tag2)

# Get all tags for article
res = article.tags.all()
for tag in res:
  print(tag.name)

Результат выполнения кода:

python"python"
"api"

Пример

Очистим все теги у статьи и проверим результат:

pythonfrom myapp.models import Article

# Get article
article = Article.objects.get(title='Django REST Framework')

# Clear all tags
article.tags.clear()

# Check if tags exist
res = article.tags.exists()
print(res)

Результат выполнения кода:

python"False"

Пример

Используем промежуточную модель для хранения дополнительной информации о связи:

pythonfrom django.db import models

class Article(models.Model):
  title = models.CharField(max_length=100)
  content = models.TextField()
  tags = models.ManyToManyField(
    'Tag',
    through='ArticleTag',
    related_name='articles'
  )

class Tag(models.Model):
  name = models.CharField(max_length=50, unique=True)

class ArticleTag(models.Model):
  article = models.ForeignKey(Article, on_delete=models.CASCADE)
  tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
  added_at = models.DateTimeField(auto_now_add=True)
  order = models.IntegerField(default=0)

  class Meta:
    ordering = ['order']

Пример

Работаем со связью через промежуточную модель:

pythonfrom myapp.models import Article, Tag, ArticleTag

# Create objects
article = Article.objects.create(
  title='Advanced Django',
  content='Deep dive into Django'
)
tag = Tag.objects.create(name='advanced')

# Create relationship with extra data
ArticleTag.objects.create(
  article=article,
  tag=tag,
  order=1
)

# Get all tags for article with extra data
res = ArticleTag.objects.filter(article=article)
for rel in res:
  print(f"{rel.tag.name} - order: {rel.order}")

Результат выполнения кода:

python"advanced - order: 1"

Смотрите также

  • поле ForeignKey,
    которое создает связь "один ко многим"
  • поле OneToOneField,
    которое создает связь "один к одному"
  • метод add,
    который добавляет объекты в отношение
  • метод remove,
    который удаляет объекты из отношения