- [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
🍃 해결방법
✔️ 먼저 위와 같은 상황을 해결하기 위해 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
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 다음글이전글이전 글이 없습니다.댓글