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

     

목차

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


▷ 검색 카테고리 적용

지난번에 적용한 검색을 보면 제목, 내용, 제목+내용으로 검색할 수 있는 카테고리기능이 있습니다. 기능은 적용하지 않았는데 이번에 적용해보도록 하겠습니다. 일단 views.py에서 넘어온 search_type을 읽어서 object.filter를 변경하면 됩니다. 적용할 때 살짝 애매모호한점이 있는데 바로 제목+내용 구현입니다. 필드 2개에서 OR조건으로 검색되야하기 때문에 특별한 기능을 사용해야 합니다.

#adminpage/view.py
from django.db.models import Q

def listfaqs(request):
    search_type = request.GET.get('search_type')
    search_keyword = request.GET.get('search_keyword')
    if search_keyword is None:
        search_keyword = ""
    if len(search_keyword) > 0:
        if search_type == '1':
            faq_list = Faq.objects.order_by('pk').filter(faq_question__contains=search_keyword)
        elif search_type == '2':
            faq_list = Faq.objects.order_by('pk').filter(faq_answer__contains=search_keyword)
        elif search_type == '3':
            faq_list = Faq.objects.order_by('pk').filter(Q(faq_question__contains=search_keyword)
                                                         | Q(faq_answer__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})

Q라는 메서드를 사용하기 위해 django.db.models에서 가져와서 사용합니다. Q(faq_question__contains=search_keyword) | Q(faq_answer__contains=search_keyword)가 or조건으로 filter를 사용한 코드입니다.

html도 약간 수정이 필요합니다. 이전에 선택한 카테고리가 유지되도록 설정해야 하거든요.

        <select id="search_type" name="search_type">
            <option value="1" {% if type == '1' %} selected {% endif %} >질문</option>
            <option value="2" {% if type == '2' %} selected {% endif %} >내용</option>
            <option value="3" {% if type == '3' %} selected {% endif %} >질문+내용</option>
        </select>

각각의 <option> 부분에 만약 넘어온 type값이 자신의 값이라면 selected 속성을 넣어줍니다. 이렇게 하면 페이지가 이동해도 카테고리가 유지됩니다.

▷ 메뉴바 만들기

이제 질문 데이터를 조회하고 수정하고 추가하는 기능을 얼추 완성되었다. 이제 또 다른 요구사항인 사용자 로그 조회하면을 만들 차례다. 지금까지 만들던 화면과 다른 화면이기 때문에 화면을 새로 만들어야 하는데, 화면을 이동하려면 메뉴바가 있으면 편할 것 같다. 일단 간단하게 메뉴바를 만들어보자.

