정참조(Forward Relation)와 역참조(Reverse Relation)는 관계형 데이터베이스 모델링에서 주로 사용되는 용어이다.
FK 나 OneToOneField 그리고 ManyToManyField를 사용할때 사용되어지는 말이라고 볼 수 있다.
즉, 관계의 정의를 위한 용어인것.
그럼 정참조는 무엇인가?
정참조란 모델 필드에서 다른 모델을 직접 참조하는 것을 의미한다.
class User(AbstractUser):
class Meta :
db_table = "user_list"
follow = models.ManyToManyField(
'self', #내 자신을 연결 User
symmetrical=False,
#서로 동일하지 않게 해주겠다
#=> 서로 팔로워가 되어있지않아도 ㄱㅊ아요.
through = "Follow", #중간 모델 직접정의
related_name='follower',#역참조
)
class Post(models.Model):
class Meta:
db_table = "post_list"
author = models.ForeignKey(
"users.User",
verbose_name="글쓴이",
null=True, # on_delete해주기위해
related_name="authors",
on_delete=models.SET_NULL,
)
title = models.CharField("제목", max_length=50)
content = models.TextField("내용", max_length=8000)
comment = models.ManyToManyField(
"users.User", related_name="comment", through="Comments"
)
위에의 Post모델에서는 User모델을 ForeignKey로, Comment모델을 ManyToMany필드로 불러내어 참조하는 것을 볼 수 있는데, 이렇게 모델안에 FK가 있거나 OneToOne, 그리고 ManyToMany가 직접 사용되어 참조할 때는 정참조라고 한다.
post = post.object.get(post_id = post_id)
post.author.Fullname
여기서 하나 추가로 말씀드리고 싶은것은,
매개변수의 형태에 _id를 붙혀주면 따로 객체를 생성할 필요가 없이
다이렉트로 연결 시킬수 있다는 것이다.
id가 1번인 "글쓴이"가 쓴 모든 포스트를 불러와줘
<case.1>
auth = User.objects.get(id=1)
Post.objects.filter(author = auth) : author는 User모델의 객체이름
<case.2>
다른 author_id의 명시적 방법을 통해 부러주는것.
Post.object.filter(author_id = 1)
이렇게 바로 불러줄 수 있다.
그럼 역참조는 무엇인가?
역 관계 :oreignKey나 OneToOneField, ManyToManyField와 같은 관계 필드를 정의할 때 자동으로 생성되는 관계로
역참조는 반대로 위의 예를 똑같이 말하면, User에서 반대로 Post를 불러올때를 말한다고 설명할 수 있다.
이러한 역관계의 생성시 relate name 이 없으면 기본적으로
=> 소문자로 클라스이름변경 _set : follow_set
역관계 이름이 생성되어진다.
related_name 을 활용하면 이름을 바꿔서 쉽게 명칭을 정해
모델의 각 데이터를 불러 올 수 있다.
=> 인스턴스이름, 모델이름.related_name.all()
예) user1_following = user1.followers.all()
user = User.objects.get(id=1)
related_name없으면 : 자동으로 소문자 변경하고 _set붙혀 =>
user.post_set.all()
related_name이 있으면 :
posts_by_user = user.authors.all()
즉, user모델에서 부터 Post모델에 있는 author필드를 통해 연결되어진 User모델이기때문에 Post이 모든 필드에 접근이 가능한것
그럼 여기서 manytomany필드에서느 어떻게 참조되는 것인가? 그리고 왜 self를 매개변수로 넣어주는 것인가에 대해서 설명!
follow = models.ManyToManyField(
'self', #내 자신을 연결 User
symmetrical=False,
#서로 동일하지 않게 해주겠다
#=> 서로 팔로워가 되어있지않아도 ㄱㅊ아요.
through = "Follow", #중간 모델 직접정의
related_name='follower',#역참조
)
이 파트에서의 self는 말 그대로 내 자신을 연결, 즉, 현 User모델을 참조하는 다른 객체를 생성하라는 말로
class Follow(models.Model):
class Meta:
db_table = "follow_list"
follower = models.ForeignKey(User, related_name="followers",on_delete=models.CASCADE)
followee = models.ForeignKey(User,related_name="followees", on_delete=models.CASCADE)
followed_at =models.DateTimeField("팔로워한날",auto_now_add=True)
자기자신을 manytomany로 연결해서 두개의 fk가 유저에서 나오는것을 볼 수 있다!
현재는 through로 인해 따로 중간 브릿지 모델을 직접생성해주는것을 볼 수 있는데 이때는 그럼 어느것을 참조하여 역참조와 정참조가 진행이 되나?
** through가 없을떄는 "[app_label][model_name][field_name]"
형태로 이름이 생성
예) like = models.ManyToManyField(settings.AUTH_USER_MODEL, verbose_name='좋아요', related_name='likes') 가 User 앱 안의 Post모델에 들어가 있는 필드명이라면
User_Post_like 가 된다.
사용자가 다른 사용자를 친구로 추가할때
user1 = User.objects.get(id=1)
user2 = User.objects.get(id=2)
특정 사용자에게 친구 요청을 보낸 모든 사용자를 찾을때 :
follow_requested = user1.followers.all()
특정 사용자가 친구요청을 보낸 모든 사용자를 찾을때 :
sent_follow_request = user2.followees.all()
추가 설명 : 모델 인스턴스 생성관련 설명
from django.db import models
class User(AbstractUser):
class Meta :
db_table = "user_list"
follow = models.ManyToManyField(
'self', #내 자신을 연결 User
symmetrical=False,
#서로 동일하지 않게 해주겠다
#=> 서로 팔로워가 되어있지않아도 ㄱㅊ아요.
through = "Follow", #중간 모델 직접정의
related_name='follower',#역참조
)
class Follow(models.Model):
class Meta:
db_table = "follow_list"
follower = models.ForeignKey(User, related_name="followers",on_delete=models.CASCADE)
followee = models.ForeignKey(User,related_name="followees", on_delete=models.CASCADE)
followed_at =models.DateTimeField("팔로워한날",auto_now_add=True)
인스턴스 생성 후에 저장해줘야 디비에 저장이되는데,
friendship = Follow(followers=user1, followees=user2, since=date.today())
friendship.save()
왜 모델을 부르는데 매개변수의 값이 들어가나 떠올렸을때
(폼에서는 직접 ({}, {}) 딕셔너리 형태로 인자를 따로따로 넣어줬음 : 이부분은 밑에서 다시 설명하겠다.)
import해서 불러와 모델링 할때 사용하는 models.Model기억하나?
그안에 내부적으로 '__init__'이라는 메서드를 가리고 있는데, 사용자가 명시적으로 모델 클라스안에 __init__을 만들어주지 않아도 장고에서 제공하는 Model로 인해 해당 메서드는 구현이 되어있다.
그렇기에 인자로 값을 바로
=>Friendship(from_user=user1, to_user=user2, since=date.today())
이렇게 넣어줄 시에 바로 init메서드는 자동으로 호출되면서 초기화 작업을 수행하게 되는것! => 직접 할당해주게 되는것
그리고나서 save!!!시켜주면! 저장된다.
이 방법이 아닌
만약 딕셔너리 방식으로 수행해주고 싶다면,
data = {
'from_user': user1,
'to_user': user2,
'since': date.today()
}
friendship = Friendship(**data)
이런식으로 해주면 되는데, 이때 꼭 ** double asterisks 를 해줘서 언패킹으로 접근해줘야한다. (딕셔너리기때문에 '**'언패킹 연산자 사용해줘야해)-----> 밑에서 추가로 설명하겠다.
아까 위에 폼에서는 인자 두개를 딕셔너리 형태로 넣어줬었는데? 뭐가 다른가 하면 폼자체에서는 매개변수의 인자를 2개 받을 수 있게되어 있는 구조이다.
그 이전 프로젝트 했을때 imageform을 생성해줬었는데
image_form = ImageForm({"post": post.id}, {"image": img_file})
이렇게 넣을 수 있는 이유는 폼은 두개의 주요인자를 받는데 :
폼의 일반 필드에 바인딩 될 데이터를 담고 있는 딕셔너리 또는 유사한 객체로서, 대체로 request.POST에서 가져오게된다.
일반필드란 그냥 char,text,integer....등등의 필드를 말한다
files 인자는 파일 업로드를 처리하기 위한 데이터를 담고 있는 딕셔너리 또는 유사한 객체로, 주로 request.FILES에서 가져옵니다.
한마디로 이미지파일 및.. file 형태의 것들을 가져오는것.
==================================================
일반적으로 request의 전체 값을 넣어주는게 보편적이다.
image_form = ImageForm(data=request.POST, files=request.FILES, 옵션들...)
특정 필드만 전달하고 싶다하면 아까처럼
여기서는 예를 더 들어 text값을 더 넣어준것으로 하면
post = PostForm(request.POST, request.FILES)
#여기선 이미 폼안에 id값이 저장되어있다는 가정.
image_form = ImageForm({"post": post.id,"text" : "mo"}, {"image": img_file})
이렇게 되는것이다.
post = Post.objects.get(id = post_id)
post_data = request.POST.copy()
# request.POST는 QueryDict 타입으로 immutable(불변)이므로 복사해서 사용
post_data['post_id'] = post.id
post_data['text'] = "추가적으로 넣고 싶은 값"
image_form = ImageForm(data=post_data, files=request.FILES)
이렇게 넣어주게 되는데... 사실 그냥 딕셔너리방식으로 넣어주는게 가장 깔끔한거같다.
MultiValueDict은 ????
여기서 request.Post 및 request.FILES 은 MultiValueDict의 인스턴스로 위에 언급했다 싶이 querydict이 형태로 불변이다.
MultiValueDict은 Django에서 리스트 형태의 여러 값들을 하나의 키에 대응하여 저장하고 처리할 수 있도록 도와주는 딕셔너리와 유사한 데이터 구조이다.
특정한 키에 대해 여러개의 값을 가진 데이터를 수동으로 만들어 주고 싶을때
*MultiValueDict**을 사용하는데...
현재는 사용자한테 값을 받아 사용하므로 필요가 없는 기능이다.
그치만 예를 보여주겠다.
from django.utils.datastructures import MultiValueDict
data = MultiValueDict({
'key1': ['value1'],
'key2': ['value2a', 'value2b']
})
이렇게 위에 utils에서 기능을 받아와야하고
그리고 data라는 인스턴스를 만들고 MultiValueDict()의 형태로 딕셔너리 값을 넣어주되, 한개의 키 값이 여러개의 값을 넣어줄수 있는 리스트 형태로 들어갈수 있다.
언패킹(unpacking) : * or **
python에서 지원하는 언패킹 연산자 2개이다.
먼저 리스트의 구조 및 활용을 잠시 보면
numbers = [1, 2, 3]
a, b, c = numbers
print(a) # 출력: 1
print(b) # 출력: 2
print(c) # 출력: 3
이렇게 순서에 따라 변수를 적어주고 리스트를 대입하면 그 순서에 맞춰 값이 대입되는 것을 볼 수 있다.
<일단 언패킹 * 사용할때를 예로 들면>
def func(a, b, c):
return a + b + c
args = [1, 2, 3]
result = func(*args) # 같은 효과: func(1, 2, 3)
print(result) # 출력: 6
리스트 형식의 arguments가 있고 그것을 매개변수로 넣어줄때 그 []를 풀어주는 역할을 하는것이 '*'연산자이다.
def func(a=0, b=0):
return a + b
args = {'a': 1, 'b': 2}
result = func(**args) # 같은 효과: func(a=1, b=2)
print(result) # 출력: 3
매개변수를 대입식으로 값을 직접 지정하여 디폴트로 메소드에 넣어줬다.
이럴때는 당연히 딕셔너리 타입을 사용할 수 밖에 없고,
arguments의 값은 딕셔너리의 타입으로 받아 fuc()안에 넣어지게 되는것이다.
이 두 연산자는 주로 함수 인자를 전달할 때나 변수에 값을 할당할 때 사용됩니다.
C언어의 pointer같다고 생각하면서도 다른점은 ***와 같은 연산자는 Python의 현재 버전에서는 정의되어 있지 않다는것.
즉 타입에 따라 주 사용 연산자가 정해져 있다는 것을 기억해 주면 된다!
'*' : 리스트 형태의 정열
'**': 딕셔너리 형태