2 minute read

데이터 모델링

서비스의 요구 사항에 맞게 데이터의 구조와 형식을 정하는 것을 데이터 모델링이라 한다.

1:N 관계

N쪽에 ForeignKey를 준다.

class Comment(models.Model):
    ...
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    review = models.ForeignKey(Review, on_delete=models.CASCADE)

이런식으로 작성하면 된다.

on_delete

  • CASCADE
    오브젝트가 삭제되면 해당 오브젝트를 참조하고 있는 오브젝트들 도 삭제된다.
  • PROTECT
    해당 오브젝트가 하나라도 참조되고 있으면 삭제하지 못한다.
  • SET_NULL
    오브젝트를 삭제하면 해당 오브젝트를 참조하는 오브젝트들의 author가 NULL이 된다.
    null=True옵션을 같이 줘야 사용할 수 있다.

1:1 관계

OneToOneField를 사용한다.
더 나중에 생성되는 쪽에 OneToOneField를 추가한다.
다르게 말해서 한쪽이 다른쪽에 속해있을 때 다른쪽에 속해있는 쪽에 OneToOneField를 추가한다.
명백히 한쪽에 속해있지 않을 경우 어느 쪽에 추가하든 상관없다.

user = models.OneToOneField(User, on_delete=models.CASCADE)

모델 나누기

모델을 나눌 때 데이터를 잃지 않으려면
0001번 마이그레이션에서 A모델에 a,b,c필드가 있으면
B모델을 만들고 가져올 필드 b,c를 만들고 0002번 마이그레이션을 한다.
A모델에서 B모델로 데이터를 옮기고 0003번 마이그레이션을 한다.
A모델에서 b,c필드를 삭제하고 0004번 마이그레이션을 한다.

N:N 관계

ManyToManyField를 사용한다.
두 모델 중 어떤 것에 넣든 상관이 없고 on_delete옵션이 없다.

following = models.ManyToManyField('self', symmetrical=False)

유저와 유저의 관계를 맺으면 자기 자신과 관계를 맺으니 self를 쓴다.
친구 관계는 대칭이지만 팔로우 관계는 대칭관계가 아닌것처럼 self관계는 대칭관계일 수도 있고 대칭관계가 아닐 수도 있다.
대칭 여부는 symmetrical옵션으로 정한다.

그리고 null옵션이 없다. 유저와 팔로워에서의 관계와 같이 팔로워가 하나도 없는 경우가 있기 때문인데 기본적으로 null이라고 보면된다.

제네릭 관계

기존의 방식을 사용했을 때는 a모델이 N쪽이고 b,c모델이 1이면 a모델에 외래키를 주는데 여기서 d모델이 추가되면 외래키를 또 추가해야하는 번거로움이 있다.
그리고 데이터베이스에 null값이 많이 들어가는 단점이 있다.
게시글에 댓글과 추천 기능을 연결시키면 댓글을 작성했을 때 추천은 null값이 들어가기 때문이다.

이렇게 특정 모델 하나와 관계를 맺는 것이 아닌 여러 모델과 일반적인 관계를 맺을 때는 contenttypes를 사용한다.

contenttypes는 장고 앱에 사용되는 모든 모델에 대한 정보를 관리하는 앱이다.

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

class Like(models.Model):
    ...
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField() # id는 항상 양수이기 때문
    liked_object = GenericForeignKey()

    def __str__(self):
        # self.user는 각 모델의 __str__메소드가 들어간다
        return f"({self.user}, {self.liked_object})"

제네릭 관계는 content_type(모델 종류)과 object_id(오브젝트 id)를 저장한다.
예를 들어 content_type이 review고 object_id가 1이면 review모델의 id가1번인 곳과 연결되는 것이다.
다만 접근할 때 좀 번거로워 지는데

model_cls = like.content_type.model_class()
model_cls.objects.get(id=object_id)

이런식으로 접근할 수 있다.
그래서 GenericFoeignKey를 사용하는데 GenericForeignKey를 사용하면 오브젝트에 쉽게 접근할 수 있다.

like.liked_object

이렇게 간단하게 접근할 수 있다.
content_type과 object_id의 이름을 그대로 사용하면 GenericForeignKey의 인자를 생략해도 된다.
그 외의 이름을 사용하면 GenericForeignKey의 인자로 넣어줘야 한다.

모델 Meta 옵션

class Comment(models.Model):
    class Meta:
        ordering = ['-dt_created']

Meta에 옵션을 줘서 모델 전체에 적용시킬 수 있다.
Meta

ModelAdmin

ModelAdmin은 특정 모델을 어드민 페이지에서 어떻게 보여줄지 정한다.
기본 설정 그대로 쓰려면 admin.ModelAdmin
커스텀을 하려면 admin.ModelAdmin를 상속 받고 작성한다.

한 페이지 안에서 데이터들을 다룰려면 inline을 사용한다.

from django.contrib.contenttypes.admin import GenericStackedInline  

class CommentInline(admin.StackedInline):
    model = Comment
    
class LikeInline(GenericStackedInline):
    model = Like

class UserInline(admin.StackedInline):
    model = User.following.through
    fk_name = 'to_user'
    verbose_name = "Follower"
    verbose_name_plural = "Followers"

UserAdmin.fieldsets += ('Custom fields', {'fields': ('nickname', 'profile_pic', 'intro', 'following',)}),
UserAdmin.inlines = (
    UserInline,
)

class ReviewAdmin(admin.ModelAdmin):
    inlines = (
        CommentInline,
        LikeInline,
    )

class CommentAdmin(admin.ModelAdmin):
    inlines = (
        LikeInline,
    )

admin.site.register(User, UserAdmin)
admin.site.register(Review, ReviewAdmin)
admin.site.register(Comment, CommentAdmin)
admin.site.register(Like)

여기서 Like는 제네릭 관계이므로 GenericStackedInline를 상속받아야 한다. 그렇지 않으면 에러가 난다.

User-User 관계는 N:N관계이므로 위와같이 작성한다. N:N관계의 모델을 inline으로 설정하려면
model = [모델].[필드].through
이렇게 작성하고

self관계인 경우에는 fk_name를 설정해야 한다.
ManyToMany필드가 가리키는 오브젝트를 inline으로 설정하려면 from_[모델이름]
ManyToMany필드에 대한 역관계를 inline으로 설정하려면 to_[모델이름]

verbose_name은 어드민 페이지에서 inline을 보여줄 때 사용할 이름이다.

Leave a comment