ISSUE/Django

[ISSUE] Tag 포함된 Site 모델의 serialize 처리

조별하 2023. 5. 6. 17:34

[22.11.27] 

🍃 구현하고자 하는 화면

1. 이전 개발 단계에서 사이트를 등록하여 해당 사이트에 Tag를 Many-to-Many관계로 등록하는 진행

2. 이제 태그가 등록된 사이트를 최신순으로 4~6개 조회 후 등록된 모든 태그를 조회하여 하단에 Tag button형태로 동적으로 뿌려주는 작업을 진행할 것이다.

3. 위와 같이 진행하기 위해서 Site항목을 조회할 뿐 아니라 Many-to-Many관계로 등록된 태그도 조회를 해야하기 때문에 Serializer 작업을 진행하게 되었다.

  • 물론 api 2개를 요청하여 각각 사이트 데이터, 태그 데이터 조회할 수도 있지만 2번을 요청한다는 것 자체가 비효율적이며 drf에 존재하는 serialize를 활용하지 못한다고 판단

1. views.py > TagsAPIView 구현

class TagsAPIView(APIView):
    """
     Site model에 Tag model값이 존재하는 것만 조회
    """

    def get(self, request):
        word: str  = request.GET['word']

        tags = [tag for tag in Tag.objects.all()]

        list_qs = Site.objects.filter(
                            (Q(tag__in=tags))&(
                            Q(title__contains=word)|
                            Q(host_name__contains=word))).distinct()


        serializer = SiteSerializer(list_qs, many=True)
        return Response(serializer.data)
  • /api/tags?word= api요청으로 들어오는 view 단 작업 코드이다.
  • word는 검색으로 조회 시 요청되는 값이므로 자세한 설명은 생략하겠다.
  • 먼저 태그가 등록된 사이트 항목을 조회하기 위해 모든 태그를 조회하여 tags에 할당
  • Site모델에 tag가 위에서 할당받은 tags가 포함되며 word로 넘어온 값이 포함한 값이 조회 되게 filter를 작성
  • 단, 중복된 값이 조회되지 않게 distinct()를 사용
  • serializer를 사용하면 쿼리 셋 및 모델 인스턴스와 같은 복잡한 데이터를 Python 데이터 타입으로 변환한 다음 JSON, XML 또는 다른 콘텐츠 유형으로 쉽게 렌더링 할 수 있기 때문에 queryset을 SiteSerializer에 넣어 반환받아 리턴 시켰다.

📌 SiteSerializer

lass SiteSerializer(ModelSerializer):

    CATEGORY_CHOICES = [(1, 'python'), (2, 'django'), (3, 'javascript'), (4, 'orm'), (5, 'mysql'), (6, 'drf'), (7, 'docker'), (8, 'os'), (9, 'aws'), (10, 'html'), (11, 'css'), (12, 'git'), (13, 'linux')]

    title           = serializers.CharField(max_length=100, allow_blank=False, trim_whitespace=True)
    thumbnail_url   = serializers.URLField(max_length=200, min_length=None, allow_blank=False)
    host_name       = serializers.CharField(max_length=30, allow_blank=False, trim_whitespace=True)
    content         = serializers.CharField(max_length=2000, allow_blank=True)
    category        = serializers.ChoiceField(choices=CATEGORY_CHOICES)
    user            = serializers.CharField(max_length=10, allow_blank=False, trim_whitespace=True)
    favorite        = serializers.BooleanField(default=False)
    video           = serializers.BooleanField(default=False)

    def create(self, validated_data):
        return Site.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.category = validated_data.get('category', instance.category)
        instance.favorite = validated_data.get('favorite', instance.favorite)
        instance.video = validated_data.get('video', instance.video)
        instance.save()
        return instance    

    class Meta:
        model = Site
        exclude = ["created_at", "updated_at"]
  • 현재 사이트 시리얼라이즈는 위와 같이 Site모델이 가지고 있는 field를 상단에 선언
  • create와 update 작업이 이루어질 때 validation작업을 할 수 있게 함수 작성
  • 반환되는 값은 created_at와 updated_at를 제외한 모든 값이 조회되게 exclude에 created_at, updated_at 을 할당해 주었다.

2. 기존 serializer를 이용한 response

