웹알못 dJango로 홈페이지 만들기(관리자페이지) [2]

     

 


목차

2019/05/16 - [Study/python] - 웹알못 dJango로 홈페이지 만들기(관리자페이지) [1]

- 개발환경, 장고설치, 첫화면 만들기, 수정/삭제기능 만들기

2019/05/16 - [Study/python] - 웹알못 dJango로 홈페이지 만들기(관리자페이지) [2]

- 페이징 처리, 검색 기능, 추가 기능

2019/05/17 - [Study/python] - 웹알못 dJango로 홈페이지 만들기(관리자페이지) [3]

- 메뉴바, 로그 페이지 추가

2019/05/20 - [Study/python] - 웹알못 dJango로 홈페이지 만들기(관리자페이지) [4]

- 일별, 월별 데이터 뽑기, 기간 필터링, 차트그리기

git - https://github.com/tkdlek11112/simple_dashboard_python


▷ 페이징 처리

페이징 처리란 게시판을 보면 아래 << < 1 2 3 4 5 ... 이런 식으로 게시글을 분할해서 볼 수 있게 해주는 기능입니다. 현재 만든 페이지는 DB에 있는 모든 데이터를 for문 돌아서 다 보여주는 형태로 되어있어서 스크롤 압박이 매우 심합니다 ㅋㅋ 한 10개나 20개로 나눠서 보여주려고 하는데 dJango에서 페이징은 어떻게 처리하는지 제가 한번 해보도록 하겠습니다.(비장)

일단 참고는 dJangoprocject공식 홈페이지에 있는 튜토리얼 입니다. 아래 링크 참조

https://docs.djangoproject.com/en/2.2/topics/pagination/

