달나라 노트

Python django project 1 - 게시판 만들기 ch.10 : 게시글 목록 만들기 본문

Python django/Python django project 1

Python django project 1 - 게시판 만들기 ch.10 : 게시글 목록 만들기

CosmosProject 2020. 12. 20. 23:27
728x90
반응형

 

 

이번에는 게시글 목록을 보여주는 화면을 만들어보겠습니다.

 

마찬가지로 MTV의 순서대로 코딩을 해나가겠습니다.

 

먼저 Model입니다.

우리는 지금까지 user app의 model만 다뤄왔을 뿐 게시글에 대한 model은 만들지 안았죠.

 

따라서 게시글 model부터 만들어봅시다.

 

일단 게시글 model에 필요한 내용이 뭔지를 생각해야합니다.

1. 게시글 제목

2. 게시글 내용

3. 게시글 작성자

4. 게시글 등록일

일단 간단하게 위 네 가지가 되겠네요.

 

그러면 위 내용을 포함하는 model을 작성해봅시다.

pro/app/post/models.py를 아래처럼 수정해봅시다.

 

from django.db import models

# Create your models here.

class ModelPost(models.Model):
    post_title = models.CharField(max_length=128, verbose_name='post_title')
    post_contents = models.TextField(verbose_name='post_contents')
    post_writer = models.ForeignKey('user.ModelUser', on_delete=models.CASCADE, verbose_name='post_writer')
    registered_dttm = models.DateTimeField(auto_now_add=True, verbose_name='registered_dttm')
    
    class Meta:
        db_table = 'post'
        verbose_name = 'Model_post'
        verbose_name_plural = 'Model_post'
    
    def __str__(self):
        return self.post_title

기본적으로 user app의 model과 비슷합니다.

 

from django.db import models

# Create your models here.

class ModelPost(models.Model):
	...
    post_contents = models.TextField(verbose_name='post_contents')
    post_writer = models.ForeignKey('user.ModelUser', on_delete=models.CASCADE, verbose_name='post_writer')
    ...

하지만 위 두 부분을 보면 새로운 두 가지가 있습니다.

TextField는 post_contents라는 컬럼에 들어갈 데이터가 Text라는 뜻이며 최대 개수 제한이 없는 것이 특징입니다.

ForeignKey는 해당 컬럼에 들어갈 데이터가 다른 모델의 데이터라는 뜻입니다. 여기서는 user app의 ModelUser라는 model의 데이터가 post_writer와 연결될거란 뜻이죠.

이렇게 한 이유는 게시글 작성 시 작성자 정보를 입력할 때 해당 작성자의 model 데이터를 그대로 넣을 것이기 때문입니다.

그리고 on_delete는 이렇게 연결된 model의 데이터가 삭제되었을 때 어떻게 할건지를 나타내는 것입니다. CASCADE옵션을 사용하였습니다. 이것은 연결된 모델 데이터가 삭제되면 연결된 게시글도 삭제된다는 옵션입니다.

예를들어 A유저가 게시글을 하나 작성하고 회원탈퇴를 하여 user app model에서 A유저 정보가 삭제되었으면 A유저가 작성한 게시글도 삭제되는 것이죠.

 

 

 

그리고 post app model을 admin page에 등록하기 위해 pro/app/post/admin.py도 아래처럼 수정해줍시다.

from django.contrib import admin
from . import models as postapp_model

# Register your models here.

class AdminModelPost(admin.ModelAdmin):
    list_display = ('id', 'post_title', 'post_writer', 'registered_dttm')

admin.site.register(postapp_model.ModelPost, AdminModelPost)

(user app admin.py와 비슷합니다.)

 

 

 

또한 이렇게 model의 변화가 있으면 반드시 migration을 진행해줘야 합니다.

pro % python manage.py makemigrations
pro % python manage.py migrate

마찬가지로 위 멍령어를 입력해서 migration을 진행해줍시다.

(migration시에는 runserver 상태가 아니어야합니다. 만약 서버 실행상태라면 ctrl + c를 눌러 서버를 종료한 후 migration을 진행해줍시다.)

 

 

 

 

어드민 페이지를 보면 post model이 잘 등록되었네요.

 

 

 

 

 

게시글 목록을 위한 MTV에서 M이 완료되었습니다.

 

이제 Template을 만들어봅시다.

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

