(Django) ajax를 이용한 댓글과 재댓글 구현하기 - 1
① 댓글 구현하기
1. model 설계
진행하고 있는 프로젝트에서는 comment모델을 생성하였다. 하나의 글에(article) 여러개의 댓글이 달릴 수 있으므로 article과 1:n으로 묶어주었다. 이때 댓글을 작성하는 writer와 article의 주인 owner과 있을테니 이들과 1:n으로 묶어주었다. TimeStampable은 생성시간/업데이트시간을 behaviors.py에 따로 작성해 상속받아 사용하였다.
# social/models.py
class Comment(TimeStampable):
content = models.CharField(max_length=255)
article = models.ForeignKey(Article, on_delete=models.SET_NULL, null=True, related_name='comment')
writer = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='guest_comment')
owner = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='comment')
def __str__(self):
return self.content
2. view 설계
나는 ajax 통신(ajax 통신하는 방법 업로드 예정)을 사용해 유저가 comment를 남길때마다 브라우저가 재랜더링 되지 않게 하였다. 정리를 해보자면
ajax를 통해 템플릿에서 form의 post 요청을 통해 서버에 전달되어질 데이터를 param에 넣어 서버에 전달해준다. 이때 param이라는 변수는 내가 지어준 네임으로 param이라는 변수에 dict형태로 서버에 보낼 데이터를 넣어주었다
서버에서 ajax를 통해 받은 데이터를 가지고 글에 존재하는 comment를 create해준다.
이제 브라우저에 결과를 리턴해주어야 하기때문에 return JsonResponse를 사용해 필요한 데이터 리턴해준다.
실시간으로 댓글이 추가되는 모습을 화면에 그리기 위해 innerHTML을 사용해 서버에서 받은 데이터를 사용하여 실시간으로 화면에 추가되는 것처럼 그려준다
우선 나는 service layer를 사용하여 코드를 분리한다. 코드를 어떻게 나누는지에 대해 간단하게 설명을 하겠다.
역할
view의 역할 : 받은 데이터를 dto형식에 넣어준 후 service에게 데이터가 들어간 dto를 전달한다. 이후 결과값을 리턴받아 해당 템플릿에 뿌려준다service의 역할 : dto형식으로 온 데이터를 상황에 맞게 가공한다(create, update, delete, filter..등등, 데이터베이스에 접근되어 지는 작업)
utils의 역할 : 공통적으로 쓰이는 로직을 함수로 만들어 utils에서 가져와 사용한다
1. 가장 먼저 comment에 필요한 데이터의 타입을 정한 dto를 작성한다
이렇게 dto를 작성하면 데이터를 전송, 가공할때 더 효율적으로 분리하여 쓸 수 있다. comment에 필요한 데이터는 model을 기준으로 작성하면 편하다. writer나 owner는 해당 pk만 존재하면 인스턴스를 찾을 수 있기 때문에 pk를 기준으로 작성하였다. 실제로 저 변수에는 해당되는 pk값들이 들어가야 한다
# social/dto.py
@dataclass
class CommentDto():
content:str
article_pk:str
writer_pk:str
owner_pk:str
2. ajax에서 보낸 데이터를 받아 dto에 맞게 데이터를 넣어주고 보내준다
- ajax에서 보내온 데이터를 data변수에 담는다
- 이전에 만들었던 dto형식에 맞게 데이터를 넣어준다
- dto에 들어간 데이터들을 가공하기 위해 우선 comment_dto에 담는다
- 담은 comment_dto를 social/service 로직에 보내준다. view에서는 데이터를 주고 받고 템플릿에 뿌려주는 역할까지만 하기 때문에 데이터베이스에 접근할 일이 없다.
# social/views.py
class CommentView(LoginRequiredMixin,generic.View):
login_url='/user/signin'
direct_field_name = None
def get(self, request, **kwargs):
return render(request,'detail.html')
def post(self, request,**kwargs):
if request.is_ajax():
data = json.loads(request.body) - 1
comment_dto = self._build_comment_dto(data) - 3
context = SocialService.create_comment(comment_dto) - 4
return JsonResponse(context)
@staticmethod
def _build_comment_dto(data): - 2
return CommentDto(
article_pk = data.get('article_pk'),
content = data.get('content'),
writer_pk = data.get('user_pk'),
owner_pk = data.get('owner_pk')
)
3. view에서 보낸 데이터를 알맞게 가공해준다
- dto:CommentDto를 작성해준 이유는 해당 dto 데이터가 Comment라는 것을 명시적으로 보여주기 위해 작성한 것이다.
- 해당 dto데이터에 .을 통해 접근하여 필요한 데이터들을 가져온다. 여기서 나는 article이나 user가 필터되어야 하는 상황에서는 filter 앱에 있는 service에 접근하여 데이터들을 가져왔다.
- Comment, 즉 댓글을 create해준다.
- 이때 장고의 객체는 자바스크립트가 읽을 수 없기 때문에 자바스크립트가 읽을 수 있는 형식으로 바꾸어 주어야 한다. 그래서 model_to_dict를 사용하여 딕셔너리형태로 바꾸어주면 되지만 여기서 발생한 이슈가 있었다.
- context를 템플릿에 뿌려주야 하는데 나는 context_infor이라는 함수를 만들어 딕셔너리 형태로 데이터들을 보내 context를 함수에서 작성한 후 리턴해주었다.
-
4번 image 이슈 해결
image는 imageField 이기 때문에 model_to_dict로는 불가능하다. 정확하게 왜 그런지는 알 수 없지만 구글링 끝에 나온 방법을 소개하겠다. image도 dict형태로 자바스크립트에 리턴하려면 __dict__로 dict 만들어주고 가장 앞에 들어가 있는 state값을 삭제해주면 딕셔너리 형태로 잘 들어간다
comment_user_img= comment_user_img.__dict__ del comment_user_img['_state']
class SocialService():
# social/service.py
@staticmethod
def create_comment(dto:CommentDto): - 1
article = ProductFilterService.find_article_infor(dto.article_pk) - 2 # article filter
owner = UserFilterService.find_user_infor(dto.owner_pk) # user filter
writer = UserFilterService.find_user_infor(dto.writer_pk) # user filter
profile_nickname = UserFilterService.find_profile_infor(dto.writer_pk).nickname # user-profile nickname
comment_user_img = UserFilterService.get_profile_infor(dto.writer_pk) #user get
user_img = UserFilterService.find_profile_infor(dto.writer_pk).image.url # user-profile image url
comment = Comment.objects.create( - 3
article = article,
writer = writer,
owner = owner,
content = dto.content,
created_at = time.time()
)
comment_writer_pk = SocialFilterService.find_by_comment_infor(comment.pk).writer.pk
# img dict로 변환하는 방법
comment_user_img= comment_user_img.__dict__ - 4
del comment_user_img['_state']
# context_infor로 context 생성
context = context_infor( - 5
comment_created = get_time_passed(comment),
profile_nickname = profile_nickname,
comment_user_img=comment_user_img,
writer_pk=comment_writer_pk,
comment_obj =model_to_dict(comment),
comment = dto.content,
user=user,
new_comment = True,
user_img = user_img
)
return context
4. 템플릿에서 이제 해당 데이터들을 받아 알맞게 화면에 뿌려주면 된다
이때 주의할점은 ajax 통신을 이용하기 때문에 화면이 재로딩될때는 장고의 템플릿으로 인해 데이터들이 화면에 보여지고 실시간으로 변할때는 ajax통신을 이용한 자바스크립트에서 화면을 그려주는 것이다. 그렇기 때문에 장고 템플릿으로 화면을 그리는 방식과 자바스크립트로 화면을 그리는 방식이 같아야 한다. 이게 무슨 소리냐, 쉽게 말해서 장고 템플릿으로 그리는 방식과 똑같이 자바스크립트에서도 innerHtml같은 기능을 사용하여 똑같이 화면에 태그와 데이터들을 추가해주는 것이다
html 코드를 보고 싶다면 여기를 클릭해서 보면된다
코드를 봤다면 알 것이다. 밑에 자바스크립트로 innerHtml을 사용하여 화면과 똑같이 그려주었다. 이때 내 코드가 복잡한 이유는 나는 판매자일 경우와 방문자일 경우를 나누어 데이터를 받고 화면에 보여주기 때문에 복잡하다.
이렇게 댓글 구현은 끝이 났다. 다음은 대댓글 구현인데 사실 comment와 매우매우 비슷하다 !