새소식

ISSUE/Drf

[ISSUE] 2종 데이터 받아오는 api를 restful하게 변경 작업

  • -

[22.11.29] 2종류 데이터를 받아오는 api, restful하게 변경 작업

🍃 many-to-many serialize

✔️ many-to-many 관계가 존재하는 Model Site, Tag의 데이터를 하나의 api를 요청해서 동시에 받아오도록 serialize와 models, view쪽 작업을 진행했었다.

 

serializers.py

**class TagSerializer(ModelSerializer):

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

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)
    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
        fields = ['id','title', 'thumbnail_url', 'host_name'
                 , 'content', 'category', 'user', 'favorite', 'video'**, 'tag'**]

✔️ serializer에 TagSerializer를 작성한 후, SiteSerializer를 거칠때 반환 값을 Tag도 포함 되게 받을 수 있게 tag라는 칼럼을 설정해 주고 Meta-fields에 tag를 추가해 주었다. serializer에서 이렇게 태그를 사용하기 위해 Model에도 설정을 잡아주어야 했다.

 

Models.py

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 = '태그 목록'

✔️ 기존에는 site칼럼에 related_name옵션으로 tag가 설정되어 있지 않았지만 시리얼라이즈에서 호출해서 사용하기 위해 추가해주었다.

 

Views.py > TagsAPIView

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

    def get(self, request):
        word: str  = request.GET['word']
        tags: list = [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))).order_by('created_at').distinct()

        serializer = SiteSerializer(list_qs, many=True)
        return Response(serializer.data)

✔️ 태그의 모든 값을 불러와서 사이트를 조회할 때 태그가 포함되어 있는 사이트만 조회 될 수 있게 filter 조건으로 추가해 준 후 serialize를 통해 위에서 작성해둔 tag데이터를 반환할 수 있게 작성하였다.

📌 Issue

 

🍃 해결방법

✔️ 먼저 위와 같은 상황을 해결하기 위해 1개의 api를 분리하여 2개의 api요청을 주어 값을 변경할 수 있게 코드를 수정해야 했다.

📌 수정 전

get-site-list.js

//${apiURL[apiUrlKey]} = /api/tags/

async function getSiteList(word='') {
    /* 각 탭에 해당되는 모든 항목들을 조회하여 함수 */

    // 선택한 탭 활성화하기  
    makeActive()

    // 해당되는 항목들 조회하기  
    await fetch(`${apiURL[apiUrlKey]}?word=${word}`)
        .then(response => response.json())
        .then(data => {
                mapPosts(data)
        })
        .catch(err => {
            console.log(err);
        })
}

✔️ 위와 같이 fetch를 통해 api를 통신을 통해 값을 불러오는데 위와 같은 작업으로 data안에 site의 데이터와 tag의 데이터가 모두 존재하고 있다.

function mapTags(data) {
    /* 태그 화면 구현 함수 */

    // root 모든 요소 초기화
    removeAllNode(root)

        // 사이트 조회하는 함수
    data.map(item => {
        renderItem(item);
    })

        // 태그 조회하는 함수
    renderTag(data);
}

✔️ 전달 받은 데이터를 site를 조회하는 함수와, tag를 조회하는 함수에 각각 보내줘 데이터를 조회 할 수 있게 작성하였는 데 위의 fetch와 바인딩 되는 구조를 수정해 보겠다.

📌 수정 후

✔️ 먼저 모든 태그 화면을 클릭하여 들어 갔을 때 위에서 작성한 getSiteList()함수가 호출되게 작성되어있었다.

 

✔️ getSiteList()함수는 fetch가 한번만 호출되는 구조를 가졌기 때문에 태그화면은 다른 함수를 호출할 수 있게 분기를 걸어주었다.

// '모든 태그'는 site, tag관련하여 api를 2번 호출하기 위해 분기
apiUrlKey === 'tags' ? getSiteByTagList() : getSiteList()

✔️ 모든 태그 화면으로 이동하였을 때 apiUrlKey는 tags라는 값을 가지고 있는데 그 값을 가지면 tag화면을 조회하는 getSiteByTagList()를 호출하게 하였다.

 

get-site-list.js

function getSiteByTagList(word='') {
    /* 모든 태그 화면을 조회하기 위한 함수 */

    // 선택 메뉴 활성화
    makeActive()

    const siteFetch = fetch(`/api/sites?word=${word}`).then(response => response.json())
    const tagFetch  = fetch(`/api/tags`).then(response => response.json())

    Promise.all([siteFetch, tagFetch])
                        .then(result => {
                            let siteData = result[0]
                            let tagData  = result[1]

                            mapPosts(siteData)
                            mapTags(tagData)                           
                        })
                        .catch(error => console.log(error))
}

참고사이트

[Javascript] fetch 사용법 비동기식 프로그래밍 이해하기

Promise.all() - JavaScript | MDN

async와 await

자바스크립트 async와 await

 

serializers.py

class TagSerializer(ModelSerializer):

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

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)
    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
        fields = ['id','title', 'thumbnail_url', 'host_name', 'content'
                 , 'category', 'user', 'favorite', 'video']

✔️ 현재 사이트 조회 시 tag데이터는 필요하지 않기 때문에 Meta-fields에서 tag항목 제거

 

✔️ 태그 칼럼은 사용하지 않기 때문에 필요한 경우 다시 추가해 주겠다.

 

models.py

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

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

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

✔️ 추가 되었던 related_name='tag' 제거

 

views.py > TagsAPIView

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

    def get(self, request):
        word: str  = request.GET['word']
        tags: list = [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))).order_by('created_at').distinct()

        serializer = SiteSerializer(list_qs, many=True)
        return Response(serializer.data)
class TagsAPIView(APIView):
    """
     Site model에 Tag model값이 존재하는 것만 조회
    """

    def get(self, request):
        """
        Site 태그 조회
        """ 

        tags       = Tag.objects.all()
        serializer = TagSerializer(tags, many=True)

        return Response(serializer.data)

✔️ 좌측 사이트조회 와 우측 태그를 각각 조회하는 로직으로 변경

 

'ISSUE > Drf' 카테고리의 다른 글

[ISSUE] APIView로 bulk update 구현  (0) 2023.05.05
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.