[

Pagination | Django documentation | Django

Django The web framework for perfectionists with deadlines.

docs.djangoproject.com

](https://docs.djangoproject.com/en/2.2/topics/pagination/)

기존에 있는 html과 구분되기 위해 새로 html을 만들었습니다. 일단 index.html을 복사해서 list_faqs.html을 만들고 urls에 추가!

urlpatterns = [    
...    
url(r'^list_faqs/', view2.listfaqs, name='list_faqs'),    
...
]

위에 urldptj views2.listfaqs를 보면 아시겠지만 adminpage/views.py에 listfaqs를 추가해주어야 합니다. 이전에 전체 리스트를 보여주었을 때는 views에서 Faq.objects.all()을 사용했었는데, 여기에서는 몇 개만 보여줘야 하기 때문에 코드가 살짝 다릅니다.

# adminpage/views.py
from __future__ import unicode_literals
from .models import Faq
from .forms import FaqForm
from django.urls import reverse_lazy
from django.core.paginator import Paginator
from bootstrap_modal_forms.generic import BSModalCreateView, BSModalUpdateView, BSModalDeleteView
from django.shortcuts import render


def index(request):
    faqs = Faq.objects.all()
    context = {'faqs' : faqs }
    return render(request, 'adminpage/index.html', context)


def listfaqs(request):
    faq_list = Faq.objects.all()
    paginator = Paginator(faq_list, 5) # "5" 한페이지에서 보여줄 갯수를 정한다.
    page = request.GET.get('page')
    faqs = paginator.get_page(page)
    return render(request, 'adminpage/list_faqs.html', {'faqs': faqs})

차이가 보이시나요? 일단 dJango에서 기본으로 제공하는 Paginator를 이용해서 한페이지에 몇 개의 객체를 보여줄지 정합니다. 그다음 지금 보여줄 페이지가 몇 번째 페이지인지 정해야 하는데, request.GET.get('page')를 통해 현재 페이지를 받아옵니다. 이 코드의 의미는 이제 adminpage/list_faqs.html을 호출할 때는 GET방식으로 페이지 번호를 던져줘야 한다는 의미입니다. 아마 list_faqs.html?page=1 이런 식으로 호출해야 되겠죠?

마지막으로 paginator.get_page(page)를 통해서 page객체를 반환합니다. paginator와 page객체에 대한 정의를 보시려면 아래 링크로~!

https://docs.djangoproject.com/en/2.2/topics/pagination/

[

Pagination | Django documentation | Django

Django The web framework for perfectionists with deadlines.

docs.djangoproject.com

](https://docs.djangoproject.com/en/2.2/topics/pagination/)

문서를 보는 습관을 길러야합니다. ㅎ_ㅎ

반환받은 page객체를 render를 통해 list_faqs.html로 보내면 views.py는 완성입니다. 이제 넘겨받은 page객체를 html에서 어떻게 사용하는지 html을 만들어보도록 하죠.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="/static/jquery-3.2.1.min.js"></script>
    <link rel="stylesheet" href="/static/bootstrap.css">
</head>
<body>
<style type="text/css">
.tg  {border-collapse:collapse;border-spacing:0;}
.tg td{font-family:Arial, sans-serif;font-size:14px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:black;}
.tg th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:black;}
.tg .tg-21xh{font-weight:bold;background-color:#34cdf9;color:#333333;border-color:inherit;text-align:left;vertical-align:top}
.tg .tg-0pky{border-color:inherit;text-align:left;vertical-align:top}
    .content_wrap {width: 80%; margin: 0 auto;}

</style>

    <script type="text/javascript" src="/static/bootstrap.js"></script>
    <script type="text/javascript" src="/static/jquery.bootstrap.modal.forms.js"></script>
    <div class="modal fade" tabindex="-1" role="dialog" id="modal">
        <div class="modal-dialog" role="document">
            <div class="modal-content">

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

<div class="content_wrap">
    <table class = "tg">
    <colgroup>
        <col width="10%">
        <col width="5%">
        <col width="40%" span="2">
        <col>
    </colgroup>
        <tr>
            <th class="tg-21xh"> 코드 </th>
            <th class="tg-21xh"> 타입 </th>
            <th class="tg-21xh"> 질문 </th>
            <th class="tg-21xh"> 답변 </th>
            <th class="tg-21xh"> 작업 </th>
        </tr>
        {%  for faq in faqs %}
        <tr>
            <td>{{ faq.faq_id }}</td>
            <td>{{ faq.faq_type }}</td>
            <td>{{ faq.faq_question }}</td>
            <td>{{ faq.faq_answer }}</td>
            <td><button id="button_1" class="update-faq btn btn-sm btn-primary" data-id="{% url 'update_faq' faq.pk %}">수정</button>
                <button id="button_2" class="delete-faq btn btn-sm btn-primary" data-id="{% url 'delete_faq' faq.pk %}">삭제</button>
                </td>
        </tr>
        {% endfor %}
    </table>
    <div class="pagination">
        <span class="step-links">
            {% if faqs.has_previous %}
                <a href="?page=1">&laquo; first</a>
                <a href="?page={{ faqs.previous_page_number }}">previous</a>
            {% endif %}

            <span class="current">
                Page {{ faqs.number }} of {{ faqs.paginator.num_pages }}.
            </span>

            {% if faqs.has_next %}
                <a href="?page={{ faqs.next_page_number }}">next</a>
                <a href="?page={{ faqs.paginator.num_pages }}">last &raquo;</a>
            {% endif %}
        </span>
    </div>
</div>
</body>
<script type="text/javascript">
    $(function() {


        $(".update-faq").each(function () {
            $(this).modalForm({formURL: $(this).data('id')});
        });
        $(".delete-faq").each(function () {
            $(this).modalForm({formURL: $(this).data('id')});
        });

    });
</script>

</html>

기존에 index.html소스에서 아래에 pagination 소스만 추가되었습니다. 왜냐하면 index.html의 역할은 넘어온 Faq객체를 전부 뿌려주는 거였는데, 우리는 views.py에서 Faq객체를 5개만 뿌려주기로 정해서 넘겼기 때문에 전부를 출력해도 5개만 나오게 되는 거죠. 따라서 출력 부분은 따로 수정할 필요가 없습니다. 대신 아래쪽에 페이지를 표시하는 부분은 추가를 해야 되겠죠?

html에 python코드 부분을 보시면 {% if faqs.has_previous %}부분과 {% if faqs.has_next %} 부분을 확인할 수 있습니다. faqs란 views.py에서 넘겨받은 page객체를 뜻합니다. page객체 안에는 has_previous와 has_next 메서드가 있어서 지금 보여주는 페이지의 이전, 다음 페이지가 존재하는지를 알려줍니다. 만약 존재하면 이전 페이지로 돌아가기와 다음 페이지로 넘기기 버튼을 만들어주면 되는 거죠. 이제 페이지를 실행해보면 아래와 같이 페이징 처리가 되는 것을 볼 수 있습니다.

페이지처리 완료!

버튼이 맨 앞, 이전, 다음, 맨뒤만 있긴 한데.. 중간에 1 2 3 4 5 6 같은 숫자를 하려면 앞뒤로 페이지가 몇 개씩 있는지 계산을 해서 보여줘야 합니다. 이 부분은 나중에 시간 나면 ^^..

▷ 검색 기능

사실 이 데이터 전체를 훑어 볼일은 딱히 없을 것 같아요. 아마 검색을 해서 원하는 데이터를 찾아서 수정하거나 삭제하는 일이 많겠죠? 그래서 검색 기능을 넣어보겠습니다. 일단 이론적으로는, 검색을 하면 검색어로 Faq객체 내에서 검색 결과에 합당하는 객체들만 반환해서 html로 넘기면 알아서 뿌려주게 될 거라고(?) 제 뇌가 말하고 있네요. ㅋㅋㅋ

일단 검색할 수 있는 UI를 그려보겠습니다.

{# template/adminpage/list_faqs.html #}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="/static/jquery-3.2.1.min.js"></script>
    <link rel="stylesheet" href="/static/bootstrap.css">
</head>
<body>
<style type="text/css">
.tg  {border-collapse:collapse;border-spacing:0;}
.tg td{font-family:Arial, sans-serif;font-size:14px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:black;}
.tg th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:black;}
.tg .tg-21xh{font-weight:bold;background-color:#34cdf9;color:#333333;border-color:inherit;text-align:left;vertical-align:top}
.tg .tg-0pky{border-color:inherit;text-align:left;vertical-align:top}
    .content_wrap {width: 80%; margin: 0 auto;}
.pagination  {text-align: center; width: 100%;}
    .menu_wrap {width: 100%; background: yellow;margin-bottom: 30px;}
    .menu_wrap > ul {list-style: none;width: 80%;margin: 0 auto;padding: 0;}
    .menu_wrap > ul > li {display: inline-block;}
    .menu_wrap > ul > li > a {padding: 10px; color: red; display: inline-block;}
    .menu_wrap > ul > li > a:hover {text-decoration: none;background: none;}
</style>

    <script type="text/javascript" src="/static/bootstrap.js"></script>
    <script type="text/javascript" src="/static/jquery.bootstrap.modal.forms.js"></script>
    <div class="modal fade" tabindex="-1" role="dialog" id="modal">
        <div class="modal-dialog" role="document">
            <div class="modal-content">

            </div>
        </div>
    </div>
<div class="content_wrap">
<form action="" method="post">
    <fieldset>
        <label for="search_type">카테고리</label>
        <select id="search_type" name="search_type">
            <option value="1">질문</option>
            <option value="2">내용</option>
            <option value="3">질문+내용</option>
        </select>
        <label>검색어 <input type="text" name="search_keyword"/></label>
        <button type="submit">검색</button>
    </fieldset>
</form>
    <table class = "tg">
    <colgroup>
        <col width="10%">
        <col width="5%">
        <col width="40%" span="2">
        <col>
    </colgroup>
        <tr>
            <th class="tg-21xh"> 코드 </th>
            <th class="tg-21xh"> 타입 </th>
            <th class="tg-21xh"> 질문 </th>
            <th class="tg-21xh"> 답변 </th>
            <th class="tg-21xh"> 작업 </th>
        </tr>
        {%  for faq in faqs %}
        <tr>
            <td>{{ faq.faq_id }}</td>
            <td>{{ faq.faq_type }}</td>
            <td>{{ faq.faq_question }}</td>
            <td>{{ faq.faq_answer }}</td>
            <td><button id="button_1" class="update-faq btn btn-sm btn-primary" data-id="{% url 'update_faq' faq.pk %}">수정</button>
                <button id="button_2" class="delete-faq btn btn-sm btn-primary" data-id="{% url 'delete_faq' faq.pk %}">삭제</button>
                </td>
        </tr>
        {% endfor %}
    </table>
    <div class="pagination">
        <span class="step-links">
            {% if faqs.has_previous %}
                <a href="?page=1">&laquo; first</a>
                <a href="?page={{ faqs.previous_page_number }}">previous</a>
            {% endif %}

            <span class="current">
                Page {{ faqs.number }} of {{ faqs.paginator.num_pages }}.
            </span>

            {% if faqs.has_next %}
                <a href="?page={{ faqs.next_page_number }}">next</a>
                <a href="?page={{ faqs.paginator.num_pages }}">last &raquo;</a>
            {% endif %}
        </span>
    </div>
</div>
</body>
<script type="text/javascript">
    $(function() {


        $(".update-faq").each(function () {
            $(this).modalForm({formURL: $(this).data('id')});
        });
        $(".delete-faq").each(function () {
            $(this).modalForm({formURL: $(this).data('id')});
        });

    });
</script>

</html>

검색창!

검색 form을 추가하고 화면을 가운데 정렬로 깔끔하게 바꿔줍니다. from에서 action=""부분에 호출할 URL을 적으면 from 안에 name으로 정의되어있는 값들이 post로 날아갑니다. 여기에는 name = "search_type" 이랑 name = "search_keyword"가 있기 때문에 이 데이터가 action=""에 적힌 url로 post형태로 날아가게 됩니다. 한번 action="list_faqs.html"을 해보고 버튼을 눌러봅시다.

404!

404가 뜨네요?! 로그를 보니 urls.py에 정의되어있는 대로 입력해야 하나 봅니다. 그럼 우리가 가고 싶은 곳은 list_faqs이기 때문에 action="/list_faqs/"로 바꿔줍니다.
그럼 이제 정상적으로 list_faqs.html화면으로 넘어오는 것을 알 수 있습니다. 이제 views.py에서 POST로 검색값이 들어왔을때 어떻게 할지 처리하면 되겠죠?

# adminpage/views.py
from __future__ import unicode_literals
from .models import Faq
from .forms import FaqForm
from django.urls import reverse_lazy
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from bootstrap_modal_forms.generic import BSModalCreateView, BSModalUpdateView, BSModalDeleteView
from django.shortcuts import render


def index(request):
    faqs = Faq.objects.all()
    context = {'faqs' : faqs }
    return render(request, 'adminpage/index.html', context)


def listfaqs(request):

    search_type = request.POST['search_type']
    search_keyword = request.POST['search_keyword']

    print('search_type = ' + search_type + ', search_keyword = ' + search_keyword)

    faq_list = Faq.objects.all()
    paginator = Paginator(faq_list, 5) # "5" 한페이지에서 보여줄 갯수를 정한다.
    page = request.GET.get('page')
    faqs = paginator.get_page(page)
    return render(request, 'adminpage/list_faqs.html', {'faqs': faqs})

일단 로그를 한번 찍어봅시다. POST로 search_type이랑 search_keyword가 잘 넘어오는지 views.py에 print를 추가합니다.

검색어에 안녕이라고 넣어보자.
로그가 딱~ 나옵니다.

POST로 값이 잘 넘어오는것을 확인할 수 있습니다. 이제 search_typesearch_keyword를 이용해서 필터를 만들어봅시다. 그전에 다시 한번 해당 url로 접속하면 에러가 나오는 것을 볼 수 있습니다. 그 이유는 현재 list_faqs.html을 접근할 때 검색버튼으로 접근하면 POST방식으로 데이터를 날리고, 그냥 조회할경우나 페이징처리로 들어갈경우 GET방식으로 호출하기 때문입니다. 하나로 통일해야할텐데 조회니까 그냥 GET방식을 쓰면 되겠죠?

#adminpage/views.py
def listfaqs(request):
    search_type = request.GET.get('search_type')
    search_keyword = request.GET.get('search_keyword')
    if len(search_keyword) > 0:
        faq_list = Faq.objects.order_by('pk').filter(faq_question__contains=search_keyword)
    else:
        search_type = '1'
        search_keyword = ''
        faq_list = Faq.objects.all()

    paginator = Paginator(faq_list, 5) # "5" 한페이지에서 보여줄 갯수를 정한다.
    page = request.GET.get('page')
    faqs = paginator.get_page(page)
    return render(request, 'adminpage/list_faqs.html', {'faqs': faqs, 'type': search_type, 'keyword': search_keyword})

검색이 잘 되는것처럼 보인다

자 이제 검색을 누르면 잘 나오는것을 확인할 수 있습니다. 하지만 검색 후 다음 버튼을 누르면? 전체 조회에 다음 페이지가 나오게 됩니다. 페이징 처리에서 검색어의 값이 없기 때문인데요. html 파일도 조금 수정해야 합니다.

{# templates/adminpage/list_faqs.html #}
    <div class="pagination">
        <span class="step-links">
            {% if faqs.has_previous %}
                <a href="?page=1&search_type={{ type }}&search_keyword={{ keyword }}">&laquo; first</a>
                <a href="?page={{ faqs.previous_page_number }}&search_type={{ type }}&search_keyword={{ keyword }}">previous</a>
            {% endif %}

            <span class="current">
                Page {{ faqs.number }} of {{ faqs.paginator.num_pages }}.
            </span>

            {% if faqs.has_next %}
                <a href="?page={{ faqs.next_page_number }}&search_type={{ type }}&search_keyword={{ keyword }}">next</a>
                <a href="?page={{ faqs.paginator.num_pages }}&search_type={{ type }}&search_keyword={{ keyword }}">last &raquo;</a>
            {% endif %}
        </span>
    </div>

위처럼 페이지 링크에 search_type={{ type }}search_keyword={{ keyword }}를 추가해줍니다. 이러면 페이징 처리에서 검색어를 계속 들고 가기 때문에 연속적인 검색이 됩니다. 이렇게만 하면 사용자가 자기가 뭘 검색했는지 알 방법이 없으니 검색창에도 텍스트를 계속 남겨주도록 아래 코드를 추가합니다.

<label>검색어 <input type="text" name="search_keyword" value="{{ keyword }}" /></label>

속성에 value="{{keword}}"를 추가해서 검색창에 넘어온 검색어를 유지시키도록 합니다. 이제 어느 정도 검색도 되고 페이징도 되는 관리자 페이지가 완성되었습니다!!

 

검색까지 완성!!

 

 추가 기능

현재 수정과 삭제만 있는데 추가기능도 넣어봅시다. 사실 객체추가코드는 이미 만들어놨기 때문에 버튼과 html만 만들어주면 됩니다. views.py에서 createView를 만들었던거 기억하시죠? create_faq.html이랑 버튼만 만들어서 연결시키면 쉽게 추가 기능을 만들 수 있습니다.

 

{# template/adminpage/create_faq.html #}

<form method="post" action="">
    {% csrf_token %}

    <div class="modal-header">
        <h5 class="modal-title">Create new Faq</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">&times;</span>
        </button>
    </div>

    <div class="modal-body">
        {%  for field in form %}
            <div class="form-group{%  if field.errors %} invalid{%  endif %}">

                <label for="{{  field.id_for_label }}"> {{  field.label }}</label>
                {% render_field field class="form-control" placeholder=field.label %}
                {% for error in field.errors %}
                    <p class="help-block">{{  error }}</p>
                {% endfor %}
            </div>
        {% endfor %}
    </div>

    <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
        <button type="button" class="submit-btn btn btn-primary">Create</button>
    </div>

</form>

버튼은 아래 코드를 원하는 위치에 넣고 function부분에 create-faq 버튼을 눌렀을때 url을 적어주시면 됩니다.

<button id="button_3" class="create-faq btn btn-sm btn-primary" type="button" >생성</button>

$(".create-faq").modalForm({formURL: "{% url 'create_faq' %}"}) //function에 추가

마지막으로 urls.py에 추가도 잊지 마시구요.

# urls.py
urlpatterns = [
	.
    url(r'^create/', view2.FaqCreateView.as_view(), name='create_faq'),
	.
]

추가 기능 완료!

 

페이징, 검색, 추가 완료!

반응형

댓글

Designed by JB FACTORY