✔️ [http://127.0.0.1:8000/api/tags?word=](http://127.0.0.1:8000/api/tags?word=) api를 호출, 현재 response값


✔️ 현재 response값을 이용해선 사이트를 구현할 수 있지만 태그 값이 존재하지 않아 위의 이미지처럼 하단에 태그를 button형식으로 구현할 수 없다.

✔️ serializer는 한 번만 걸쳐서 tag값이 포함된 site값을 조회하기 위해 models.py와 serializers.py를 수정해 주겠다.

📌 serializers.py

✔️ drf 정식 문서를 보여 하나의 시리얼라이즈에서 many-to-many관계를 가진 두 개의 모델 데이터를 모두 조회할 수 있는 방법을 찾아보았다.

 

✔️ Nested relationships 이라고 하며 서로 두 모델 간 관계가 설정되어 있는 경우 가져올 수 있다.

class SiteSerializer(ModelSerializer):

    CATEGORY_CHOICES = [(1, 'python'), (2, 'django'), (3, 'javascript'), (4, 'orm'), (5, 'mysql'), (6, 'drf'), (7, 'docker'), (8, 'os'), (9, 'aws'), (10, 'html'), (11, 'css'), (12, 'git'), (13, 'linux')]

    title           = serializers.CharField(max_length=100, allow_blank=False, trim_whitespace=True)
    thumbnail_url   = serializers.URLField(max_length=200, min_length=None, allow_blank=False)
    host_name       = serializers.CharField(max_length=30, allow_blank=False, trim_whitespace=True)
    content         = serializers.CharField(max_length=2000, allow_blank=True)
    category        = serializers.ChoiceField(choices=CATEGORY_CHOICES)
    user            = serializers.CharField(max_length=10, allow_blank=False, trim_whitespace=True)
    favorite        = serializers.BooleanField(default=False)
    video           = serializers.BooleanField(default=False)

    **tag             = TagSerializer(many=True, read_only=True)**

    def create(self, validated_data):
        return Site.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.category = validated_data.get('category', instance.category)
        instance.favorite = validated_data.get('favorite', instance.favorite)
        instance.video = validated_data.get('video', instance.video)
        instance.save()
        return instance    

    class Meta:
        model = Site
                # exclude = ["created_at", "updated_at"]
        fields = ['title', 'thumbnail_url'
                                    , 'host_name', 'content'
                                    , 'category', 'user', 'favorite', 'video', **'tag'**]

1. 반환되는 fields에 tag를 포함하여 보낼 수 있게 추가

2. SiteSerializer가 tag를 인식할 수 있게 상단에 tag 필드 설정

3. tag필드를 설정하기 위해 TagSerializer작성

  • site에 묶인 모든 tag를 조회하기 위해 many=True, read_only=True속성 추가
class TagSerializer(ModelSerializer):

    name = serializers.CharField(max_length=20, allow_blank=False, trim_whitespace=True)
    class Meta:
        model = Tag
        fields = ['id', 'name']

1. name 필드 값을 설정

2. 반환되는 field 설정

  • 태그 이름은 화면에 바인딩할 때 사용
  • id 값은 해당 태그 선택 시 api 호출에 대한 식별자 값으로 보내기 위해 사용

✔️ 이렇게 TagSerializer도 생성을 했고 SiteSerializer를 호출할 때 반환되는 값에 tag가 포함되게 코드작성을 해 주었다.

 

 

✔️하지만 결과는 동일하게 tag는 포함되지 않고 response값이 나온 걸 확인할 수 있다.

3. models.py related_name 설정

class Tag(models.Model):
    """ 웹 항목 태그 목록 모델  """
    name = models.CharField(verbose_name='이름', max_length=20)
    site = models.ManyToManyField(Site, **related_name='tag'**,verbose_name='리스트')

    def __str__(self):
        return f"{self.name}"

    class Meta:
        verbose_name = '태그'
        verbose_name_plural = '태그 목록'

✔️ Tag 모델을 탐지할 때 site에서 값을 읽어 낼 수 있도록 related_name을 tag로 설정 후 다시 migrate 하고 진행하여 보겠다.

4. 수정한 serializer를 이용한 response

 

🍃 참고

Django

 

Django

The web framework for perfectionists with deadlines.

docs.djangoproject.com

Serializer relations

 

Serializer relations - Django REST framework

relations.py Data structures, not algorithms, are central to programming. — Rob Pike Relational fields are used to represent model relationships. They can be applied to ForeignKey, ManyToManyField and OneToOneField relationships, as well as to reverse re

www.django-rest-framework.org