본문 바로가기

Python/Django

[Django] woojin's blog 프로젝트: 글/댓글 작성

블로그를 만드는 마지막 파트다. 사용자가 관리자모드에서가 아닌 직접 블로그 창에서 글과 댓글을 작성하는 기능을 구현해본다. 

일단 늘 페이지를 만들 때 하던 '글 작성 페이지'의 기본 구조 만들자. 순서는 View -> URLconf -> Template !! (순서 상관은 크게 없음)

[blog/views.py]

def post_add(request):
	return render(request, 'post_add.html')

[config/urls.py]

from blog.views import post_add		# 위에서 만든 veiws.py에 post_add 함수 추가
...

urlpatterns = [
	...
    path("posts/add/", post_add),
]

[templates/post_add.html]

{% load static %}
<!doctype html>
<html lang='en'>
<head>
	<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
	<div>
    	<h1>Post Add</h1>
    </div>
</body>
</html>

 

1. 사용자의 입력을 받는 Template

사용자의 입력을 받을 .../posts/add/ 페이지에 기본 구성을 만들어보자. 기본 구성을 만듬과 동시에 데이터도 전송해보자

 

1-1. HTML 형태 구현

[templates/post_add.html]

...
<body>
	<div>
    	<h1>Post Add</h1>
        <form method="GET">
        	<div>
            	<label>제목</label>
                <input name='title' type='text'>
            </div>
            <div>
            	<label>내용</label>
                <textarea name='cotent' cols='50' rows='10'></textarea>
            </div>
            <button type='submit'>작성</button>
        </form>
    </div>
</body>

input과 textarea에 각각 name='title', name='content'를ㄹ 추가하여 각각의 요소에 입력된 값이 어떤 이름으로 View에 전달될지 지정한다. <button>에 type='submit' 속성을 추가하여 이 버튼을 클릭 시 <form>의 데이터를 전송하도록 지정한다.

이렇게 실행을 해보면 URL로 값을 전달하여 검색을 수행할 수 있다. 하지만 우리가 하고자 하는 것은 단순한 조회가 아닌 DB에 새로운 객체를 생성하려고 하는 것이다. 이 과정을 URL을 통해 하는 것은 여러 가지 제약이 있으며 보안상으로도 좋지 않다. 이를 GET 메서드를 사용한 전송이라고 부르면 반면, URL을 통하지 않고 더 많은 데이터를 제약 없이 보내려면 POST 메서드를 사용해야 한다. POST 메서드를 사용하려면 위에서 이미 method="GET"으로 정의된 값을 "POST"로 바꾸면 된다. 이때 단순히 POST로 바꾸고 실행하면 Forbidden 오류가 뜬다. Forbidden 오류가 뜨는 이유는 GET보다 POST가 더 높은 보안 수준을 적용하기 때문인데 Django의 CSRF 공격 방어기법이 적용되기 때문이다. 설명에는 브라우저별로 구분되는 값으로 치환을 해줘야 한다고 나와있다. 정확하게 이해는 못해서 일단 그냥 넘어간다.

 

1-2. Forbidden 오류를 수정한 form

{% csrf_token %} 태그를 추가해주면 오류가 나지 않는다.

[templates/post_add.html]

<h1>Post Add</h1>
<form method="POST">
	{% csrf_token %}
    <div>

2. View에서 POST 요청 처리

2-1. POST데이터 다루기와 메서드에 따른 분기 처리

이제 요청에 전달된 데이터를 다루어보자

[blog/views.py]

def post_add(request):
	if request.method == "POST":	# method가 POST일 때
            title = request.POST['title']
            content = request.POST["content"]

    return render(request, "post_add.html")

기본은 method가 GET일 때 실행이 되기 때문에 POST로 불러올 때는 이렇게 지정을 해줘야 한다. 그리고 template에서 지정해줬던 name의 변수를 title과 content에 변수 할당을 해준다.

 

2-2. POST 데이터를 사용한 DB row 생성

가져온 데이터를 사용해 DB에 새 row를 생성해야한다. 이때 objects.create 메서드를 사용하면 된다. shell에서 먼저 연습해보자

>>> from blog.models import Post
# Admin에서 생성한 글 목록이 출력된다.
>>> Post.objects.all()
# 새 객체를 만든다.
>>> post = Post.objects.create(title="ShellTtile", content="ShellContent")
# 모든 Post를 생성 순서의 역순으로 가져온다.
>>> Post.objects.order_by('-id')

이렇게 인터프리터에서 만들어도 관리자 페이지에서 확인할 수 있다.

 

이제 view에서 객체를 생성해보자.

[blog/views.py]

def post_add(request):
	if request.method == "POST":
    	title = request.POST['title']
        content = request.POST['content']
        Post.objects.create(
        	title=title,
            content=content,
        )
     return render(request, "post_add.html")

2-3. 글 작성 완료 후 이동하기

지금까지 사용했던 render 함수는 인수로 전달된 html 파일을 브라우저로 보여주는 역할을 한다. 우리가 이번에 추가해야 할 기능은 글을 작성했을 때 작성한 글의 상세페이지로 이동해야 한다. 이때 사용되는 함수는 redirect 이다.

[blog/views.py]

from django.shortcuts import redirect	# shortcuts 패키지 안에 redirect 함수를 사용한다.