{% 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-12">
        <table class="table table-light">
            <thead class="thead-light">
                <tr>
                    <th>Post key</th>
                    <th>Title</th>
                    <th>Writer</th>
                    <th>Registered at</th>
                </tr>
            </thead>
            <tbody class="text-dark">
                <tr>
                    <th></th>
                    <th></th>
                    <td></td>
                    <td></td>
                </tr>
            </tbody>
        </table>
    </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 Register</button>
    </div>
</div>

{% endblock %}

먼저 게시글이 표현될 부분은 table tag를 사용하여 나타내었습니다.

표의 컬럼 네임이 될 부분을 thead 부분에 표시하였고, tbody 부분에 게시글 목록이 표시될텐데 이 부분은 일단 비워뒀습니다.

 

그리고 아래쪽에 Home으로 이동할 버튼과 게시글을 등록하기 위한 Post Register 버튼을 만들어놨습니다.

Post Register 관련 기능은 아직 만들지 않았으므로 onclick의 href를 비워뒀습니다.

 

 

 

 

template이 일차적으로 완성되었으니 마지막 view를 작성해봅시다.

 

pro/app/post/views.py를 아래처럼 작성합시다.

from django.shortcuts import render, redirect
from django.core.paginator import Paginator
from . import models as postapp_model

# Create your views here.

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

    if not login_userkey:
        return redirect('/user/user_login/')
    else:
        modelpost = postapp_model.ModelPost.objects.all().order_by('-id')
        page = int(request.GET.get('p', 1))

        pagination = Paginator(modelpost, 5)
        show_page = pagination.get_page(page)
        
        dict_render = {
            'show_page': show_page
        }

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

위 코드를 하나하나 봐봅시다.

 

 

from django.shortcuts import render, redirect
from django.core.paginator import Paginator
from . import models as postapp_model

# Create your views here.

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

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

일단 위 부분은 session으로부터 login_userkey라는 key에 저장된 value를 가져오고있습니다.

그리고 이 값이 없다면(if not login_userkey) login url로 redirect를 해주죠.

 

이 이유는 보통 게시글을 열람할 때 로그인되지 않은 사용자는 게시글을 보지 못하게 하기 위함입니다.

그래서 로그인 정보가 session에 없을 때에는 login 페이지로 돌려보내는 것이죠.

 

 

from django.shortcuts import render, redirect
from django.core.paginator import Paginator
from . import models as postapp_model

# Create your views here.

def view_post_list(request):
	...
    else:
        modelpost = postapp_model.ModelPost.objects.all().order_by('-id')
        page = int(request.GET.get('p', 1))

        pagination = Paginator(modelpost, 5)
        show_page = pagination.get_page(page)
        
        dict_render = {
            'show_page': show_page
        }

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

만약 로그인이 되어있으면 위 코드블록을 실행하게 됩니다.

 

여기서 paging 개념을 배워야 합니다.

등록된 총 게시글이 10,000개가 있다면 이 10,000개의 게시글을 한 화면에 다 보여주나요?

아니죠, 한 화면에는 일정 개수의 게시글을 보여주며, 여러 페이지가 있다는 정보를 사용자에게 보여줄겁니다.

이것을 위한 기능을 django에서는 Paginator라는 것으로써 제공하고 있습니다.

 

from django.shortcuts import render, redirect
from django.core.paginator import Paginator
from . import models as postapp_model

# Create your views here.

def view_post_list(request):
	...
    else:
        modelpost = postapp_model.ModelPost.objects.all().order_by('-id')
	...

게시글 목록을 나타내려면 먼저 게시글 모델로부터 게시글 데이터를 불러와야겠죠.

위 내용은 post app model로부터 모든 obejcts(objects.all())를 불러와서 primary key(id) 기준으로 내림차순 정렬한 정보를 modelpost라는 변수에 저장하는 내용입니다.

 

 

from django.shortcuts import render, redirect
from django.core.paginator import Paginator
from . import models as postapp_model

# Create your views here.

def view_post_list(request):
	...
    else:
        ...
        page = int(request.GET.get('p', 1))
	...

위 부분은 게시글 리스트에 보여줄 페이지 번호를 얻어오는 부분입니다.

사용자가 보길 원하는 게시글 목록의 페이지가 1페이지일 수도 있고, 2페이지일 수도 있습니다.

이러한 페이지 정보를 우리는 GET방식으로 전달할 것입니다.

 

127.0.0.1:8000/post/post_list/?p=1

예를들어 위와 같은 주소라면 1페이지를 보여주게 되는것이죠.

이렇게 GET 방식으로 정보를 전달할 땐 url의 맨 뒤에 ?를 쓴 후 key=value의 형태로 전달하게 됩니다.

 

그리고 이렇게 GET 방식으로 전달된 정보를 조회할 땐

사용자의 요청(request)에 전달된 get(GET) 방식에 존재하는 key('p')를 조회하여 그 값을 불러오게됩니다.

request.GET, request.POST, request.session 모두 마치 python의 dictionary처럼 사용할 수 있습니다.

따라서 여기서도 get('p', 1)처럼 get 함수를 사용하여 사용자가 요청한 page를 가져오되 이 정보가 없으면 기본으로 보여줄 1페이지를 return 하라는 뜻입니다.

 

from django.shortcuts import render, redirect
from django.core.paginator import Paginator
from . import models as postapp_model

# Create your views here.

def view_post_list(request):
	...
    else:
	...
        pagination = Paginator(modelpost, 5)
	...

이제 django의 Paginator를 이용해봅시다.

인자로는 모든 post 정보를 담은 modelpost와 한 페이지에 보여줄 post 개수(위 예시에선 5개)를 명시해줍니다.

여기까지 쉽게 이해하자면 paginator는 모든 게시글을 5개씩 나눠서 저장하게됩니다.

 

 

 

from django.shortcuts import render, redirect
from django.core.paginator import Paginator
from . import models as postapp_model

# Create your views here.

def view_post_list(request):
	...
    else:
    	...
        page = int(request.GET.get('p', 1))

    	...
        show_page = pagination.get_page(page)
    	...

그리고 pagination에서 get_page를 이용해서 내가 보여줄 페이지 정보를 가져와서 show_page에 저장합니다.

 

 

from django.shortcuts import render, redirect
from django.core.paginator import Paginator
from . import models as postapp_model

# Create your views here.

def view_post_list(request):
	...
    else:
	...
        dict_render = {
            'show_page': show_page
        }

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

마찬가지로 dictionary의 형태로 show_page를 template에 전달합니다.

 

그러면 이게 무슨 의미가 있을까요?

show_page에는 다음과 같은 정보가 담겨있습니다.

 

- 현재 페이지에 보여줄 5개의 post 정보

- show_page.has_previous = 이전 페이지가 있는지 없는지 여부

- show_page.has_next = 다음 페이지가 있는지 없는지 여부

- show_page.previous_page_number = 이전 페이지 번호

- show_page.number = 현재 페이지 번호

- show_page.next_page_number = 다음 페이지 번호

- show_page.paginator.num_pages = 총 페이지 개수

 

paginator의 위력은 위와같이 paging에 필요한 정보를 다 만들어준다는 것이죠.

 

 

이제 만들어진 page 정보를 가지고 template을 마저 완성해봅시다.

{% 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-12">
        <table class="table table-light">
            <thead class="thead-light">
                <tr>
                    <th>Post key</th>
                    <th>Title</th>
                    <th>Writer</th>
                    <th>Registered at</th>
                </tr>
            </thead>
            <tbody class="text-dark">
                {% for field in show_page %}
                <tr>
                    <th>{{ field.id }}</th>
                    <th>{{ field.post_title }}</th>
                    <td>{{ field.post_writer }}</td>
                    <td>{{ field.registered_dttm }}</td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
</div>

<div class="row mt-5">
     <div class="col-12">
         <nav class="pagination justify-content-center">

             {% if show_page.has_previous %}
             <li class="page-item">
                 <a class="page-link" href="?p={{ show_page.previous_page_number }}">Previous</a>
             </li>
             {% else %}
             <li class="page-item disabled">
                 <a class="page-link" href="">Previous</a>
             </li>
             {% endif %}

             <li class="page-item active">
                 <a class="page-link" href="">{{ show_page.number }} / {{ show_page.paginator.num_pages }}</a>
             </li>

             {% if show_page.has_next %}
             <li class="page-item">
                 <a class="page-link" href="?p={{ show_page.next_page_number }}">Next</a>
             </li>
             {% else %}
             <li class="page-item disabled">
                 <a class="page-link" href="">Next</a>
             </li>
             {% endif %}

         </nav>
     </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 Register</button>
    </div>
</div>

{% endblock %}

 

 

 

 

 

 

{% 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-12">
        <table class="table table-light">
            <thead class="thead-light">
                <tr>
                    <th>Post key</th>
                    <th>Title</th>
                    <th>Writer</th>
                    <th>Registered at</th>
                </tr>
            </thead>
            <tbody class="text-dark">
                {% for field in show_page %}
                <tr>
                    <th>{{ field.id }}</th>
                    <th>{{ field.post_title }}</th>
                    <td>{{ field.post_writer }}</td>
                    <td>{{ field.registered_dttm }}</td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
</div>

...

여기를 보면 tbody부분에 show_page 객체를 반복문을 돌려 정보를 출력하고 있습니다.

show_page는 기본적으로 post app model 정보를 담고있기 때문에 model의 컬럼 이름을 이용해서 정보를 추출할 수 있습니다.

 

또한 {%%} 기호 안에 if문 뿐 아니라 반복문도 쓸 수 있습니다.

 

 

 

{% 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-12">
         <nav class="pagination justify-content-center">

             {% if show_page.has_previous %}
             <li class="page-item">
                 <a class="page-link" href="?p={{ show_page.previous_page_number }}">Previous</a>
             </li>
             {% else %}
             <li class="page-item disabled">
                 <a class="page-link" href="">Previous</a>
             </li>
             {% endif %}

             <li class="page-item active">
                 <a class="page-link" href="">{{ show_page.number }} / {{ show_page.paginator.num_pages }}</a>
             </li>

             {% if show_page.has_next %}
             <li class="page-item">
                 <a class="page-link" href="?p={{ show_page.next_page_number }}">Next</a>
             </li>
             {% else %}
             <li class="page-item disabled">
                 <a class="page-link" href="">Next</a>
             </li>
             {% endif %}

         </nav>
     </div>
</div>

...

{% endblock %}

그리고 위처럼 paging 정보를  위처럼 표현하였습니다.

각 페이지가 표시되고 이를 a태그안에 표시되게 하였으며, href로 연결되는 링크를 GET방식으로(?p=페이지번호 의 형식으로) 나타내었습니다.

 

- tip.

li 태그에 disabled가 된 것 볼 수 있는데 이 속성을 넣어두면 li 태그에 있는 a태그가 클릭할 수 없는 상태가 됩니다.

 

 

이제 url을 연결해줍시다.

pro/pro/urls.py를 아래처럼 수정해줍시다.

from django.contrib import admin
from django.urls import path, include
from app.user import views as userapp_view

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', userapp_view.home),
    path('user/', include('app.user.urls')),
    path('post/', include('app.post.urls'))
]

 

 

 

그리고 pro/app/post 디렉토리에 urls.py를 만든 후 아래처럼 작성합시다.

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)
]

 

 

 

 

또한 post list 화면으로 이동하기 위해 pro/app/user/templates/home.html에 post list 버튼을 만들어줍시다.

{% extends 'base.html' %}

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


{% if request.session.login_userkey %}
<div class="row mt-5">
    <div class="col-12">
        <h4>Login user key : {{ request.session.login_userkey }}</h4>
        <h6>Login user name : {{ request.session.login_username }}</h6>
    </div>
</div>
{% else %}
<div class="row mt-5">
    <div class="col-12">
        <h4>Not Logined</h4>
    </div>
</div>
{% endif %}


{% if request.session.login_userkey %}
<div class="row mt-5">
    <div class="col-12">
        <button type="button" class="btn btn-primary btn-block" onclick="location.href='/user/user_logout/'">Logout</button>
    </div>
</div>
<div class="row mt-3">
    <div class="col-12">
        <button type="button" class="btn btn-primary btn-block" onclick="location.href='/post/post_list/'">Post List</button>
    </div>
</div>
{% else %}
<div class="row mt-5">
    <div class="col-6">
        <button type="button" class="btn btn-primary btn-block" onclick="location.href='/user/user_register/'">Register</button>
    </div>
    <div class="col-6">
        <button type="button" class="btn btn-primary btn-block" onclick="location.href='/user/user_login/'">Login</button>
    </div>
</div>
<div class="row mt-3">
    <div class="col-12">
        <button type="button" class="btn btn-primary btn-block" onclick="location.href='/post/post_list/'">Post List</button>
    </div>
</div>
{% endif %}

{% endblock %}

 

 

 

 

 

여기까지 하고 home화면으로 이동해보면

이렇게 Post List 버튼이 생성되었고, 이걸 누르면,

 

 

 

 

이렇게 Post List화면이 보이는 것을 알 수 있습니다.

아직 게시글이 하나도 없어서 게시글 목록이 안보이긴 하지만요.

 

 

 

 

다음에는 게시글 등록 기능을 만들어 게시글을 추가해봅시다.

 

 

 

728x90
반응형
Comments