ジェネリックリレーションのないジェネリック機能



あなたが何かのようないくつかの一般的な機能を持っているときに、コメント、またはupvotable、それは使用するのが一般的ですGeneric Relations in Django . ジェネリックリレーションシップの問題は、データベースレベルではなくアプリケーションレベルでのリレーションシップを作成するためであり、ジェネリック機能を共有するコンテンツを集約する場合は、データベースクエリーが必要です.私がこの記事であなたに示す別の方法があります.
私は2002年に私の最初の仕事でこのテクニックを学び、次に数年前にジャンゴと再びそれを再発見しました.トリックは、ジェネリックItem 他のすべての自律モデルが1対1の関係を持つモデルItem . また、Item モデルはitem_type フィールドを使用すると、後方の1対1の関係を認識できます.
次に、いくつかの一般的なカテゴリを持っている必要があるたびに、あなたはItem. メディアギャラリー、コメント、好き、またはupvotesのような一般的な機能を作成するたびにItem . パーミッション、パブリッシングステータス、ワークフローを操作する必要があるときは、Item . あなたがグローバル検索やゴミ箱を作成する必要があるたびに、あなたはItem インスタンス.
コードを見てみましょう.

アイテム


まず、作成しますitems つのモデルでアプリ:以前言及Item と抽象モデルItemBase 様々なモデルが継承するために1対1の関係を持ちます.
# items/models.py
import sys

from django.db import models
from django.apps import apps

if "makemigrations" in sys.argv:
    from django.utils.translation import gettext_noop as _
else:
    from django.utils.translation import gettext_lazy as _


class Item(models.Model):
    """
    A generic model for all autonomous models to link to.

    Currently these autonomous models are available:
    - content.Post
    - companies.Company
    - accounts.User
    """
    ITEM_TYPE_CHOICES = (
        ("content.Post", _("Post")),
        ("companies.Company", _("Company")),
        ("accounts.User", _("User")),
    )
    item_type = models.CharField(
        max_length=200, choices=ITEM_TYPE_CHOICES, editable=False, db_index=True
    )

    class Meta:
        verbose_name = _("Item")
        verbose_name_plural = _("Items")

    def __str__(self):
        content_object_title = (
            str(self.content_object) if self.content_object else "BROKEN REFERENCE"
        )
        return (
            f"{content_object_title} ({self.get_item_type_display()})"
        )

    @property
    def content_object(self):
        app_label, model_name = self.item_type.split(".")
        model = apps.get_model(app_label, model_name)
        return model.objects.filter(item=self).first()


class ItemBase(models.Model):
    """
    An abstract model for the autonomous models that will link to the Item.
    """
    item = models.OneToOneField(
        Item,
        verbose_name=_("Item"),
        editable=False,
        blank=True,
        null=True,
        on_delete=models.CASCADE,
        related_name="%(app_label)s_%(class)s",
    )

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        if not self.item:
            model = type(self)
            item = Item.objects.create(
                item_type=f"{model._meta.app_label}.{model.__name__}"
            )
            self.item = item
        super().save()

    def delete(self, *args, **kwargs):
        if self.item:
            self.item.delete()
        super().delete(*args, **kwargs)
それから、1と1対1の関係を持ついくつかの自律モデルを作成しましょうItem . 「自律モデル」によって、私は自分自身で十分である、例えば、柱、会社または口座を意味します.型、カテゴリ、タグ、または好きなモデルは、自律的ではない.

ポスト


第二に、私はcontent アプリケーションとPost モデル.このモデルはItemBase これは保存時に1対1の関係を作成し、item_type AScontent.Post :
# content/models.py
import sys

from django.contrib.auth.base_user import BaseUserManager
from django.db import models
from django.contrib.auth.models import AbstractUser

if "makemigrations" in sys.argv:
    from django.utils.translation import gettext_noop as _
else:
    from django.utils.translation import gettext_lazy as _

from items.models import ItemBase


class Post(ItemBase):
    title = models.CharField(_("Title"), max_length=255)
    slug = models.SlugField(_("Slug"), max_length=255)
    content = models.TextField(_("Content"))

    class Meta:
        verbose_name = _("Post")
        verbose_name_plural = _("Posts")

会社


第三に、私はcompanies アプリケーションとCompany モデル.このモデルもItemBase これは保存時に1対1の関係を作成し、item_type AScompanies.Company :
# companies/models.py
import sys

