[ISSUE] ManytoMany ๊ด๊ณ๋ฅผ ๊ฐ์ง ๋ ๋ชจ๋ธ Bulk Create ์์
- -
[22.11.26]
๐ ManytoMany ๊ด๊ณ๋ฅผ ๊ฐ์ง ๋ ๋ชจ๋ธ Bulk Create ์์
โ๏ธ ํ์ฌ ์์ ๊ฐ์ด ๋ฑ๋ก๋ ํญ๋ชฉ์ ๋ํด ํ๊ทธ๋ฅผ ์ถ๊ฐํ๋ ๊ธฐ๋ฅ์ ๊ฐ๋ฐ ๊ตฌํ ์ค์ด๋ค.
- ๋ฑ๋ก๋ ํญ๋ชฉ์ Scrap Parsing์ ํตํด ํน์ ์ฌ์ดํธ์ ๊ด๋ จํ url, ์ธ๋ค์ผ, ์ ๋ชฉ ๋ฑ์ ์ ์ฅํ์ฌ ์ ๊ทผํ ์ ์๊ฒ ๊ตฌํํ ํํฉ ํ๋ฉด์ด๋ค.
- ํน์ ์ฌ์ดํธ๋ฅผ ์ ํํ์ฌ bulk(๋ค์ค)๋ก ํ๊ทธ๋ฅผ ๋ฑ๋กํ ์ ์๊ฒ ์คํฌ๋ฆฝํธ ๊ตฌํ
- ์ ํํ ๊ฐ Site์ Tag Model์ด ManytoMany ๊ด๊ณ๋ก ๋ฐ์ดํฐ ๋ชจ๋ธ๋ง
1. ๋ชจ๋ธ ์ฝ๋
๐ Tag Model
class Site(models.Model):
""" ํญ๋ชฉ์ ๊ดํ ๋ฐ์ดํฐ ๋ชจ๋ธ """
title = models.CharField(verbose_name='ํ์ดํ', max_length=100)
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='์ ์ ')
url = models.CharField(verbose_name='url', max_length=2000, null=True)
thumbnail_url = models.CharField(verbose_name='์ธ๋ค์ผ์ฃผ์', max_length=2000)
host_name = models.CharField(verbose_name='ํธ์คํธ๋ช
', max_length=500)
content = models.TextField(verbose_name='์ปจํ
์ธ ')
# category choices
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')]
category = models.IntegerField(verbose_name='์นดํ
๊ณ ๋ฆฌ', choices=CATEGORY_CHOICES)
favorite = models.BooleanField(verbose_name='์ฆ๊ฒจ์ฐพ๊ธฐ', default=False)
video = models.BooleanField(verbose_name='๋น๋์ค', default=False)
created_at = models.DateTimeField(verbose_name='์์ฑ์ผ', auto_now_add=True)
updated_at = models.DateTimeField(verbose_name='๊ฐฑ์ ์ผ', auto_now=True)
def __str__(self):
return f"{self.title} ({self.get_category_display()})"
class Meta:
verbose_name = 'ํญ๋ชฉ'
verbose_name_plural = 'ํญ๋ชฉ ๋ชฉ๋ก'
๐ Tag Model
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 = 'ํ๊ทธ ๋ชฉ๋ก'
2. js์์ ์ ํํ ํญ๋ชฉ์ id๊ฐ๊ณผ ์ ๋ ฅ๋ Tag๊ฐ ์ ๋ฌ
function setFechData(method, body){
/* Fetch data ์
ํ
*/
let csrftoken = getCookie('csrftoken');
const data = {
method: method,
headers: {
'content-type': 'application/json',
'X-CSRFToken' : csrftoken,
},
body: JSON.stringify(body)
}
return data
}
function bulkTag() {
/* ๋ฒํฌ ํ๊ทธ ์ด๋ฒคํธ */
const data = setFechData("POST",{
pk_ids: selected_articles,
tags: added_tags,
user: "User Id"
})
fetch(`/api/sites/tags`, data)
.then(response => {
let status = response.status
})
.then(() => getSiteList())
.then(() => changeSelected())
.catch(error => console.log(error)
}
โ๏ธ ‘์ ์ฅ’ ๋ฒํผ Click
1. bulkTag() ์คํ ๋ฐ fetch์ ๋ด์ ์ ์กํ data ํ ๋น
2. data๋ setFechData() ํจ์๋ฅผ ์ด์ฉ
- ๋ค๋ฅธ api์์ฒญ ์์๋ ์ฌ์ฌ์ฉํ ์ ์๋๋ก ๊ณตํตํจ์ ์์ฑ
- method๋ ์๋ก ์ ์ฅ๋๋ api์ด๊ธฐ ๋๋ฌธ์ POST๋ก ์์ฒญ
3. pk_ids์๋ ์ ํํ ํญ๋ชฉ์ pk(id) ๊ฐ์ ๋ฐฐ์ด ํ์์ผ๋ก ํ ๋น ex) [1, 2]
4. tags์๋ ์ ํํ ํญ๋ชฉ์ ๋ฑ๋กํ ํ๊ทธ ๋ช ์ ๋ฐฐ์ด ํ์์ผ๋ก ํ ๋น ex) [’python’, ‘django’]
5. fetchํจ์๋ฅผ ์ด์ฉํ์ฌ /api/sites/tags
api์ data ๋ด์ ์ ์ก
3. api url ์ค์
from pocket.views import (
# api_view
SiteBulkAPIView
**SiteTagsAPIView,**
)
api_patterns = [
path('sites/bulk', SiteBulkAPIView.as_view()),
**path('sites/tags', SiteTagsAPIView.as_view()),**
]
urlpatterns = [
# api
**path('api/', include(api_patterns)),**
]
- ํ๋ฉด ์ด๋์ด ์๋ api ๊ด๋ จ์ include ํ์ฌ api_patterns๋ก ์ฐ๊ฒฐ๋๊ฒ ์ค์
- js์์ ์ ์กํ request๋ฅผ SiteTagsAPIView ๋ทฐ๋จ์ผ๋ก ์ด๋ํ ์ ์๊ฒ ๋งดํ
4. view ์ฝ๋ ์์ฑ
class SiteTagsAPIView(APIView):
"""
๋ฒํฌ ํญ๋ชฉ ํ๊ทธ api
"""
# 2_b
def get_list(self):
pk_ids: list = self.request.data.get('pk_ids')
return get_list_or_404(Site, id__in=pk_ids)
# 2_a
def validate_ids(self):
pk_ids: list = self.request.data.get('pk_ids')
for id in pk_ids:
get_object_or_404(Site,id=id)
return self.get_list()
# 1
def post(self, request):
"""
Site ํ๊ทธ ์ถ๊ฐ
"""
# 2,3
sites = validate_ids()
tags = self.request.data.get('tags')
with transaction.atomic():
'''ํธ๋์ ์
์์'''
# 4_a
bulk_tags = [Tag(name=tag) for tag in tags]
# 4_b
created_tags = Tag.objects.bulk_create(bulk_tags)
# 5
[tag.site.add(site.id) for tag in created_tags for site in sites]
return Response({'msg':'Add Tag successfully'}, status=status.HTTP_200_OK)
1. js์์ ์์ฒญํ method์ ๋์ผํ๊ฒ ํจ์๋ช ์ post๋ก ์ง์
2. ์ ๋ฌ๋ฐ์ pk(id)๊ฐ ๋ด๊ธด ๋ฐฐ์ด์ ์ด์ฉํ์ฌ Site list๋ฅผ ์กฐํ
- validate_ids() ํธ์ถ ํ ์กด์ฌํ๋ id๊ฐ๋ค์ธ ๊ฒฝ์ฐ get_list() ํธ์ถ
- get_list()๋ฅผ ํธ์ถํ์ฌ
get_list_or_404(Site, id__in=pk_ids)
์ ๊ฐ์ด pk_ids๊ฐ์ ํฌํจํ Site๋ค ์กฐํ (๋จ, ํญ๋ชฉ์ด ์กด์ฌํ์ง ์์ ๊ฒฝ์ฐ 404 ์๋ฌ ๋ฐํ)
3. ์ ๋ฌ๋ฐ์ ํ๊ทธ๊ฐ ๋ด๊ธด ๋ฐฐ์ด ๋ณ์ ํ ๋น
4. ํ๊ทธ ์์ฑ์ ์ํ Tag ๊ฐ์ฒด ์์ฑ
- list comprehesion์ ์ด์ฉํ์ฌ Tag๊ฐ ๋ด๊ธด ๋ฐฐ์ด ์์ฑ
- bulk_create() ํจ์๋ฅผ ์ด์ฉํ์ฌ ์ด์ ๋จ๊ณ์์ ๋ด์๋ Tag๊ฐ์ฒด๋ฅผ ๋งค๊ฐ๋ณ์๋กํ์ฌ create_tags ํ ๋น
5. ์์ฑ๋ Tags ๋ฆฌ์คํธ์ Sites ๋ฆฌ์คํธ ManytoMany ๋งค์นญ
- list comprehesion์ ์ด์ฉํ์ฌ Tag ๋ชจ๋ธ์ site ์นผ๋ผ์ ์ ๋ฌ๋ฐ๋ site๋ค์ ๋ฃ์ด์ฃผ๋ ์์ ์งํ
bulk_create()๋ฅผ ์ฌ์ฉํ ์ด์
โ๏ธ ๋ฒํฌ ์์ ์ ์ผ๋ฐ create๋ก ์์ ์ ํ๊ฒ ๋๋ฉด ํ๋ํ๋ create๋ฅผ ํ ๋๋ง๋ค DB์ connection์ ์์ฒญํ๊ธฐ ๋๋ฌธ์ ์ค๋ ๊ฑธ๋ฆฌ๊ธฐ๋ ํ๊ณ ๊ณผ๋ถํ๊ฐ ๊ฑธ๋ฆฐ๋ค๋ ๋จ์ ์ด ์๋ค.
โ๏ธ ๊ทธ๋์ Django์์ ์ ๊ณตํ๋ bulk_create()๋ฅผ ์ฌ์ฉํ ๊ฒ์ด๋ค. ์ด ํจ์๋ DB์ connection์ ํ ๋ฒ๋ง ์์ฒญํ์ฌ ์์ฑ์ ํ ๋ฒ์ ์ฒ๋ฆฌํด ์ฃผ๋ ํจ์์ด๊ธฐ ๋๋ฌธ์ ํจ์จ์ ์ธ ๋ฉด์์ ์ข๋ค.
5. Issue1
Internal Server Error: /api/sites/tags
Traceback (most recent call last):
File "/Users/cjy/Lecture/Devket/env-devket/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "/Users/cjy/Lecture/Devket/env-devket/lib/python3.10/site-packages/django/core/handlers/base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/Users/cjy/Lecture/Devket/env-devket/lib/python3.10/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "/Users/cjy/Lecture/Devket/env-devket/lib/python3.10/site-packages/django/views/generic/base.py", line 70, in view
return self.dispatch(request, *args, **kwargs)
File "/Users/cjy/Lecture/Devket/env-devket/lib/python3.10/site-packages/rest_framework/views.py", line 509, in dispatch
response = self.handle_exception(exc)
File "/Users/cjy/Lecture/Devket/env-devket/lib/python3.10/site-packages/rest_framework/views.py", line 469, in handle_exception
self.raise_uncaught_exception(exc)
File "/Users/cjy/Lecture/Devket/env-devket/lib/python3.10/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
raise exc
File "/Users/cjy/Lecture/Devket/env-devket/lib/python3.10/site-packages/rest_framework/views.py", line 506, in dispatch
response = handler(request, *args, **kwargs)
File "/Users/cjy/Lecture/Devket/pocket/decorator.py", line 18, in exec_func
return func(self, request, sites=sites)
File "/Users/cjy/Lecture/Devket/pocket/views.py", line 189, in post
[tag.site.add(site.id) for tag in created_tags for site in sites]
File "/Users/cjy/Lecture/Devket/pocket/views.py", line 189, in <listcomp>
[tag.site.add(site.id) for tag in created_tags for site in sites]
File "/Users/cjy/Lecture/Devket/env-devket/lib/python3.10/site-packages/django/db/models/fields/related_descriptors.py", line 536, in __get__
return self.related_manager_cls(instance)
File "/Users/cjy/Lecture/Devket/env-devket/lib/python3.10/site-packages/django/db/models/fields/related_descriptors.py", line 851, in __init__
raise ValueError('"%r" needs to have a value for field "%s" before '
**ValueError: "<Tag: ์๋ฐ>" needs to have a value for field "id" before this many-to-many relationship can be used.**
โ๏ธ ์์ ๋์ผํ๊ฒ ์ฝ๋๋ฅผ ์์ฑํ์ฌ ์ด๋ฒคํธ๋ฅผ ์คํ์ํค๋ฉด ์ ์ผ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
- Error ๋ด์ฉ
- Tag๋ผ๋ ๊ฐ์ฒด๋ many-to-many ์์ ์ ํ๊ธฐ ์ํด id ํ๋๊ฐ ํ์ํ๋ค.๋ผ๋ ๋ด์ฉ์ด ๋ด๊ฒจ ์๋ค.
bulk_create() ์ฌ์ฉ์ผ๋ก ์๋ฌ๊ฐ ๋ฐ์ํ ์ด์
โ๏ธ ์ฌ๊ธฐ์ ์๋ฌ๋ฅผ ์๊ฐ๋ณด๋ค ์ค๋ ์ก์ง ๋ชปํ๋ ์ด์ ๊ฐ ์์ ์ ํ๋ฉด์ ๋น์ฐํ bulk_create() ํจ์๋ฅผ ์ฌ์ฉํ๋ฉด
โ๏ธ ๋ฆฌํด๋๋ ๊ฐ์ฒด๊ฐ Tag๋ฅผ ์์ฑํ์์ผ๋ ๊ทธ ์์ฑํ id์ pk๊ฐ์ ๊ฐ์ง๊ณ ์๋ค๊ณ ํ๋จํ๊ณ ์์ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ๋ id๊ฐ์ด ์กด์ฌํ์ง ์๋ค๊ณ ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค.
โ๏ธ ๊ฒฐ๋ก ์ ์ผ๋ก bulk_create() ํจ์๋ ์ฌ๋ฌ ์์ฑ ์์ ๋ง ๊ฐ์ง๊ณ ๋ ํจ์จ์ ์ด์ง๋ง ์ฌ์ฌ์ฉ์ฑ ๋ฉด์์๋ ์ข์ง ์๋ค๋ ๊ฒ์ด๋ค. ๊ทธ ์ด์ ์ Tag๋ฅผ Many-to-Many๋ก ์ถ๊ฐํด์ผ ํ๋ ์ํฉ์์ ํ์ฒ๋ฆฌ ์์ ์ ํ์ง ๋ชปํ๋ค.
6. ํด๊ฒฐ ๋ฐฉ๋ฒ
class SiteTagsAPIView(APIView):
"""
๋ฒํฌ ํญ๋ชฉ ํ๊ทธ api
"""
def get_list(self):
pk_ids: list = self.request.data.get('pk_ids')
return get_list_or_404(Site, id__in=pk_ids)
def validate_ids(self):
pk_ids: list = self.request.data.get('pk_ids')
for id in pk_ids:
get_object_or_404(Site,id=id)
return self.get_list()
def post(self, request, **kwards):
"""
Site ํ๊ทธ ์ถ๊ฐ
"""
sites = validate_ids()
tags = self.request.data.get('tags')
with transaction.atomic():
'''ํธ๋์ ์
์์'''
created_tags = [Tag.objects.get_or_create(name=tag)[0] for tag in tags]
[tag.site.add(site.id) for tag in created_tags for site in sites]
return Response({'msg':'Updated successfully'}, status=status.HTTP_200_OK)
- Tag ๊ฐ์ฒด๊ฐ ๋ด๊ธด ๋ฆฌ์คํธ๋ฅผ bulk_create()๋ก ์์ฑํด ์ฃผ๋ ๋ก์ง์์ list comprehesion์ ์ด์ฉํ์ฌ Tag๋ฅผ ์์ฑํ๋ฉฐ ๋ฐํ๋๋ Tag๊ฐ์ฒด๋ฅผ ๋ฐฐ์ด์ ํ ๋นํด ์ค๋ค.
- ํ ๋นํด ์ค Tags ๋ฆฌ์คํธ์ Sites๋ฅผ ๋ฐ๋ณต๋ฌธ์ ํตํด์ tag.site.add(site.id)ํ์ฌ Many-to-Many๊ด๊ณ๋ฅผ ๋งค์นญํด ์ค๋ค.
'ISSUE > Django' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[ISSUE] Tag ํฌํจ๋ Site ๋ชจ๋ธ์ serialize ์ฒ๋ฆฌ (0) | 2023.05.06 |
---|---|
[ISSUE]decorator๋ฅผ ์ด์ฉํ ์ค๋ณต์์ ์ ์ฒ๋ฆฌ (0) | 2023.05.05 |
[ISSUE] signup AbstractBaseUser class (0) | 2023.05.05 |
์์คํ ๊ณต๊ฐ ๊ฐ์ฌํฉ๋๋ค