TIL - 0206

self

  • 자기자신을 참조로 가지는 ManyToMany관계
  • 대칭적인 관계를 가짐
  • ex) 내가 친구를 걸면 상대방도 함께 친구가 되는 것
  • ex) 페이스북
from django.db import models

__all__ = (
    'FacebookUser',
)

class FacebookUser(models.Model):
    name = models.CharField(max_length=50)
    friends = models.ManyToManyField('self')

    class Meta:
        verbose_name_plural = 'self - FacebookUser'

    def __str__(self):
        # friends_string = ''
        # for friend in self.friends.all():
        #     friends_string += friend.name
        #     friends_string += ','
        # friends_string = friends_string[:-2]

        # list comprehension 사용
        friends_string = ','.join([friend.name for friend in self.friends.all()])

        # Manager의 values_list 를 사용
        # DB에서 모든 freinds의 name필드 값만 가져옴
        friends_string = ','.join(self.friends.values_list('name', flat=True))

        return '{name} (친구:{friends})'.format(
            name = self.name,
            friends = friends_string,
        )

symmetrical

  • symmetrical : 사전적 의미로 ‘대칭의’란 뜻을 가짐

class InstagramUser(models.Model):
    name = models.CharField(max_length=50)
    following = models.ManyToManyField(
        'self',
        # 1번에서 2번 친구 2번에서 1번 친구는 자동이 아님 false
        # 대칭관계가 아님
        symmetrical=False,
        # 역참조시 사용할 이름
        related_name= 'followers',
    )

symmetrical_intermediate

from django.db import models

__all__ = (
    'TwitterUser',
    'Relation',
)


class TwitterUser(models.Model):
    '''
     내가 a를 follow 함
     나는 a의 follower
     a는 나의 followee

    a와 내가 서로 follow함
        나와 a는 friend

     Block기능이 있어야 함
    '''

    name = models.CharField(max_length=50)
    relations = models.ManyToManyField(
        # 관계에서 무언가를 가져와야하기 때문에 ,through로
        'self',
        symmetrical=False,
        through='Relation',
        related_name='+'
    )

    class Meta:
        verbose_name_plural = 'symmetrical_intermediate - TwitterUser'

    def __str__(self):
        return self.name

    @property
    def block_user(self):
        pk_list = self.relations_by_from_user.filter(
            type=Relation.RELATION_TYPE_BLOCK).values_list('to_user', flat=True)

        return TwitterUser.objects.filter(pk__in=pk_list)

    @property
    def following(self):
        # 내가 from_user이며, type이 팔로잉인 Relation의 쿼리셋
        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)
        # TwitterUser테이블에서 pk가
        # 바로 윗줄에서 만든 follwing_pk_list(내가 팔로잉하는 유저의 pk 리스트)
        # 에 포함하는 User목록을 following_users변수로 할당
        following_users = TwitterUser.objects.filter(pk__in=following_pk_list)
        return following_users

    def follow(self, to_user):
        self.relations_by_from_user.filter(to_user=to_user).delete()

        self.relations_by_from_user.create(
            type=Relation.RELATION_TYPE_BLOCK,
            to_user=to_user,
        )

        # Relation.objects.create(
        #     from_user = self,
        #     to_user = to_user,
        #     type =  RELATION_TYPE_FOLLOWING
        # )

    def block(self, to_user):
        self.relations_by_from_user.filter(to_user=to_user).delete()

        self.relations_by_from_user.create(
        type = Relation.RELATION_TYPE_BLOCK,
        to_user = to_user, )


    def is_followee(self,to_user):
        '''
        내가 to_user를 follow하고 있는지 여부를 true/false로 반환
        :param to_user:
        :return:
        '''
        return self.following.filter(pk=to_user.pk).exists()

    def is_follower(self,from_user):
        return self.followers.filter(pk=from_user.pk).exists()

    @property
    def followers(self):
        pk_list = self.relations_by_to_user.filter(
            type = Relation.RELATION_TYPE_FOLLOWING).values_list('from_user', flat = True)
        return TwitterUser.objects.filter(pk__in=pk_list)


class Relation(models.Model):
    # 변수를 선언해서 사용하는 것이 좀 더 편리함. 나중에 기억하기 좋아서
    RELATION_TYPE_FOLLOWING = 'f'
    RELATION_TYPE_BLOCK = 'b'
    CHOICES_TYPE = (
        (RELATION_TYPE_FOLLOWING, '팔로잉'),
        (RELATION_TYPE_BLOCK, '차단'),
    )
    # 역참조하는 경우, 이름이 같기 때문에 역참조 이름이 필요함
    # from_user와 to_user 둘다 TwitterUser에서 가져오기때문
    from_user = models.ForeignKey(
        TwitterUser,
        on_delete=models.CASCADE,
        #  자신이 from_user인인경우 Relations목록을 가져오고 싶은 경우
        related_name='relations_by_from_user'
    )
    to_user = models.ForeignKey(
        TwitterUser,
        on_delete=models.CASCADE,
        related_name='relations_by_to_user'
    )
    type = models.CharField(max_length=1, choices=CHOICES_TYPE)
    # auto_now 업데이트 될때마다, auto_now_add 처음 불러왔을 때의 시간
    created_date = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name_plural = 'symmetrical_intermediate - Relation'

        unique_together = (
            # from_user와 to_user의 값이 이미 있을 경우
            # DB에 중복 데이터 저장을 막음
            # EX) from_user가 1, to_user가 3인 데이터가 이미 있다면 두 항목의 값이
            # 모두 같은 또 다른 데이터가 존재 할 수 없음.
            # 팔로우 혹은 차단 어떤 관계가 하나라도 있으면 체크
            ('from_user', 'to_user'),
        )

values_list

  • values() - 딕셔너리
  • values_list() key가 없고 value만 출력
  • flat=True 옵션은 각 튜플들을 하나의 리스트로 만들어줌

one-to-one relationships

  • 1:1

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

    def __str__(self):
        return "%s the place" % self.name


class Restaurant(models.Model):
    # 테이블은 다르지만 place의 pk와 동일하므로, 자기자신의 pk이만 가져오면됨

    place = models.OneToOneField(
        Place,
        on_delete=models.CASCADE,
        primary_key=True,
    )

    serves_hot_dog = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

    def __str__(self):
        return "%s the restaurant" % self.place.name


class Waiter(models.Model):
    restaurant = models.ForeignKey(
        Restaurant,
        on_delete=models.CASCADE
    )
    name = models.CharField(max_length=50)

    def __str__(self):
        return "%s the watiter at %s"(self.name, self.restaurant)

hasatter()

  • 문자열의 속성이 있는지 없는지를 물을 때 사용
hasatter(p2,'restaurant')

QuerySet API

Documentation