TIL DAY 22 || Python Django Recursive Relationship Model

TK·2021년 3월 14일
1

TIL

목록 보기
31/55

인스타그램 클론 프로젝트를 진행하면서 팔로우/팔로잉 기능을 구현해야 했다.

분명 many-to-many 관계 였지만 뭔가 달랐다.

우선 처음부터 정리해보자.

  • 인스타그램에서 특정 유저(follower)가 누군가(followee)를 팔로잉한다는 것은, many to many 관계이다.

  • 어떤 유저 한명이 여러 유저들을 팔로우 할 수 있고 반대로 어떤 유저 한명이 다른 유저들에게 팔로잉 당할 수 있기 때문이다.

  • 그런데 이 유저들은 모두 User 모델에 속해있다.

  • 따라서 유저와 유저 간 팔로우 관계는 many to many 이지만, 다른 모델이 아닌 자기 자신을 참조해야 되는 구조가 나오는 것이다.

--> User 모델의 인스턴스가 같은 User 모델의 또 다른 인스턴스를 참조하는 구조

Recursive Relationship Model 'User'

다음은 내가 작성한 User 모델이며, 자기참조를 하고 있다.

  • User 테이블에 ManyToManyField 인스턴스 follow 를 추가했다.
  • 첫 번째 인자에 모델 명을 적는데 자기 자신을 참조하는 것이기 때문에 'self' 를 인자로 넘겨주었다.
  • 키워드 파라미터 through 새로 생성한 중개모델 Follow 를 지정해주었다.

이렇게 지정해주면 Follow 중개모델이 User 테이블을 참조한다는 의미이다. 원래 보통은 두개의 다른 테이블을 참조하지만 여기서는 같은 테이블을 참조한다.

symmetrical option

symmetrical=True

해당 관계는 django 가 역방향 관계에 대한 descriptor 를 추가하지 않기 때문에 대칭적 관계로 형성된다.

유저 a 가 유저 b 를 친구로 추가했을 때 b 또한 a 의 친구라는 개념이다.

하지만 인스타의 팔로우 관계처럼 일방적인(비대칭) 관계를 형성하고자 할 경우 symmetrical=False 옵션을 ManyToManyField 에 추가한다.

symmetrical=False

유저 a 가 유저 b 를 팔로우 했다고 가정해보자.

a 가 b 를 팔로우한다고 해서 b 가 자동으로 a 를 팔로우 하는 것이 아니기 때문에 a 와 b 의 관계는 대칭적인 것이 아니다.

따라서 symmetrical=False 옵션을 주게 되면 django 는 역참조 관계를 위한 descriptor 를 만들게되고 비대칭적인 관계가 형성된다.

보통은 비대칭적인 관계가 많기 때문에 symmetrical=False 로 설정해둔다.

How to add values

다음은 내가 views 에서 작성한 예제이다.

  • follow 요청이 왔을 때 중개모델 Follow 에 직접 접근해서 User 인스턴스를 추가해주고 있다.

  • unfollow 요청이 왔을 때 마찬가지로 중개모델에서 해당 row 를 삭제한다.

    이렇게 하지 않고 User 인스턴스 user1.add(user2) 이런식으로 해도 추가가 되긴 하지만 누가 누구를 팔로잉 하는지에 대한 것이 명확하지 않다. 따라서 중개모델에서 직접 from_user, to_user 에 User 인스턴스를 할당하는 것이 명확하고 확실하다.

다음은 중개모델 Follow 에서는 어떤 역할을 하고 있는지 자세히 알아보자.

Intermidiary Model 'Follow'

  • 중개모델 Follow 에 팔로우하는 유저 간 방향성을 명확하게 하기 위해서 User 모델을 참조하는 fk(외래키) from_user 와 to_user 를 설정해주었다.

  • related_name 은 자기가 바라보는 테이블이 자기 모델을 역참조를 했을 때 <model 명 소문자>_set 대신 쓸 이름을 지정해주는 것이다.

  • from_user 의 related_name 을 follow_by_from_user 로 설정하고, to_user 의 related_name 을 follow_by_to_user 로 설정했다.

  • 유저 a 가 유저 b 를 팔로우했을 때 a.follow_by_from_user 를 하면 a 가 from_user 필드에 있는 모든 row 들을 QuerySet 객체로 보여준다.

  • 반대로 b.follow_by_to_user 를 했을 때도 마찬가지로 유저 b 가 to_user 필드에 있는 모든 row 들을 QuerySet 객체로 반환한다.

Meta class attribute 'unique_together'

의문점이 생겼다. views 에서 add 메소드를 사용해서 user1.add(user2) 와 같은 방식으로 팔로잉 관계를 추가해주면 Follow 테이블에서 알아서 중복 관리가 된다.

하지만 지금은 create 메소드로 Follow.objects.create(from_user=from_user, to_user=to_user) 이렇게 추가해주고 있기 때문에 이미 a 가 b 를 팔로우 하는 row 가 있어도 추가적으로 계속 들어가게 된다.

따라서 이를 방지하려면 중복 값에 대한 제한을 걸어야 한다.

나는 이제까지 어떤 특정한 컬럼 값에 대해서만 중복 제한을 걸었지만 Meta 클래스에서 제공하는 기능을 공부하고 나서 다수의 컬럼 값에 대해서도 unique constraint 를 걸 수 있다는 것을 알게되었다.

여러개 컬럼 값에 대해 중복 제한을 거는 방법은 간단하다.
Meta 클래스의 unique_together attribute 에 튜플형태로 컬럼 명을 지정해주면 된다.

이렇게 되면 a 가 b 를 이미 팔로우하고 있을 때 a 가 b 를 팔로우 하겠다는 요청이 왔을 때
unique constraint 에 걸려서 다음과 같은 오류가 발생하게 된다.

django.db.utils --> IntegrityError

profile
Backend Developer

0개의 댓글