{# 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;}
.create-faq { alignment: right;}
</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="menu_wrap">
    <ul>
        <li><a href="">menu1</a></li>
        <li><a href="">menu2</a></li>
        <li><a href="">menu3</a></li>
        <li><a href="">menu4</a></li>
    </ul>
</div>

<div class="content_wrap">
<form action="/list_faqs/" method="get">
    <fieldset>
        <label for="search_type">카테고리</label>
        <select id="search_type" name="search_type">
            <option value="1" {% if type == '1' %} selected {% endif %} >질문</option>
            <option value="2" {% if type == '2' %} selected {% endif %} >내용</option>
            <option value="3" {% if type == '3' %} selected {% endif %} >질문+내용</option>
        </select>
        <label>검색어 <input type="text" name="search_keyword" value="{{ keyword }}" /></label>
        <button type="submit">검색</button>
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
        <button id="button_3" class="create-faq btn btn-sm btn-primary" type="button" >질문답변 새로만들기</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&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>
</div>
</body>
<script type="text/javascript">
    $(function() {

        $(".create-faq").modalForm({formURL: "{% url 'create_faq' %}"})

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

    });
</script>

</html>

상단 메뉴 완성

일단은 현재 사용 중인 list_faqs.html 화면 상단에 메뉴바를 추가해봤다. CSS를 간단하게 만들고 href를 이용해 간단하게 메뉴 1~4까지 텍스트로 만들어 보았다. 뭐 아직 4개나 필요한지 모르겠지만 일단 만들었다. 아마 1번째는 리스트 보는 거고 2번째가 로그 보는 거겠지?

이 메뉴를 만들기 위한 코드가 꽤 많이 필요한데, 사용해야 하는 화면마다 이 코드를 추가하면 번거로울 것이다. 물론 복붙 하면 되겠지만 나중에 관리하기 힘드니까. 그럼 어떻게 한 곳에서 관리할 수 있을까요? 일단 메뉴 부분만 따로 html로 만듭니다.

{# template/adminpage/menu.html #}
<style type="text/css">
.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>
<div class="menu_wrap">
    <ul>
        <li><a href="">menu1</a></li>
        <li><a href="">menu2</a></li>
        <li><a href="">menu3</a></li>
        <li><a href="">menu4</a></li>
    </ul>
</div>

이제 이 html을 필요한 곳에서 호출하기만 하면 됩니다. 호출하는 방법은 {% include "adminpage/menu.html" %}을 이용하면 됩니다.

        </div>
    </div>

{% include "adminpage/menu.html" %}

<div class="content_wrap">
<form action="/list_faqs/" method="get">

요렇게 사이에 넣어주면 완성! 이제 메뉴를 누르면 어디로 이동할지 url만 정해주면 됩니다.

 

▷ 로그 페이지 만들기

이제 새로운 페이지를 만들어야 합니다. 로그페이지는 여태까지 우리가 사용하던 FAQ모델과는 다른 형태의 데이터이기 때문에 모델부터 새롭게 만들어야합니다.

# adminpage/models.py
from __future__ import unicode_literals
from datetime import datetime 
from django.db import models

# Create your models here.

class Faq(models.Model):
    faq_id = models.CharField(max_length=16, default='0000000000000001')
    faq_type = models.CharField(max_length=10, default='999')
    faq_question = models.TextField(default='질문')
    faq_answer = models.TextField(default='답변')

    def __str__(self):
        return self.faq_question


class Log(models.Model):
    log_date = models.DateTimeField(default=datetime.now())
    log_userid = models.CharField(max_length=60)
    log_question = models.TextField(default='질문')
    log_answer = models.TextField(default='답변')

새로운 모델은 로그 형태이기 때문에 DateTimeField가 들어갑니다. 그 외에는 뭐 특이사항은 없는 것 같네요. 그럼 로그 모델을 출력하는 views와 html을 만들어볼까요? Faq에 사용되는 것을 복붙 하면 금방 완성할 수 있습니다.

#adminpage/views.py
def listlogs(request):
    search_type = request.GET.get('search_type')
    search_keyword = request.GET.get('search_keyword')
    if search_keyword is None:
        search_keyword = ""

    if len(search_keyword) > 0:
        if search_type == '1':
            log_list = Log.objects.order_by('pk').filter(Log_question__contains=search_keyword)
        elif search_type == '2':
            log_list = Log.objects.order_by('pk').filter(Log_answer__contains=search_keyword)
        elif search_type == '3':
            log_list = Log.objects.order_by('pk').filter(Q(Log_question__contains=search_keyword)
                                                         | Q(Log_answer__contains=search_keyword))

    else:
        search_type = '1'
        search_keyword = ''
        log_list = Log.objects.all()

    paginator = Paginator(log_list, 5) # "5" 한페이지에서 보여줄 갯수를 정한다.
    page = request.GET.get('page')
    logs = paginator.get_page(page)
    return render(request, 'adminpage/list_logs.html', {'logs': logs, 'type': search_type, 'keyword': search_keyword})
urlpatterns = [
    .
    url(r'^list_logs/', view2.listlogs, name='list_logs'),
    .
]
{# template/adminpage/list_logs.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%;}
</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>

{% include "adminpage/menu.html" %}

<div class="content_wrap">
<form action="/list_logs/" method="get">
    <fieldset>
        <label for="search_type">카테고리</label>
        <select id="search_type" name="search_type">
            <option value="1" {% if type == '1' %} selected {% endif %} >질문</option>
            <option value="2" {% if type == '2' %} selected {% endif %} >내용</option>
            <option value="3" {% if type == '3' %} selected {% endif %} >질문+내용</option>
        </select>
        <label>검색어 <input type="text" name="search_keyword" value="{{ keyword }}" /></label>
        <button type="submit">검색</button>
    </fieldset>
</form>
    <table class = "tg">
    <colgroup>
        <col width="10%">
        <col width="10%">
        <col width="40%">
    </colgroup>
        <tr>
            <th class="tg-21xh"> 일시 </th>
            <th class="tg-21xh"> 사용자 </th>
            <th class="tg-21xh"> 질문 </th>
            <th class="tg-21xh"> 답변 </th>
        </tr>
        {%  for log in logs %}
        <tr>
            <td>{{ log.log_date }}</td>
            <td>{{ log.log_userid }}</td>
            <td>{{ log.log_question }}</td>
            <td>{{ log.log_answer }}</td>
        </tr>
        {% endfor %}
    </table>
    <div class="pagination">
        <span class="step-links">
            {% if logs.has_previous %}
                <a href="?page=1&search_type={{ type }}&search_keyword={{ keyword }}">&laquo; first</a>
                <a href="?page={{ logs.previous_page_number }}&search_type={{ type }}&search_keyword={{ keyword }}">previous</a>
            {% endif %}

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

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

</html>

 

순서대로 views.py, urls.py, list_logs.html을 추가하고, ~/list_logs/로 접속해봅시다.

 

잘보이기는 하는데...?

 

음 데이터가 없어서 그런지 뭔가 제대로 나오고 있는 건지 모르겠네요? ㅋㅋㅋ 일단 DB에 더미 데이터를 넣어봅시다.

 

더미데이터를 만들어서 넣어봅시다.


이젠 화면에 보이겠죠?

 

비율이 조금 불-편하지만 나중에 맞추면 되죠 ^^

이제 두 개의 화면이 나왔으니 메뉴바에 적용시켜 보죠!

 

{# template/adminpage/menu.html #}
<style type="text/css">
.menu_wrap {width: 100%; background: orange;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; font-size:20px; color: black; display: inline-block;}
.menu_wrap > ul > li > a:hover {text-decoration: none;background: none;}
</style>

<div class="menu_wrap">
    <ul>
        <li><a href="{% url 'list_faqs' %}" >FAQ 데이터</a></li>
        <li><a href="{% url 'list_logs' %}" >LOG 데이터</a></li>
        <li><a href="">menu3</a></li>
        <li><a href="">menu4</a></li>
    </ul>
</div>

이색이 좀 더 낳은듯.. ㅎ

가볍게 새로운 페이지를 완성했습니다. 이제 남은 건 통계 페이지인데요. 그래프를 사용할지 표를 사용할지 모르니 천천히 생각해 봐야겠습니다. ㅎㅎ

반응형

댓글

Designed by JB FACTORY