2018-02-06 TIL WPS. 22 Intermediate Models

##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