관계를 표현하는 모델 필드 #ForeignKey

2021. 12. 31. 17:58강의 정리/Django Models

반응형
ORM은 어디까지나, SQL 생성을 도와주는 라이브러리이지, ORMDB에 대한 모든 것을 알아서 처리해주지는 않는다.그러므로 보다 성능 높은 애플리케이션을 만들고자 한다면, 사용할 DB엔진과 SQL에 대해 보다 높은 이해가 필요하다.

 


 

 

RDBMS에서의 관계 예시 #설계하기 나름임

1 : N 관계 -> models.ForeignKey로 표현

  • 1명의 유저(User)가 쓰는 다수의 포스팅(Post) # 다수측(Post)에 ForeignKey
  • 1명의 유저(User)가 쓰는 다수의 댓글(Comment) # 다수측(Comment)에 ForeignKey
  • 1개의 포스팅(Post)에 다수의 댓글(Comment) # 다수측(Comment)에 ForeignKey

 

1 : 1 관계 -> models.OneToOneField로 표현

  • 1명의 유저(User)는 1개의 프로필(Profile) # 관계를 User측이나 Profile측 아무 곳에 정의 해도된다. 그러나 보통 Profile 측에서 User에 대한 관계를 정의한다.
  • Django에서는 "auth"라는 앱에서 User라는 모델을 지원해준다. 물론 커스텀 User 모드를 만들 수는 있다.

 

M : N 관계 -> models.ManyToManyField로 표현

  • 1개의 포스팅(Post)에는 다수의 태그(Tag)
  • 1개의 태그(Tag)에는 다수의 포스팅(Post)

 


 

 

ForeignKey

쉽게 설명하면, 테이블 내의 열 중 다른 테이블의 기본키를 참조하는 열을 말한다.

 

1 : N 관계에서 N측에 명시 ex) Post : Comment, User : Post, User : Comment

# 그러나 이러한 것이 꼭 지켜야하는 것은 아니고, 개발하는 서비스에 맞춰 관계 방식을 변경할 수 있다.

 

ForeignKey(to, on_delete) #인자 옵션

to : 대상모델 (1:N에서 1측에 있는 모델)

  • 클래스를 직접 지정하거나, 클래스명을 문자열로 지정. 자기 참조는 "self" 지정

 

on_delete : Record 삭제시 (1 : N의 관계에서 1측에 있는 레코드가 삭제될 때 N측에 속한 1측 레코드들을 어떻게 처리할지에 대한  ) Rule

  • CASCADE : FK로 참조하는 다른 모델의 Record도 삭제 # 디폴트 값
  • PROTECT : ProtectedError를 발생시키며, 삭제 방지
  • SET_NULL : null로 대체, 필드에 null=True 옵션 필수
  • SET_DEFAULT : 디폴트 값으로 대체. 필드에 디폴트 값 지정 필수
  • SET : 대체할 값이나 함수 지정. 함수의 경우 호출하여 리턴값을 사용
  • DO_NOTHING : 어떠한 액션 X. DB에 따라 오류가 발생할 수도 있음

 

class Post(models.Model):
    message = models.TextField(blank=False)
    photo = models.ImageField(blank=True, upload_to='instagram/post/%Y/%m/%d')
    is_public = models.BooleanField(default=False, verbose_name='공개여부')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now_add=True)
    

class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE) # post_id 라는 필드가 생성이 됨
    message = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now_add=True)
# 1 : N의 관계에서 실제 N측에는 어떤 값이 저장되냐면 관계를 정의할 때는 실제 각각의 Model에는 pk(id)값이 있음. 1 : N의 관계의 예를 들어서 Post : Comment의 관계에서는 1에 속해있는 PostpkN에 속해있는 Comment에 저장한다. 그 때 저장할 필드명을 임의로 정할 수 있는데 "지정한 변수_id"라는 값으로 저장된다.

ex)
    post = models.ForeignKey(Post, on_delete=models.CASCADE)

