달나라 노트

Python django project 1 - 게시판 만들기 ch.11 : 게시글 등록 기능 만들기 본문

Python django/Python django project 1

Python django project 1 - 게시판 만들기 ch.11 : 게시글 등록 기능 만들기

CosmosProject 2020. 12. 21. 00:55
728x90
반응형

 

 

이번엔 게시글 등록 기능을 만들어봅시다.

 

MTV 순서대로 코딩할 것이며 이번엔 좀 새로운 form이라는 것을 이용할 것입니다.

 

일단 Model부터 체크해보죠.

게시글 등록을 위해선 post app model과 게시글 작성자를 위한 user app model이 필요합니다.

우리는 이미 이 두 모델을 다 만들어놨죠.

 

 

 

그러면 이제 Template을 봅시다.

pro/app/post/templates 디렉토리에 post_register.html을 만들고 아래처럼 작성합니다.

{% extends 'base.html' %}

{% block body %}
<div class="row mt-5">
    <div class="col-12 text-center">
        <h1>Post Register</h1>
    </div>
</div>

<div class="row mt-5">
    <div class="col-12">
        <form method="POST" action=".">
            {% csrf_token %}

            <div class="form-group">
                <label for=""></label>
                <input id="" class="form-control" type="" name="" placeholder=""/>
            </div>

            <div class="row mt-5">
                <div class="col-6">
                    <button type="button" class="btn btn-primary btn-block" onclick="location.href='/post/post_list'">Post List</button>
                </div>
                <div class="col-6">
                    <button type="submit" class="btn btn-primary btn-block">Submit</button>
                </div>
            </div>
        </form>
    </div>
</div>
{% endblock %}

근데 뭔가 좀 이상합니다.

form 태그에 있는 label, input 태그들의 속성이 비어있습니다.

이 이유는 View 작성시에 form이라는 것을 이용해보려고 하기 때문인데 일단 template은 위처럼 작성해두고 view로 넘어갑시다.

 

 

 

form을 이용해본다고했죠.

이것은 로그인, 회원가입, 게시글 등록 등 사용자로부터 어떤 값을 입력받아 이 값을 모델에 저장하는 등의 기능에 사용할 수 있습니다.

이때 위 기능을 구현하기 위해 form을 이용하면 좀 더 짧은 코드로 동일한 기능을 구현할 수 있기 때문에 유용한 기능입니다.

 

pro/app/post 디렉토리에 forms.py를 만들고 아래처럼 작성합시다.

from django import forms
from . import models as postapp_model

class FormPostRegister(forms.Form):
    post_title = forms.CharField(
        error_messages={
            'required': 'Title is required.'
        },
        max_length=128, label='Title'
    )
    post_contents = forms.CharField(
        error_messages={
            'required': 'Content is required.'
        },
        widget=forms.Textarea, label='Contents'
    )

forms.py의 존재의미는 결국 사용자로부터 입력값을 받기 위함입니다.

게시글 작성 시에 입력받을 값이 뭐가 있나 생각해보면 제목과 내용입니다.

현재 작성자가 누군지는 session에 로그인 정보를 가져오면 되니까 필요없죠.

 

그래서 위 내용을 보면 post_title은 게시글 제목을, post_contents는 게시글 내용을 받아오게 됩니다.

그리고 각각에 대한 내용이 있습니다.

 

from django import forms
from . import models as postapp_model

class FormPostRegister(forms.Form):
    post_title = forms.CharField(
        error_messages={
            'required': 'Title is required.'
        },
        max_length=128, label='Title'
    )
    ...

post_title부터 봅시다.

forms.CharField -> 일단 post_title을 model에서 정의했을 때 CharField로 지정했으니 동일하게 CharField로 지정해줍니다.

error_messages -> 이 부분은 에러메세지를 추가해주는 것인데 required라는 key에 에러 메세지를 설정해줬습니다. 즉, 이 필드가 채워지지 않고 전송되었을 때 이 필드를 채워야한다고 에러메세지를 띄워주게되는데 이때 띄워줄 메세지를 설정하는 것입니다.

