##Many-to-Many에 대해서 조금 더 공부하기
수업시간에는 facebook의 예로 many-to-many relationships에 대해서 연습해보았다.
class Post(models.Model):
title = models.CharField(max_length=50)
like_users = models.ManyToManyField(
'User',
through='PostLike',
related_name='like_posts',
)
class Meta:
verbose_name_plural = 'Intermediate - Post'
def __str__(self):
return self.title
class User(models.Model):
name = models.CharField(max_length=50)
class Meta:
verbose_name_plural = 'Intermediate - User'
def __str__(self):
return self.name
class PostLike(models.Model):
post = models.ForeignKey(
Post,
on_delete=models.CASCADE,
)
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
)
created_date = models.DateTimeField(
auto_now_add=True
)
class Meta:
verbose_name_plural = 'Intermediate - PostLike'
def __str__(self):
return f'"{title}" 글의 좋아요({name},{date})'.format(
title=self.post.title,
name=self.user.name,
date=datetime.strftime(
timezone.make_naive(self.created.date),'%Y,%m,%d')
)
class FacebookUser(models.Model):
# 자기 자신을 MTM필드로 갖는 모델
name = models.CharField(max_length=50)
friends = models.ManyToManyField('self')
def __str__(self):
# 1. for loop를 사용해서 친구 목록 가져오기
# friends_string = ''
# for friend in self.objects.all():
# friends_string += friend
# friends_string += ', '
# friends_string=friends_string[:-2]
# 2. list Comprehension이용하기
# friends_string = ', '.join([friend.name for friend in self.friends.all()])
# 3. Manager의 values_list를 사용
# DB에서 모든 friends의 'name'필드 값만 가져옴
friends_string = ', '.join(self.friends.values_list('name', flat=True))
return '{name} (친구 : {friends})'.format(
name = self.name,
friends = friends_string,
)
ManyToManyField.symmetrical
한 쪽에서 친구를 맺으면 양 쪽이 친구 관계가 성립하는 Facebook과는 달리 Instagram은 한 쪽에서 일방적으로 follow라는 것을 할 수 있다. 서로의 동의하에 친구가 될 필요 없이 소식을 받을 수 있는 기능이다. 위에서 자기 자신을 MTM필드로 갖는 Facebook 모델에서 한 가지 옵션만 추가하면 된다. ManyToManyField메소드에 symmetrical=False옵션을 설정하면 된다.
class InstagramUser(models.Model):
name = models.CharField(max_length=50)
following = models.ManyToManyField(
'self',
# 대칭관계가 아님을 설정
symmetrical=False,
# 역참조시 사용할 이름
related_name='followers',
)
def __str__(self):
return self.name
Twitter예제를 통해 SNS following과 block기능 구현하기
class TwitterUser(models.Model):
name = models.CharField(max_length=50)
relations = models.ManyToManyField(
'self',
symmetrical=False,
through='Relation',
related_name='+',
)
def __str__(self):
return self.name
def following(self):
# 내가 팔로잉하는 유저목록을 가져옴
# 내가 from_user이며 type이 following인 Relations의 쿼리
following_relations = self.relations_by_from_user.filter(
type=Relation.RELATION_TYPE_FOLLOWING,
)
# 위에서 정제한 쿼리셋에서 'to_user'값만 리스트로 가져옴(내가 팔로잉하는 유저의 pk리스트)
following_pk_list = following_relations.values_list('to_user', flat=True)
following_users = Twitteruser.objects.filter(pk__in=following_pk_list)
return following_users
@property
def block_user(self):
# 내가 block하고 있는 TwitterUser목록을 가져옴
pk_list = self.relations_by_from_user.filter(
type=Relation.RELATION_TYPE_BLOCK).value_list('to_user', flat=True)
return TwitterUser.objects.filter(pk__in=pk_list)
def follow(self, to_user):
# to_user에 주어진 TwitterUser를 follow함
self.relations_by_from_user.create(
to_user=to_user,
type=Relation.RELATION_TYPE_FOLLOWING,
)
def block(self, to_user):
# to_user에 주어진 TwitterUser를 block함
self.relations_by_from_user.create(
to_user=to_user,
type=Relation.RELATION_TYPE_FOLLOWING,
)
class Relation(models.Model):
# 유저간의 관계를 정의하는 모델
# 단순히 자신의 MTM이 아닌 중개모델의 역할을 함
# 추가적으로 받는 정보는 관계의 타입(팔로잉 또는 차단)
RELATION_TYPE_FOLLOWING = 'f'
RELATION_TYPE_BLOCK = 'b'
CHOICES_TYPE = (
(RELATION_TYPE_FOLLOWING, '팔로잉'),
(RELATION_TYPE_BLOCK, '차단'),
)
from_user = models.ForeignKey(
TwitterUser,
on_delete=models.CASCADE,
# 자신이 from_user일때 Relation목록 가져오고 싶을 경우
related_name='relations_by_from_user',
)
to_user=models.Foreignkey(
TwitterUser,
on_delete=models.CASCADE,
# 자신이 to_user일때 Relation목록 가져오고 싶을 경우
related_name='relations_by_to_user',
)
type = models.CharField(max_length=1, choices=CHOICES_TYPE)
class Meta:
unique_together = (
# from_user와 to_user의 값이 이미 있을 경우
# DB에 중복 데이터가 저장을 막음
# ex) from_user가 1, to_user가 3인 데이터가 이미 있다면
# 두 항목의 값이 모두 같은 또 다른 데이터가 존재할 수 없음
('from_user', 'to_user'),
)
Shell에서 실습해보자
>>> from many_to_many.models import *
>>> u1, u2, u3, u4 = [TwitterUser.obejcts.create(name=name) for name in ['박찬혁', '박보영', '아이유', '수지']]
>>> Relation.objects.create(
>>> from_user=u1,
>>> to_user=u2,
>>> type='f',
>>> )
>>> # u1이 u2를 follower함
>>>
>>> Relation.objects.create(
>>> from_user=u1,
>>> to_user=u4,
>>> type='b',
>>> )
>>> # u1이 u4를 block함
Comments