자바스크립트없이 포스팅 화면에서 댓글 쓰기 UI 개선

guava·2022년 1월 9일
0

파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 강의를 듣고 정리한 글입니다.

본 포스팅에서는 웹 문서 형태만(자바스크립트 없이)으로 댓글 쓰기 화면을 구성하고 개선해본다.
물론 웹문서이기 때문에 부분적으로 화면이 업데이트하지 않고 새로고침 되기에 UX가 깔끔하지는 않다.

그럼에도 다음과 같은 이유로 포스팅을 해보려고 한다.

  1. 장고 폼을 좀더 유연하게 활용해봄으로써 폼에 대한 이해도를 높인다.
  2. 폼을 잘 이해하면 추후 DRF의 시리얼라이즈를 이해하는데 도움이 된다.
  3. 자바스크립트의 활용이 없기에 백엔드(파이썬) 프로그래밍에 집중할 수 있다.

댓글쓰기 구현하기


우선 일반적인 MTV 형태로 댓글 쓰기를 구현해 보겠다.

Views

뷰의 형태는 일반적이다. 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'),
]

Models

코멘트 모델을 정의했다. 저자(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']

Forms

해당 폼의 관심사는 오직 message 필드이다. 나머지는 뷰에서 채운다.

# instagram/forms.py

class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ['message']

Templates

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. 포스트 카드는 포스트 상세페이지, 포스트 리스트 페이지에서 포스트를 랜더할 때 공통으로 사용한다.

Before

기존에는 포스트 카드에 댓글을 바로 쓰는게 아니라 댓글 쓰기 버튼이 있다.

포스트 카드에서 댓글 쓰기 버튼을 누르면 댓글 쓰기 폼으로 이동한다. 일반적인 UI/UX는 아니다.

After

다음과 같이 포스트 카드에 댓글 쓰기가 존재하도록 개선하면 좋겠다.

자바스크립트 없이 댓글 쓰기 UI 개선


Views

기존의 포스트 리스트 및 포스트 상세 페이지에 코멘트 폼을 함께 랜더하기 한다. 이를 위해 응답 뷰에 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,
    })

Forms

UI개선을 위해 widget 속성을 추가한다. 장고에서는 폼의 위젯 속성을 수정해서 HTML 위젯 속성을 부여할 수 있다.

# instagram/forms.py
class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ['message']
        widgets = {
            'message': forms.Textarea(attrs={'rows': 3}),
        }

Templates

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를 개선해 보겠다.

0개의 댓글