일반 관계가 없는 일반 기능
댓글을 달거나 호감을 줄 수 있거나 찬성할 수 있는 것과 같은 일반적인 기능이 있는 경우 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
앱을 만듭니다. 이 모델은 저장 시 일대일 관계를 생성하고 ItemBase
를 item_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
앱을 만듭니다. 이 모델은 또한 저장 시 일대일 관계를 생성하고 ItemBase
를 item_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
모델에서 설정된 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
모든 관계에서 콘텐츠 집계
마지막으로 게시물, 회사 및 사용자를 단일 보기에 표시한 예입니다. 이를 위해 주석이 있는
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의 표지 사진
Reference
이 문제에 관하여(일반 관계가 없는 일반 기능), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/djangotricks/generic-functionality-without-generic-relations-1dfh텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)