본문 바로가기

Django/성능 개선 + @

[Django] ManyToMany Field를 이용한 태그 구현

 

메가 쇼핑몰 서비스에서 마켓 생성시 해시태그도 같이 생성되게끔 구현하려고 한다.
해시 태그는 ManyToMany 필드를 사용할 것이다.

아래의 사진처럼 마켓에 해시태그를 추가해서 해당 마켓이 추구하는 스타일 또는 의미를 나타내게 하는 것이다.

 

 

 

1. 모델

Tag 모델

# tag/models.py

class Tag(models.Model):
    name = models.CharField('태그', max_length=30, unique=True)

    class Meta:
        db_table = 'tag'

    def __str__(self):
        return self.name

 

Market 모델

market/models.py

class Market(models.Model):
    name = models.CharField('마켓 이름', max_length=100)
    site_url = models.URLField('마켓 사이트', max_length=100)
    email = models.EmailField('마켓 이메일', max_length=100)
    description = models.TextField('마켓 설명')
    review_point = models.DecimalField('리뷰 포인트', max_digits=2, decimal_places=1, default=0)

    reg_date = models.DateTimeField('생성 날짜', auto_now_add=True)
    update_date = models.DateTimeField('수정 날짜', auto_now=True)

    master = models.OneToOneField(User, on_delete=models.CASCADE)
    tag = models.ManyToManyField(Tag, blank=True)

    class Meta:
        db_table = 'market'

    def __str__(self):
        return f'{self.id}, {self.name}, {self.master}'

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)

        old_tags = self.tag.all()
        new_tags = self.extract_tag_list()

        delete_tags: list[Tag] = []
        add_tags: list[Tag] = []

        for old_tag in old_tags:
            if not old_tag in new_tags:
                delete_tags.append(old_tag)

        for new_tag in new_tags:
            if not new_tag in old_tags:
                add_tags.append(new_tag)

        for delete_tag in delete_tags:
            self.tag.remove(delete_tag)

        for add_tag in add_tags:
            self.tag.add(add_tag)

    def extract_tag_list(self) -> list[Tag, ...]:
        tag_name_list = re.findall(r'#([a-zA-Z\dㄱ-힣]+)', self.description)
        tag_list = []

        for tag_name in tag_name_list:
            tag, _ = Tag.objects.get_or_create(name=tag_name)
            tag_list.append(tag)
        return tag_list

 

 

2. 마켓 생성시 태그 생성 로직

마켓이 생성될 때, 마켓의 decription 필드에서 #xxx 형태로 입력을 받으면, 함수가 실행돼서 xxx 이라는 태그 데이터를 생성하게 된다.

먼저 마켓과 태그는 ManyToMany 필드로 생성을 한다.
그리고 실질적인 태그 생성 함수는 def extract_tag_list() 함수가 된다. 이 함수를 통해서 decription의 해시 태그를 추출해서 Tag 모델 데이터를 생성하게 되는 것이다.

태그 추출

market의 description에서 해시 태그를 추출하기 위해서는 추가적인 함수가 필요하다.
    def extract_tag_list(self) -> list[Tag, ...]:
        tag_name_list = re.findall(r'#([a-zA-Z\dㄱ-힣]+)', self.description)
        tag_list = []

        for tag_name in tag_name_list:
            tag, _ = Tag.objects.get_or_create(name=tag_name)
            tag_list.append(tag)
        return tag_list​

extract_tag_list()에서 re.findall 함수로 #xxx 형식의 해시태그를 탐색하고 정규표현식을 이용해서 태그를 추출 후 저장한다.

get_or_create()를 통해서 1차적인 태그 중복 저장 문제를 해결한다.

 

태그 중복

마켓 생성시 태그 데이터를 생성하게 되지만 기존에 있던 태그 데이터는 어떻게 될까?

old_tags는 기존 태그 데이터를 가져오는 쿼리셋이고 new_tags는 마켓 생성시 쿼리가 발생하는 태그 데이터이다.
old_tags를 for문 돌려서 old_tag가 만약 new_tags 리스트에 존재하지 않는다면 기존의 old_tag를 삭제하는 delete_tags 리스트에 append 한다.

new_tags의 경우에는 old_tags 리스트에 새로운 태그가 없다면 add_tags 리스트에 append해서 생성하도록 한다.

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)

        old_tags = self.tag.all()
        new_tags = self.extract_tag_list()

        delete_tags: list[Tag] = []
        add_tags: list[Tag] = []

        for old_tag in old_tags:
            if not old_tag in new_tags:
                delete_tags.append(old_tag)

        for new_tag in new_tags:
            if not new_tag in old_tags:
                add_tags.append(new_tag)

        for delete_tag in delete_tags:
            self.tag.remove(delete_tag)

        for add_tag in add_tags:
            self.tag.add(add_tag)​