max_length=128 -> post app model에서 post_title을 정의할 때 max_length=128로 설정했는데 동일하게 설정해줍니다.

label='Title' -> 이것은 template에 label 태그에 표시해줄 label 텍스트를 설정해주는 것입니다.

 

 

from django import forms
from . import models as postapp_model

class FormPostRegister(forms.Form):
	...
    post_contents = forms.CharField(
        error_messages={
            'required': 'Content is required.'
        },
        widget=forms.Textarea, label='Contents'
    )

post_contents 필드를 봅시다.

대부분 post_title과 동일한데 widget이 있습니다.

widget은 입력받을 값의 type을 의미합니다.

post_contents를 model에서 정의했을 때 이 필드는 TextField로 정의했습니다. 따라서 이 필드는 일반적인 CharField가 아닌 TextField라는 것을 명시해주기 위해서 해당 정보를 widget이라는 속성에 표시해줍니다.

참고로 widget=forms.PasswordInput을 넣게되면 해당 필드의 type이 password가 되며 이는 사용자가 비밀번호를 입력할 때 입력값이 아닌 암호화된 문자(.)가 보이게 됩니다.(이건 다음 장에서 다시 살펴봅시다.)

 

 

 

위처럼 forms.py를 다 작성했으니 pro/app/post/views.py로 돌아옵시다.

view_post_register 함수를 추가합시다.

from django.shortcuts import render, redirect
from django.core.paginator import Paginator
from . import models as postapp_model
from . import forms as postapp_form
from app.user import models as userapp_model

# Create your views here.

...

def view_post_register(request):
    login_userkey = request.session.get('login_userkey', None)

    if not login_userkey:
        return redirect('/user/user_login/')
    else:
        if request.method == 'GET':
            formpostregister = postapp_form.FormPostRegister()

            dict_render = {
                'formpostregister': formpostregister
            }

            return render(request, 'post_register.html', dict_render)

        elif request.method == 'POST':
            formpostregister = postapp_form.FormPostRegister(request.POST)

            if formpostregister.is_valid():
                post_title = formpostregister.cleaned_data.get('post_title', None)
                post_contents = formpostregister.cleaned_data.get('post_contents', None)

                if not(post_title and post_contents):
                    pass
                else:
                    modelpost = postapp_model.ModelPost()
                    modelpost.post_title = post_title
                    modelpost.post_contents = post_contents
                    modelpost.post_writer = userapp_model.ModelUser.objects.get(pk=login_userkey)
                    modelpost.save()

                    return redirect('/post/post_list/')

            dict_render = {
                'formpostregister': formpostregister
            }

            return render(request, 'post_register.html', dict_render)

 

 

 

마찬가지로 부분적으로 코드를 나눠봅시다.

from django.shortcuts import render, redirect
from django.core.paginator import Paginator
from . import models as postapp_model
from . import forms as postapp_form
from app.user import models as userapp_model

# Create your views here.

...

def view_post_register(request):
    login_userkey = request.session.get('login_userkey', None)

    if not login_userkey:
        return redirect('/user/user_login/')
    else:
        ...

마찬가지로 로그인 유저 정보를 session에서 가져온 후 로그인되어있지 않으면 login 화면으로 redirect를 해줍니다.

 

 

 

from django.shortcuts import render, redirect
from django.core.paginator import Paginator
from . import models as postapp_model
from . import forms as postapp_form
from app.user import models as userapp_model

# Create your views here.

def view_post_register(request):
    ...
    else:
        if request.method == 'GET':
            formpostregister = postapp_form.FormPostRegister()

            dict_render = {
                'formpostregister': formpostregister
            }

            return render(request, 'post_register.html', dict_render)
     ...

만약 로그인이 되어있다면 사용자의 요청을 검토합니다.

