정참조와 역참조 (FK로서의 예시와 ManytoManyField의 예시) 그리고 모델 클라스 사용법 설명 및 관련 MultiValueDict과 언패킹 관련 요약.

권수민·2023년 9월 21일
1

정참조(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 가 된다.

  1. 정참조

사용자가 다른 사용자를 친구로 추가할때

user1 = User.objects.get(id=1)
user2 = User.objects.get(id=2)
  1. 역참조

특정 사용자에게 친구 요청을 보낸 모든 사용자를 찾을때 :

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

<form 의 주요 인자 2개>

이렇게 넣을 수 있는 이유는 폼은 두개의 주요인자를 받는데 :

1. data:

폼의 일반 필드에 바인딩 될 데이터를 담고 있는 딕셔너리 또는 유사한 객체로서, 대체로 request.POST에서 가져오게된다.

일반필드란 그냥 char,text,integer....등등의 필드를 말한다

2. files:

files 인자는 파일 업로드를 처리하기 위한 데이터를 담고 있는 딕셔너리 또는 유사한 객체로, 주로 request.FILES에서 가져옵니다.

한마디로 이미지파일 및.. file 형태의 것들을 가져오는것.

==================================================

사용예

일반적으로 request의 전체 값을 넣어주는게 보편적이다.

image_form = ImageForm(data=request.POST, files=request.FILES, 옵션들...)

특정 필드만 전달하고 싶다하면 아까처럼

1. 딕셔너리 방식으로 인자를 넣어주던지,

여기서는 예를 더 들어 text값을 더 넣어준것으로 하면

post = PostForm(request.POST, request.FILES)
#여기선 이미 폼안에 id값이 저장되어있다는 가정.
image_form = ImageForm({"post": post.id,"text" : "mo"}, {"image": img_file})

이렇게 되는것이다.

2. request. 값들 복사해서 사용하기

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의 현재 버전에서는 정의되어 있지 않다는것.

즉 타입에 따라 주 사용 연산자가 정해져 있다는 것을 기억해 주면 된다!
'*' : 리스트 형태의 정열
'**': 딕셔너리 형태

profile
초보개발자

0개의 댓글