Поле GenericForeignKey

Поле GenericForeignKey не является стандартным полем базы данных, а представляет собой специальный класс в Django, который реализует универсальную связь между моделями. Оно не сохраняется напрямую в базе данных, а использует два других поля: поле для хранения идентификатора типа контента (ForeignKey на модель ContentType) и поле для хранения первичного ключа целевого объекта. При обращении к такому полю Django автоматически определяет нужную модель и загружает связанный объект.

Синтаксис

pythonfrom django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models

class YourModel(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

Пример

Рассмотрим создание модели комментариев, которые могут быть привязаны к разным типам объектов (статьям, постам или продуктам):

pythonfrom django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models

class Comment(models.Model):
    text = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)

Пример

Теперь создадим комментарии для разных объектов:

pythonfrom myapp.models import Article, Post, Product, Comment

article = Article.objects.create(title="First article", content="...")
post = Post.objects.create(title="First post", content="...")
product = Product.objects.create(name="Laptop", price=1000.00)

comment1 = Comment.objects.create(
    text="Great article!",
    content_object=article
)

comment2 = Comment.objects.create(
    text="Interesting post",
    content_object=post
)

comment3 = Comment.objects.create(
    text="Nice product",
    content_object=product
)

print(f"Comment 1: {comment1.content_object} - {comment1.text}")
print(f"Comment 2: {comment2.content_object} - {comment2.text}")
print(f"Comment 3: {comment3.content_object} - {comment3.text}")

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

python"Comment 1: Article object (1) - Great article!"
"Comment 2: Post object (1) - Interesting post"
"Comment 3: Product object (1) - Nice product"

Пример

Получим все комментарии для конкретной статьи:

pythonfrom myapp.models import Article, Comment
from django.contrib.contenttypes.models import ContentType

article = Article.objects.get(id=1)
article_content_type = ContentType.objects.get_for_model(article)

comments = Comment.objects.filter(
    content_type=article_content_type,
    object_id=article.id
)

for comment in comments:
    print(f"Comment: {comment.text}")

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

python"Comment: Great article!"
"Comment: Another great article!"

Пример

Используем обратную связь через поле GenericRelation:

pythonfrom django.contrib.contenttypes.fields import GenericRelation
from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    comments = GenericRelation('Comment')

class Comment(models.Model):
    text = models.TextField()
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

# Now we can get comments directly from article
article = Article.objects.get(id=1)
for comment in article.comments.all():
    print(comment.text)

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

python"Great article!"
"Another great article!"

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

  • поле GenericRelation,
    которое позволяет создавать обратную универсальную связь
  • класс prefetch_related_objects,
    который используется для оптимизации запросов с универсальными связями
  • класс prefetch_related,
    который применяется для предварительной загрузки связанных объектов
  • класс filter,
    который используется для фильтрации записей в QuerySet