만약 GET방식으로 post register 화면이 요청되었다면 사용자가 별도로 값을 입력하지 않고 그냥 post register 화면에 접속했단 얘기입니다.(왜냐면 값을 입력했을 때에는 POST 방식으로 요청되도록 template에 있는 form 태그의 method를 POST로 설정해놨기 때문이죠.)

이런 경우 그냥 FormPostRegister class를 받아 render 함수의 인자로 담아서 return해줍니다.

그러면 forms.py에서 제공한 형식대로 화면이 보이겠죠.

 

 

 

 

 

 

 

from django.shortcuts import render, redirect
from django.core.paginator import Paginator
from . import models as postapp_model
from . import forms as postapp_form
from app.user import models as userapp_model

# Create your views here.

...
def view_post_register(request):
    login_userkey = request.session.get('login_userkey', None)

    if not login_userkey:
        return redirect('/user/user_login/')
    else:
        ...       
        elif request.method == 'POST':
            formpostregister = postapp_form.FormPostRegister(request.POST)
            
            if formpostregister.is_valid():
                post_title = formpostregister.cleaned_data.get('post_title', None)
                post_contents = formpostregister.cleaned_data.get('post_contents', None)

                if not(post_title and post_contents):
                    pass
            ...

만약 request method가 POST라면 사용자가 뭔가 값을 입력한 후 제출했다는 뜻입니다.

이 경우 입력값들이 request의 POST에 저장되어 전송됩니다.

따라서 FormPostRegister class를 가져오는데 이때 사용자가 입력한 값(request.POST)을 인자로서 전달한 후 FormPostRegister를 불러옵니다.

 

그러면 FormPostRegister는 입력받은 값을 forms.py에 정의된 필드에 넣은 후 여러 테스트를 할겁니다.

값은 정해둔대로 다 입력이 되었는지, max_length 내에서 입력되었는지 등등이요.

누락된 값이 있다면 그대로 게시글 저장을 하면 안되겠죠?

그래서 if formpostregister.is_valid()라는 것이 존재합니다.

간단하게 말하면 is_valid는 값 테스트(대표적으로 누락된 값 테스트)를 한 후 누락값이 있으면 False 효과를 내어 is_valid 속 코드블럭을 실행하지 않습니다.

 

그리고 누락값 없이 is_valid를 통과한 입력된 값들은 cleaned_data라는 key에 저장되어있으며 이 또한 dictionary처럼 이용할 수 있기 때문에 get 함수를 사용하여 값들을 불러왔습니다.

이때 get 함수에 post_title, post_contents라는 값을 key로서 명시했는데 이것은 forms.py에서 정의한 각 값들의 field명입니다.

 

그리고 이 값들 중 하나도 존재하지 않으면 어떠한 행동도 하지 않고 pass로 넘겨버립니다.

그 이유는 값이 누락되었을 경우 forms.py에서 정의한 누락 시의 에러메세지를 post_register.html에 그대로 띄워주기 위함이죠.

방금 request.POST값을 받아 사용자의 입력값을 forms.py에서 테스트한다고 했는데 이 때 누락값도 테스트합니다.

값이 누락되었을 경우엔 forms.py가 자동으로 인식하여 forms.py에서 설정했던 누락 error messge를 FormPostRegister에 담아주기 때문에 views.py에선 별도로 누락 에러메세지를 설정할 필요가 없습니다.

 

 

from django.shortcuts import render, redirect
from django.core.paginator import Paginator
from . import models as postapp_model
from . import forms as postapp_form
from app.user import models as userapp_model

# Create your views here.

def view_post_register(request):
    login_userkey = request.session.get('login_userkey', None)

    if not login_userkey:
        return redirect('/user/user_login/')
    else:
        ...
              else:
                  modelpost = postapp_model.ModelPost()
                  modelpost.post_title = post_title
                  modelpost.post_contents = post_contents
                  modelpost.post_writer = userapp_model.ModelUser.objects.get(pk=login_userkey)
                  modelpost.save()

                  return redirect('/post/post_list/')
        ...

