• 티스토리 홈
  • 프로필사진
    조별하
  • 방명록
  • 공지사항
  • 태그
  • 블로그 관리
  • 글 작성
조별하
  • 프로필사진
    조별하
    • 분류 전체보기 (124)
      • 1. 공부 (0)
        • 기술 면접 (0)
      • 2. 웹개발 (7)
        • Java (1)
        • IT 정보 (5)
        • Error 모음 (1)
      • 3. 개인관심 (1)
        • 전자 (1)
      • GITHUB (5)
      • IT 기술 면접 (3)
      • COMPUTER (2)
      • TASK (1)
      • LANGUAGES (20)
        • Python (3)
        • Django (6)
        • Java (7)
        • Node (1)
        • Jsp (2)
        • R (1)
      • 데이터과학 (2)
        • 머신러닝 및 딥러닝 (2)
      • DATABASE (5)
        • Oracle (2)
      • ISSUE (10)
        • Django (4)
        • Drf (2)
        • Javascript (1)
        • Git (1)
      • JAVA 교육 (65)
        • Java (3)
        • Jquery (2)
        • Sql (16)
        • Jdbc (1)
        • Db (2)
        • Jsp (9)
        • myWeb (15)
        • Servlet (4)
        • Spring (12)
        • Crawling (0)
        • Hosting (1)
      • 정보처리산업기사 (1)
      • EDUCATION (2)
        • IoT 서비스 (2)
  • 방문자 수
    • 전체:
    • 오늘:
    • 어제:
  • 최근 댓글
      등록된 댓글이 없습니다.
    • 최근 공지
        등록된 공지가 없습니다.
      # Home
      # 공지사항
      #
      # 태그
      # 검색결과
      # 방명록
      • [ISSUE] 2종 데이터 받아오는 api를 restful하게 변경 작업
        2023년 05월 05일
        • 조별하
        • 작성자
        • 2023.05.05.:53

        [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

        ✔️ 하지만 이 과정은 아래 이미지와 같이 상단과 하단 두개의 다른 데이터를 조회하여 뿌려줘야하는 상황에서 api를 1개만 사용하여 두 모델의 값을 받아올 수 있게 코드가 작성된 것이다.
        ✔️ 위와 같이 코드를 작성하게 되면 문제가 2가지가 발생하게 된다. 1. 일단 하나의 api로 2가지의 데이터를 가져와 처리하기 때문에 rest ful하지 못한다는 점이다.
        ✔️ 두번째는 사이트 데이터를 가져오면서 에러가 나게 되면 아래 가져오는 Tag데이터도 가져오지 못한다는 문제가 발생한다.

         

        🍃 해결방법

        ✔️ 먼저 위와 같은 상황을 해결하기 위해 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
        다음글
        다음 글이 없습니다.
        이전글
        이전 글이 없습니다.
        댓글
      조회된 결과가 없습니다.
      스킨 업데이트 안내
      현재 이용하고 계신 스킨의 버전보다 더 높은 최신 버전이 감지 되었습니다. 최신버전 스킨 파일을 다운로드 받을 수 있는 페이지로 이동하시겠습니까?
      ("아니오" 를 선택할 시 30일 동안 최신 버전이 감지되어도 모달 창이 표시되지 않습니다.)
      목차
      표시할 목차가 없습니다.
        • 안녕하세요
        • 감사해요
        • 잘있어요

        티스토리툴바