파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 강의를 듣고 정리한 글입니다.
본 포스팅에서는 웹 문서 형태만(자바스크립트 없이)으로 댓글 쓰기 화면을 구성하고 개선해본다.
물론 웹문서이기 때문에 부분적으로 화면이 업데이트하지 않고 새로고침 되기에 UX가 깔끔하지는 않다.
그럼에도 다음과 같은 이유로 포스팅을 해보려고 한다.
우선 일반적인 MTV 형태로 댓글 쓰기를 구현해 보겠다.
뷰의 형태는 일반적이다. GET 요청 시 빈 폼을 응답하고 POST 요청 시 데이터를 수정한다.
# instagram/views.py
@login_required
def comment_new(request, post_pk):
post = get_object_or_404(Post, pk=post_pk)
if request.method == 'POST':
form = CommentForm(request.POST, request.FILES)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.author = request.user
comment.save()
return redirect(comment.post)
else:
form = CommentForm()
return render(request, 'instagram/comment_form.html', {
'form': form,
})
# instagram/urls.py
from django.urls import path, re_path
from . import views
app_name = 'instagram'
urlpatterns = [
# ...
path('post/<int:post_pk>/comment/new/', views.comment_new, name='comment_new'),
]
코멘트 모델을 정의했다. 저자(author), 포스트의 PK, 내용(message)를 갖는다.
# instagram/models.py
class BaseModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class Comment(BaseModel):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
message = models.TextField()
class Meta:
ordering = ['-id']
해당 폼의 관심사는 오직 message 필드이다. 나머지는 뷰에서 채운다.
# instagram/forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['message']
instagram/templates/instagram/_post_card.html
여러개의 코멘트가 출력되고 코멘트 버튼을 클릭했을 때 comment_new 뷰를 호출하도록 구성하였다.
{% load humanize %}
<div class="card">
<div class="card-header">...</div>
<div class="card-body">
...
<div class="comment-list mt-3 mb-3">
{% for comment in post.comment_set.all %}
<div class="comment">
<strong>{{ comment.author }}</strong>
{{ comment.message }}
<small class="text-muted">{{ comment.created_at|naturaltime }}</small>
</div>
{% endfor %}
</div>
</div>
<div class="card-footer">
<a href="{% url 'instagram:comment_new' post.pk %}">댓글 쓰기</a>
</div>
</div>
</div>
</div>
instagram/templates/instagram/comment_form.html
{% extends 'instagram/layout.html' %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-sm-6 offset-sm-3">
{% include '_form.html' with form_title='댓글 쓰기' submit_label='댓글 쓰기' %}
</div>
</div>
</div>
{% endblock %}
장고의 일반적인 MTV구조로 댓글 쓰기를 구현했다. 그러나 아직 UI/UX측면에서 개선해야 할 점이 많다.
실제 인스타그램이라고 생각해보면 포스트 리스트나 포스트 상세 페이지에 바로 댓글 쓰기 폼이 있어야 할 것 같다. 그러나 우리가 작성한 코드는 댓글 작성 페이지가 따로 존재한다. (아래 이미지를 참고하자)
어떻게 개선할 수 있을까? 이러한 문제는 자바스크립트를 작성하지 않고도 폼과 템플릿을 좀더 유연하게 사용함으로써 개선이 가능하다. 코멘트 폼 페이지에서 폼을 받는 것이 아니라 포스트 카드에서 코멘트의 빈 폼을 받아서 코멘트 폼까지 함께 랜더링 하는 것이다.
PS. 포스트 카드는 포스트 상세페이지, 포스트 리스트 페이지에서 포스트를 랜더할 때 공통으로 사용한다.
기존에는 포스트 카드에 댓글을 바로 쓰는게 아니라 댓글 쓰기 버튼이 있다.
포스트 카드에서 댓글 쓰기 버튼을 누르면 댓글 쓰기 폼으로 이동한다. 일반적인 UI/UX는 아니다.
다음과 같이 포스트 카드에 댓글 쓰기가 존재하도록 개선하면 좋겠다.
기존의 포스트 리스트 및 포스트 상세 페이지에 코멘트 폼을 함께 랜더하기 한다. 이를 위해 응답 뷰에 CommentForm을 추가한다.
# instagram/views.py
@login_required
def index(request):
post_qs = Post.objects.all()
post_qs = post_qs.filter(
Q(author=request.user) |
Q(author__in=request.user.following_set.all())
)
timesince = timezone.now() - timedelta(days=3)
post_qs = post_qs.filter(created_at__gte=timesince)
suggested_user_list = get_user_model().objects.all()
suggested_user_list = suggested_user_list.exclude(pk=request.user.pk)
suggested_user_list = suggested_user_list.exclude(pk__in=request.user.following_set.all())[:3]
comment_form = CommentForm() # 기존의 list뷰 응답에 CommentForm을 추가한다.
return render(request, "instagram/index.html", {
'post_list': post_qs,
'suggested_user_list': suggested_user_list,
'comment_form': comment_form
})
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk)
comment_form = CommentForm() # 기존의 detail뷰 응답에 CommentForm을 추가한다.
return render(request, 'instagram/post_detail.html', {
'post': post,
'comment_form': comment_form,
})
UI개선을 위해 widget 속성을 추가한다. 장고에서는 폼의 위젯 속성을 수정해서 HTML 위젯 속성을 부여할 수 있다.
# instagram/forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['message']
widgets = {
'message': forms.Textarea(attrs={'rows': 3}),
}
instagram/templates/instagram/_post_card.html
card-footer에 있던 댓글 쓰기 버튼을 삭제하고 댓글 폼을 랜더한다.
이제 포스트 카드에서 직접 댓글을 작성할 수 있다.
<div class="card">
<div class="card-header">
...
</div>
<div class="card-body">
...
</div>
<div class="card-footer">
<form action="{% url 'instagram:comment_new' post.pk %}" method="POST">
{% csrf_token %}
{% bootstrap_form comment_form %}
{% buttons %}
<button type="submit" class="btn btn-primary btn-block">
댓글 쓰기
</button>
{% endbuttons %}
</form>
</div>
</div>
이제 포스트의 댓글을 포스트 카드에서 직접 작성할 수 있다.
폼을 꼭 단일 뷰에서 활용하기 보다는 위와 같이 유연하게 다른 뷰에서도 활용함으로써 UI/UX를 개선하였다.
아직 웹 문서 형태이기 때문에 새로고침해야만 추가된 댓글을 볼 수 있다. 대신 코멘트 뷰에서는 포스팅 성공 시 언제나 상세 페이지로 리디렉션 하기 때문에 댓글 작성 시 자연스럽게 새로고침이 된다.
다만 포스트 상세 페이지, 포스트 리스트 페이지에 상관 없이 언제나 상세 페이지로 리디렉션 되는 점은 이상할 수 있다. 다음 포스팅에서는 AJAX를 활용해서 바로 부분 업데이트가 되어 한번 더 UI/UX를 개선해 보겠다.