그리고 값이 모두 입력되었으면,

 

modelpost = postapp_model.ModelPost() -> post app model을 불러와서

 

modelpost.post_title = post_title
modelpost.post_contents = post_contents
modelpost.post_writer = userapp_model.ModelUser.objects.get(pk=login_userkey)

->model의 각 필드에 값을 저장합니다. 이때 post_writer는 user app model과 Foreign key로 연결되어있으므로 단순히 작성자 이름 등의 어떤 값 하나가 아닌 해당 작성자의 model object 자체를 넣어줍니다.

 

modelpost.save() -> 그리고 model을 저장한 후

 

return redirect('/post/post_list/') -> 게시글 화면으로 redirect 해주는것이죠.

 

 

 

 

 

 

이제 template을 보완해봅시다.

post_register.html 파일을 아래처럼 수정합시다.

{% extends 'base.html' %}

{% block body %}
<div class="row mt-5">
    <div class="col-12 text-center">
        <h1>Post Register</h1>
    </div>
</div>

<div class="row mt-5">
    <div class="col-12">
        <form method="POST" action=".">
            {% csrf_token %}

            {% for field in formpostregister %}

            {% if field.name == 'post_contents' %}
            <div class="form-group">
                <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                <textarea id="{{ field.id_for_label }}" class="form-control" type="{{ field.field.widget.input_type }}" name="{{ field.name }}" placeholder="{{ field.label }}"></textarea>
            </div>
            {% else %}
            <div class="form-group">
                <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                <input id="{{ field.id_for_label }}" class="form-control" type="{{ field.field.widget.input_type }}" name="{{ field.name }}" placeholder="{{ field.label }}"/>
            </div>
            {% endif %}

            <span style="color: orange">{{ field.errors }}</span>
            {% endfor %}

            <div class="row mt-5">
                <div class="col-6">
                    <button type="button" class="btn btn-primary btn-block" onclick="location.href='/post/post_list'">Post List</button>
                </div>
                <div class="col-6">
                    <button type="submit" class="btn btn-primary btn-block">Submit</button>
                </div>
            </div>
        </form>
    </div>
</div>
{% endblock %}

formpostregister는 views.py에서 넘어온 것입니다.

이것은 forms.py의 내용을 담고있는데 forms.py에서 정의된 FormPostReigster class는 post_title, post_contents 2개의 필드를 가지고있었죠.

따라서 반복문을 통해 각각의 field 정보를 태그에 넣어 html 파일을 완성하려는 것입니다.

 

{% extends 'base.html' %}

{% block body %}
<div class="row mt-5">
    <div class="col-12 text-center">
        <h1>Post Register</h1>
    </div>
</div>

<div class="row mt-5">
    <div class="col-12">
        <form method="POST" action=".">
            {% csrf_token %}

            {% for field in formpostregister %}

            {% if field.name == 'post_contents' %}
            <div class="form-group">
                <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                <textarea id="{{ field.id_for_label }}" class="form-control" type="{{ field.field.widget.input_type }}" name="{{ field.name }}" placeholder="{{ field.label }}"></textarea>
            </div>
            {% else %}
            <div class="form-group">
                <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                <input id="{{ field.id_for_label }}" class="form-control" type="{{ field.field.widget.input_type }}" name="{{ field.name }}" placeholder="{{ field.label }}"/>
            </div>
            {% endif %}

            ...
            {% endfor %}

            ...
        </form>
    </div>
</div>
{% endblock %}

먼저 post_contents field의 type은 무엇이었나요? Textarea(=TextField)였죠.

이는 글자 수 제한이 없는 Field라고 했습니다.

이렇게 글자 수 제한이 없는 Field는 단순한 input 태그보다 textarea 태그를 사용하여 입력창을 넓게 만들어주는게 좋습니다.