python manage.py sqlmigrate instagram 0004 

CREATE INDEX "instagram_comment_post_id_41735a7d" ON "instagram_comment" ("post_id");


# 다른 앱의 model 또한 관계를 표현할 수 있다.

post = models.ForeignKey('instagram/Post', on_delete=models.CASCADE)

 


 

 

올바른 User 모델 지정

# models.py에서 User 모델을 지정할 수 있게된다.

form django.conf import settings

author = models.ForeignKey(settings.AUTH_USER_MODEL , on_delete=models.CASCADE)

 

 

장고 기본의 django.contrib.auth 앱에서는 User 모델을 기본 제공해주고 있습니다.  이 User 모델이 장고 모든 인증의 중심이 됩니다. (세션 인증, JWT 인증 등)

django.contrib.auth 앱에서 제공되는 User 모델이라서 미리 만들어진 모델인데요. 프로젝트에 따라 User 모델을 우리가 만든 장고앱의 User 모델로 변경하고 싶을 수 있습니다. 그때 myapp 장고앱의 CustomUser 모델을 사용코자 한다면, settings 에서 AUTH_USER_MODEL 값으로 "myapp.CustomUser" 문자열을 지정하면 됩니다.

 

이제 프로젝트에서 사용하는 유저 모델이 다른 앱의 모델로 변경될 수 있음을 아셨을 것입니다. 그런데 이 유저 모델에 대한 외래키를 지정코자 한다면, 유저 모델이 바뀔 수 있기에, 하드 코딩으로 해당 모델 클래스를 임포트해서 지정할 수는 없습니다. 유연하지 못한 대처인 것이죠.

 

외래키는 모델 클래스를 지정할 수도 있지만, "앱이름.모델명" 과 같은 문자열로도 지정할 수 있습니다. 그래서 settings.AUTH_USER_MODEL 을 지정해줄 수 있습니다. 이렇게 지정하시면 현재 어떤 앱의 모델을 유저 모델로 사용하는 지 신경쓰지 않고, 외래키를 지정할 수 있게 되는 것입니다.

 

 

 

 

FK(외래키)에서의 reverse_name  # reverse 접근 시의 속성명 : 디폴트 -> "모델명소문자_set"

related_name="instagram_author_set")

# reverse란 1 : N의 관계에서 1N에 접근하는 것이다.

 

디폴트는 1 : N의 관계에서 1측에서 사용하는 것이다. 

from django.db import models

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

class Post(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    message = models.TextField()


>>> comment.post

>>> post.comment_set.all() <=> Comment.objects.filter(post=post)

 

 


중요 !!!

reverse_name 디폴트 명은 앱이름 고려 X, 모델명만 고려하기에 충돌이 일어날 수 있음

ex)

blog 앱 Post모델, author = FK(User) 

shop앱 Post모델, author = FK(User) 

이름이 충돌이 난다면, makemigrations 명령이 실패한다.

 

충돌을 피하는 방법 # 외래키를 지정할 때 커스텀 지정을 해야한다.

1. 어느 한 쪽의 FK에 대해, reverse_name을 포기 -> related_name = '+'  # 

2. 어느 한 쪽의 (혹은 모두) FK의 reverse_name을 변경

 -1 : FK(User, ..., related_name="blog_post_set")

 

 -2 : FK(User, ..., related_name="shop_post_set")

 

 

 

 


 

ForeignKey.limit_choices_to 옵션

Form을 통한 Choice 위젯에서 선택항목 제한 가능. # 즉 선택 할 모델들의 조건을 달 수 있음. ex) 사진이 있는 글만 선택 가능etc...

  • dict / Q  객체를 통한 지정 : 일괄 지정
  • dict / Q 객체를 리턴하는 함수 지정 : 매번 다른 조건 지정가능
  post = models.ForeignKey(Post, on_delete=models.CASCADE, limit_choices_to={'is_public': True}) # post_id 필드가 생성

 

ManyToManyField에서도 지원

 

 

반응형