from django.contrib.auth.base_user import BaseUserManager
from django.db import models
from django.contrib.auth.models import AbstractUser

if "makemigrations" in sys.argv:
    from django.utils.translation import gettext_noop as _
else:
    from django.utils.translation import gettext_lazy as _

from items.models import ItemBase


class Company(ItemBase):
    name = models.CharField(_("Name"), max_length=255)
    slug = models.SlugField(_("Slug"), max_length=255)
    description = models.TextField(_("Description"))

    class Meta:
        verbose_name = _("Company")
        verbose_name_plural = _("Companies")

アカウント


第四に、私はより広範な例を持っているaccounts アプリケーションを含むUser モデル.このモデルはAbstractUser からdjango.contrib.auth だけでなくItemBase 一対一の関係のために.The item_type 設定するItem モデルはaccounts.User :
# accounts/models.py
import sys

from django.db import models
from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.models import AbstractUser

if "makemigrations" in sys.argv:
    from django.utils.translation import gettext_noop as _
else:
    from django.utils.translation import gettext_lazy as _

from items.models import ItemBase


class UserManager(BaseUserManager):
    def create_user(self, username="", email="", password="", **extra_fields):
        if not email:
            raise ValueError("Enter an email address")
        email = self.normalize_email(email)
        user = self.model(username=username, email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, username="", email="", password=""):
        user = self.create_user(email=email, password=password, username=username)
        user.is_superuser = True
        user.is_staff = True
        user.save(using=self._db)
        return user


class User(AbstractUser, ItemBase):
    # change username to non-editable non-required field
    username = models.CharField(
        _("Username"), max_length=150, editable=False, blank=True
    )
    # change email to unique and required field
    email = models.EmailField(_("Email address"), unique=True)
    bio = models.TextField(_("Bio"))

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

    objects = UserManager()

新しいアイテムの作成


Djangoシェルを使用して、いくつかの自律モデルインスタンスと関連する項目を作成します.
>>> from content.models import Post
>>> from companies.models import Company
>>> from accounts.models import User
>>> from items.models import Item
>>> post = Post.objects.create(
...     title="Hello, World!",
...     slug="hello-world",
...     content="Lorem ipsum…",
... )
>>> company = Company.objects.create(
...     name="Aidas & Co",
...     slug="aidas-co",
...     description="Lorem ipsum…",
... )
>>> user = User.objects.create_user(
...     username="aidas",
...     email="[email protected]",
...     password="jdf234oha&6sfhasdfh",
... )
>>> Item.objects.count()
3

すべてのそれらの関係から内容を集めること


最後に、1つのビューで投稿、企業、およびユーザーを持つ例です.そのためには、Item 注釈付きのQuerySet
from django import forms
from django.db import models
from django.shortcuts import render
from django.utils.translation import gettext, gettext_lazy as _

from .models import Item


class SearchForm(forms.Form):
    q = forms.CharField(label=_("Search"), required=False)


def all_items(request):
    qs = Item.objects.annotate(
        title=models.Case(
            models.When(
                item_type="content.Post", 
                then="content_post__title",
            ),
            models.When(
                item_type="companies.Company", 
                then="companies_company__name",
            ),
            models.When(
                item_type="accounts.User",
                then="accounts_user__email",
            ),
            default=models.Value(gettext("<Untitled>")),
        ),
        description=models.Case(
            models.When(
                item_type="content.Post",
                then="content_post__content",
            ),
            models.When(
                item_type="companies.Company",
                then="companies_company__description",
            ),
            models.When(
                item_type="accounts.User", 
                then="accounts_user__bio",
            ),
            default=models.Value(""),
        ),
    )

    form = SearchForm(data=request.GET, prefix="search")
    if form.is_valid():
        query = form.cleaned_data["q"]
        if query:
            qs = qs.annotate(
                search=SearchVector(
                    "title",
                    "description",
                )
            ).filter(search=query)

    context = {
        "queryset": qs,
        "search_form": form,
    }
    return render(request, "items/all_items.html", context)

最終語


あなたは、一般的な機能を持つことができますまだItem 包括的関係の代わりに1対1アプローチ
名前Item モデルは異なることができ、あなたは、例えば、様々な目的のために複数のそのようなモデルを持つことができます.TaggedItem タグのみ.
あなたは、あなたのプロジェクトで類似した何でも使いますか?
どのようにこのアプローチを改善することができますか?
コメントで知らせてください!
カバー画像Pixabay