일반 관계가 없는 일반 기능



댓글을 달거나 호감을 줄 수 있거나 찬성할 수 있는 것과 같은 일반적인 기능이 있는 경우 Generic Relations in Django 을 사용하는 것이 일반적입니다. 일반 관계의 문제점은 데이터베이스 수준이 아닌 응용 프로그램 수준에서 관계를 만들고 일반 기능을 공유하는 콘텐츠를 집계하려는 경우 많은 데이터베이스 쿼리가 필요하다는 것입니다. 이 기사에서 보여줄 또 다른 방법이 있습니다.

나는 2002년 첫 직장에서 이 기술을 배웠고 몇 년 전 Django에서 다시 발견했습니다. 요령은 다른 모든 자율 모델이 Item와 일대일 관계를 갖는 일반적인 Item 모델을 갖는 것입니다. 또한 Item 모델에는 item_type 필드가 있어 역방향 일대일 관계를 인식할 수 있습니다.

그런 다음 일반 범주가 필요할 때마다 Item.에 연결합니다. 미디어 갤러리, 댓글, 좋아요 또는 찬성 투표와 같은 일반 기능을 만들 때마다 Item에 연결합니다. 권한, 게시 상태 또는 워크플로로 작업해야 할 때마다 Item 를 처리합니다. 전역 검색 또는 휴지통을 만들어야 할 때마다 Item 인스턴스로 작업합니다.

몇 가지 코드를 살펴보겠습니다.

항목



먼저 두 가지 모델이 있는 items 앱을 생성합니다. 이전에 언급한 모델Item과 다양한 모델이 상속할 일대일 관계가 있는 추상 모델ItemBase입니다.

# 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)


그런 다음 Item 와 일대일 관계를 갖는 자율 모델을 만들어 보겠습니다. "자율 모델"이란 게시물, 회사 또는 계정과 같이 자체적으로 충분한 모델을 의미합니다. 유형, 카테고리, 태그 또는 좋아요와 같은 모델은 자율적이지 않습니다.

게시물



둘째, content 모델로 Post 앱을 만듭니다. 이 모델은 저장 시 일대일 관계를 생성하고 ItemBaseitem_type로 정의하는 content.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 앱을 만듭니다. 이 모델은 또한 저장 시 일대일 관계를 생성하고 ItemBaseitem_type로 정의하는 companies.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까지 확장됩니다. item_type 모델에서 설정된 Itemaccounts.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


모든 관계에서 콘텐츠 집계



마지막으로 게시물, 회사 및 사용자를 단일 보기에 표시한 예입니다. 이를 위해 주석이 있는 Item 쿼리 세트를 사용합니다.

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을 사용하여 일반 기능을 사용하면서도 데이터베이스에 대한 다중 적중을 방지할 수 있습니다.
Item 모델의 이름은 다를 수 있으며 예를 들어 태그 전용 TaggedItem과 같이 다양한 목적을 위해 이러한 모델을 여러 개 가질 수도 있습니다.

프로젝트에서 비슷한 것을 사용합니까?

이 접근 방식이 어떻게 개선될 수 있는지 보십니까?

댓글로 알려주세요!


Pixabay의 표지 사진

좋은 웹페이지 즐겨찾기