따라서 for loop 속 if 문을 적용하여 field.name이 post_contents인 경우 textarea 태그가 나오도록 하였습니다.

그리고 나머지는 그냥 input 태그가 적용되도록 했죠.

 

근데 반복문 속 label, textarea, input 태그들ㄹ의 속성값들을 보면 뭐 이상한 것들이 많습니다.

이것들이 각각 무엇을 의미하는지 forms.py의 내용과 대조해봅시다.

class FormPostRegister(forms.Form):
    post_title = forms.CharField(
        error_messages={
            'required': 'Title is required.'
        },
        max_length=128, label='Title'
    )
    post_contents = forms.CharField(
        error_messages={
            'required': 'Content is required.'
        },
        widget=forms.Textarea, label='Contents'
    )

위 코드는 forms.py의 내용을 다시 가져온것입니다.

{{ field.id_for_label }} = 이것은 forms.py에서 정의한 field name에 'id_'라는 접두어가 붙은 값입니다. 이 예시에선 id_post_title, id_post_contents 두 가지가 되겠네요.

{{ field.label }} = forms.py에서 정의한 각 field의 label 값을 의미합니다. Title, Contents 두 값이 되겠네요.

{{ field.field.widget.input_type }} = forms.py에서 정의한 widget값을 불러옵니다. post_title의 경우 설정하지 않았으니 CharField의 기본값인 text일 것이고, post_contents field는 Textarea가 되겠네요.

{{ field.name }} = forms.py에서 정의한 field name입니다. post_title, post_contents 두 가지가 되겠네요.

{{ field.errors }} = forms.py에서 정의한 error_messages입니다. 여기선 required라는 key에 연결된 'Title is required.' 또는 'Content is required.'가 존재합니다.

 

 

 

 

이제 pro/app/post/urls.py에 posr register url을 연결해줍시다.

from django.contrib import admin
from django.urls import path, include
from . import views as postapp_view

urlpatterns = [
    path('post_list/', postapp_view.view_post_list),
    path('post_register/', postapp_view.view_post_register)
]

 

 

 

 

그리고 post_list.html의 post register 버튼에 post register url을 연결해줍시다.

{% extends 'base.html' %}

{% block body %}
<div class="row mt-5">
    <div class="col-12 text-center">
        <h1>Post list</h1>
    </div>
</div>

...

<div class="row mt-5">
    <div class="col-6">
        <button type="button" class="btn btn-primary btn-block" onclick="location.href='/'">Home</button>
    </div>
    <div class="col-6">
        <button type="button" class="btn btn-primary btn-block" onclick="location.href='/post/post_register/'">Post Register</button>
    </div>
</div>

{% endblock %}

 

 

 

 

여기까지 하고 post list 화면에서 post register 버튼을 누르면

위처럼 post register 화면이 잘 뜨는 것을 알 수 있습니다.

 

 

 

 

값을 입력하지 않고 Submit 버튼을 누르면 에러메세지도 다 표시되네요.

 

 

 

 

 

값을 다 잘 입력한 후 Submit을 하면 위처럼 Post list 화면으로 돌아와지고 게시글 목록이 생겼네요.

 

 

 

 

 

 

 

 

한 번 게시글을 여러 개 등록해서 pagination이 제대로 되는지도 봐봅시다.

이전 챕터에서 한 화면에 보여줄 게시글 개수를 5개로 제한해놔서 게시글이 5개가 넘어가면 전체 페이지가 2로 표시되는 것을 알 수 있습니다.

전 총 7개의 게시글을 등록했고, 이 게시글이 Post key(=id =Primary key)의 역순으로 정렬되며 Next버튼이 활성화되었음을 알 수 있죠.

 

 

그리고 Next를 누르면 2페이지로 이동이 됩니다. (이 때의 url = http://127.0.0.1:8000/post/post_list/?p=2)

 

 

 

728x90
반응형
Comments