def post_add(request):
    if request.method == 'POST':   
        title = request.POST['title']
        content = request.POST['content']
        thumbnail = request.FILES['thumbnail']
        post = Post.objects.create(
            title=title,
            content=content,
            thumbnail=thumbnail,
        )
        return redirect(f"/posts/{post.id}/")	# 글 작성을 완료하고 해당 글의 상세페이지로 이동한다.
    return render(request, "post_add.html")

3. CSS 적용 및 링크 생성

이제 template에 css까지 적용해보자.

[templates/post_add.html]

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href = "{% static 'css/style.css' %}">
</head>
<body>
    <div id="navbar">		# 상단에 id='navabr' css 적용
            <span>글 작성</span>
    </div>
    <div id="post-add">	# 기존에 있던 form 형태에 id='post-add'을 적용
        <form method="POST" enctype="multipart/form-data">
            {% csrf_token %}
            <div>
                <label>제목</label>
                <input name='title' type="text">
            </div>
            <div>
                <label>내용</label>
                <textarea name="content" cols="50" rows="10"></textarea>
            </div>
            <button type="submit" class="btn btn-primary">작성</button>	# 작성 버튼에 'btn btn-primary' 적용
        </form>
    </div>
</body>
</html>

위에 css를 적용했을 때 posts/add/ 페이지 모습

그리고 기존에는 posts/add/를 url창에 직접 쳐서 들어왔다면 이제 posts/ 창에서 바로 들어올 수 있게 링크를 연결해 주자.

[templates/post_list]

...
<body>
    <div id="navbar">
        <span>Woojin's blog</span>
        <a href="/posts/add/" class="btn btn-primary">글 작성</a>
    </div>
    ...

확인을 해보면 우측 상단에 글작성 링크가 들어가있는 것을 볼 수 있다. (posts/add/ 에 있는 작성 class와 같은 class를 썼기 때문에 같은 css가 적용되었다.)

4. 댓글 작성

이제 페이지에서 직접 댓글을 달 수 있는 기능을 만들어보자. 우선 ORM 에서 먼저 확인을 해보자

>>> from blog.models import Comment
# 이전에 생성했던 댓글들을 전부 지워준다.
>>> Comment.objects.all().delete()
# 글을 작성했을 때와 같이 content 속성을 사용해 댓글을 만들어보
>>> Comment.objects.create(content='SampleContent')

위와 같이 시도해보면 django.db.utils.IntegrityError 오류가 발생한다. 왜냐하면 Comment 모델은 Post 모델과 ForeignKey 관계로 연결되어 있기 때문이다.

>>> from blog.models import Post
# 가장 처음으로 생성된 post를 가져온다
>>> post = Post.objects.first()
# 가장 처음으로 생성된 post와 연결되는 comment를 생성한다.
>>> Comment.objects.create(post=post, content ='SampleComment')

이렇게 Comment를 생성하려면 어떤 Post와 연결될지를 반드시 지정해주어야 한다.

samplecomment가 작성된 걸 볼 수 있다.

이제 댓글을 브라우저에서 바로 달 수 있게 창을 만들어줘야한다. 각각의 상세페이지안에 만들어야 하므로 post_detail html을 만져준다.

[templates/post_detail.html]

...
<form method='POST'>
	{% csrf_token %}
    <textarea name='comment'></textarea>
    <button type='submit' class='btn btn-primary'>댓글 작성</button>
</form>
...

이제 view에서 comment를 생성할 차례이다.

[blog/views.py]

...

def post_detail(request, post_id):
	post = Post.objects.get(id=post_id)
    if request.method == 'POST':
    	comment_content = request.POST['comment']
        Comment.objects.create(
        	post=post,
            content=comment_content,
     	)
     context = {
     	"post":post,
     }
     return render(request, 'post_detail.html', context)

5. 글 작성 시 이미지 업로드

Post 모델에는 썸네일을 다루는 이미지 필드가 있다. 텍스트와 다르게 이미지와 같은 파일을 form 으로 전달받으려면 Template과 View에서 별도의 처리가 필요하다.

[templates/post_add.html]

...
<div id = 'post_add'>
	<form method='POST' enctype="multipart/form-data"> # 데이터를 서버로 전송할 때 어떤 인코딩 유형을 사용할 것인지
    	...
        <div>
        	<label>썸네일</label>
            <input name='thumbnail'  type='file'>
        </div>
        <button type="submit" class="btn btn-primary">작성</button>
    </form>

전송된 파일은 veiws에 request.POST가 아닌 request.FILES에서 가져와야 한다.

[blog/views.py]

def post_add(request):
    if request.method == 'POST':    # method가 POST일 때
        title = request.POST['title']
        content = request.POST['content']
        thumbnail = request.FILES['thumbnail']
        post = Post.objects.create(
            title=title,
            content=content,
            thumbnail=thumbnail,
        )
        return redirect(f"/posts/{post.id}/")
    return render(request, "post_add.html")

여기까지 글과 댓글을 직접 브라우저에서 만들 수 있는 기능을 만들어보았다. 잘 실행되는지도 확인해보자!!

글을 작성해보자
블로그 글 리스트 페이지이다. 잘 만들어진 것을 확인할 수 있다.
상세페이지에서 댓글을 작성해봤다.

댓글까지 잘 달리는 것을 확인할 수 있었다. 직접 나만의 블로그를 만드니 뿌듯하다 ㅎㅎ 나중에 좀 더 수정해서 괜찮은 블로그도 만들어봐야겠다