본문 바로가기

프로젝트형 IoT 서비스 개발 4회차/인터페이스 개발 프로젝트 - 음식점 리뷰 사이트

2. 화면 구성 - (6) 고객센터 - 고객 상담 게시판

728x90

[1] 개요

  1. URL

    1) 리스트 화면 : HOST/board

    2) 등록 화면 : HOST/board/iv

    3) 상세 화면 : HOST/board/g/<int:pk>

    4) 수정 화면 : HOST/board/uv/<int:pk>

  2. 구성

    1) 리스트 화면

      - 게시글을 한 페이지에 5개씩 출력

      - 로그인 상태에서만 등록 버튼 출력

    2) 등록 화면

      - 제목, 내용만 입력하여 등록

    3) 상세 화면

      - 제목, 글번호, 글쓴이, 작성일, 내용을 출력

      - 로그인된 ID로 작성한 글만 수정/삭제 버튼 출력

    4) 수정 화면

      - 기존의 제목, 내용이 출력된 상태에서 수정

[2] CODE

  1. 리스트 화면

    1) HTML

      - ajax로 데이터 5개 가져와서 리스트로 출력

      - board table에 있는 레코드 수에 따라 페이지 번호 버튼도 생성

/templates/boardlist.html

<div class="board_wrap">
    <div class="board_title">
        <strong>고객 상담 </strong>
        <p>답변을 빠르고 정확하게 안내해드립니다.</p>
    </div>
    <div class="board_list_wrap">
        <div id="board_list" class="board_list">
        <!--ajax로 리스트 뿌리는 곳-->
        </div>
        <div class="board_page">
            <a href="#" class="bt first" onclick="getFirst();"><<</a>
            <a href="#" class="bt prev" onclick="getPrevious(idx, page);"><</a>
            <div id="page_list" style="display:inline;">
                <!--Page 번호 뿌려지는 곳-->
            </div>
            <a href="#" class="bt next" onclick="getNext(idx, page);">></a>
            <a href="#" class="bt last" onclick="getLast();">>></a>
        </div>
        {% if request.session.sessionid != none %}
        <div class="bt_wrap">
            <a href="/board/iv" class="on">등록</a>
            <!--<a href="#">수정</a>-->
        </div>
        {% endif %}
    </div>
</div>

    2) CSS

      - /static/css/style.css + media.css

    3) JavaScript

      - 페이지 생성을 위한 변수와 함수들로 구성

      ① getData(i, p) : 테이블의 인덱스 번호와 현재 페이지를 받아서 ajax를 통해 데이터를 가져오는 함수

      ② display(data) : 가져온 데이터를 출력해 주는 함수

      ③ getFirst, getPrevious, getNext, getLast : 첫페이지, 이전페이지, 다음페이지, 마지막 페이지 출력을 위한 함수

      ④ getPage() : 각 페이지 버튼 클릭 시 해당 페이지로 이동

      ⑤ makePageButton() : 테이블 전체 데이터 수에 맞게 페이지 버튼 생성

      ⑥ currentPage() : 페이지 버튼에 현재 페이지 표시

/templates/boardlist.html -> <script>태그

        var idx = 0;                        // 데이터 뿌리는 시작 인덱스(0이면 제일 최신 데이터) (변동)
        var page = 1;                           // 화면 뜨면 1페이지로 setting (변동)
        var cnt = {{count}};                // 게시판 데이터 갯수 (고정)
        if (cnt % 5 == 0){
            var pages = parseInt(cnt/5);    // 게시판 페이지 수 계산 (고정)
            var remain = 5;                 // 게시판 마지막 페이지 데이터 갯수 (고정)
        } else {
            var pages = parseInt(cnt/5) + 1;
            var remain = cnt % 5;
        }

        // 데이터 출력
       function display(data){
            results = ''
            results = '<div class="top">';
            results += '<div class="num">번호</div>';
            results += '<div class="title">제목</div>';
            results += '<div class="writer">글쓴이</div>';
            results += '<div class="date">작성일</div>';
            results += '</div>';
            $(data).each(function(index, item){
                var result = '';
                result += '<div>';
                result += '<div class="num">' + item.id + '</div>';
                result += '<div class="title"><a href="/board/g/'+item.id+'">' + item.title + '</a></div>';
                result += '<div class="writer">' + item.cust_id + '</div>';
                result += '<div class="date">' + item.regdate + '</div>';
                result += '</div>'
                results += result;
            $('#board_list').html(results);
            });
        }

        // 해당하는 index와 page 정보로 데이터 가져오기
        function getData(i, p){
            if (p == pages){
                var getcnt = remain;
            } else {
                var getcnt = 5
            }
            // alert('page'+page+' / '+'pages'+pages);
            $.ajax({
                url: '/board/listview/'+i+'/'+getcnt,
                success: function(data){
                    display(data);
                }
            });
        }

        // 첫 페이지
        function getFirst(){
            if (idx == 0){
                alert('첫 페이지입니다.');
                return;
            }
            idx = 0;
            page = 1;
            getData(idx, page);
            currentPage();
        }

        // 이전 페이지
        function getPrevious(i, p){
            if (i == 0){
                alert('첫 페이지입니다.');
                return;
            }
            idx = i - 5;
            page = p - 1;
            getData(idx, page);
            currentPage();
        }

        // 다음 페이지
        function getNext(i, p){
            if (p == pages){
                alert('마지막 페이지입니다.');
                return;
            }
            idx = i + 5;
            page = p + 1;
            getData(idx, page);
            currentPage();
        }

        // 마지막 페이지
        function getLast(){
            if (page == pages){
                alert('마지막 페이지입니다.');
                return;
            }
            idx = pages * 5 - 5     // 마지막 페이지 시작 인덱스 계산
            page = pages;
            getData(idx, page);
            currentPage();
        }
        // 각 페이지 버튼 클릭 시 해당 페이지로 이동
        function getPage(i){
            idx = i * 5 - 5         // 해당페이지 시작 인덱스 계산
            page = i;
            getData(idx, page);
            currentPage();
        }

        // 페이지 수에 맞게 페이지 버튼 생성
        function makePageButton(){
            results = '';
            for (var i=1; i<pages+1; i++){
                result = '';
                result += '<a href="#" id="num' + i + '" onclick="getPage(' + i + ');" class="num">'
                result += i;
                result += '</a>';
                results += result;
            }
            $('#page_list').html(results);
        }

        // 페이지 버튼에 현재 페이지 표시
        function currentPage(){
            for (var i=1; i<pages+1; i++){
                var id = '#num' + i;
                if (i == page){
                    $(id).addClass('on');
                } else {
                    $(id).removeClass('on')
                }
            }
        }

        $(document).ready(function(){
            getData(idx, page);
            makePageButton();
            currentPage();
        });

    4) Application

      ① board() : boardlist.html 을 테이블 레코드 수와 함께 보내주는 함수

      ② listview(idx, getcnt)

        - ajax로 요청 받는 함수

        - 테이블 인덱스와 가져올 데이터 수를 받아서 테이블에서 해당 레코드들을 불러옴

        - 불러온 데이터를 json데이터로 변환하여 ajax에게 보내줌

/web/views_board.py -> class BoardView(View)

@request_mapping("/", method="get")
def board(self, request):
    count = Board.objects.all().count()
    context = {
        'count': count
    }
    return render(request, 'boardlist.html', context)

@request_mapping("/listview/<int:idx>/<int:getcnt>", method="get")
def listview(self, request, idx, getcnt):
    objs = Board.objects.all().order_by('-id')[idx:idx+getcnt]
    data = []
    for obj in objs:
        datum = dict()
        datum['id'] = str(obj.id)
        datum['title'] = str(obj.title)
        datum['cust_id'] = str(obj.cust.id)
        datum['regdate'] = str(obj.regdate)
        data.append(datum)
    return HttpResponse(json.dumps(data), content_type='application/json')

  2. 등록 화면

    1) HTML

      - 모든 input 태그에 required 속성 부여

      - 등록 버튼 클릭 시 /board/i 를 get 방식으로 요청

      - 취소 버튼 클릭 시 해당 clickCancel() 함수 실행(JavaScript에 존재)

/templates/boardreg.html

<div class="board_wrap">
    <div class="board_title">
        <strong>질문 등록</strong>
        <p>답변을 빠르고 정확하게 안내해드립니다.</p>
    </div>
    <form action="/board/i" method="get">
        <div class="board_write_wrap">
            <div class="board_write">
                <div class="title">
                    <dl>
                        <dt>제목</dt>
                        <dd><input type="text" placeholder="제목 입력" name="title" required></dd>
                    </dl>
                </div>
                <div class="cont">
                    <textarea placeholder="내용 입력" name="content" required></textarea>
                </div>
            </div>
            <div class="bt_wrap">
                <a href="#" class="on"><button type="submit">등록</button></a>
                <a href="#" onclick="clickCancel();">취소</a>
            </div>
        </div>
    </form>
</div>

    2) CSS

      - /static/css/style.css + media.css

    3) JavaScript

      - 취소 버튼 클릭 시 확인 창을 띄우기 위한 함수

      - 확인 누르면 

/templates/boardreg.html -> <script> 태그

function clickCancel(){
    c = confirm('[주의]\n작성한 내용이 저장되지 않았습니다.\n정말 질문 등록을 취소하시겠습니까?');
    if(c == true){
        location.href = '/board';
    }
}

    4) Application

      ① insertview() : 등록 화면 출력

      ② insert() : form으로부터 get 방식으로 받은 제목과 내용을 DB에 저장 후, 리스트 화면으로 이동

/web/views_board.py -> class BoardView(View)

@request_mapping("/iv", method="get")
def insertview(self, request):
    return render(request, 'boardreg.html')

@request_mapping("/i", method="get")
def insert(self, request):
    title = request.GET['title']
    content = request.GET['content']
    objs = Board(cust_id=request.session['sessionid'], title=title, content=content)
    objs.save()
    return redirect('/board')

  3. 상세 화면

    1) HTML

      - Application에서 받은 데이터를 context로 받아서 항목별로 출력

      - 목록 버튼 클릭 시 리스트 화면으로 이동

      - 수정/삭제 버튼은 로그인 상태에서만 보이도록 if문 안에 작성

      - 수정 버튼 클릭 시 해당 글 ID를 URL로 가지고 수정 화면으로 이동

      - 삭제 버튼 클릭 시, 확인창을 띄우고, 삭제하기 위해 clickDelete() 함수 실행(JavaScript에서)

/templates/boardget.html

<div class="board_wrap">
    <div class="board_title">
        <strong>고객 문의</strong>
        <p>빠르고 신속하게 답변 드리겠습니다.</p>
    </div>
    <div class="board_view_wrap">
        <div class="board_view">
            <div class="title">
                {{objs.title}}
            </div>
            <div class="info">
                <dl>
                    <dt>번호</dt>
                    <dd>{{objs.id}}</dd>
                </dl>
                <dl>
                    <dt>글쓴이</dt>
                    <dd>{{objs.cust.id}}</dd>
                </dl>
                <dl>
                    <dt>작성일</dt>
                    <dd>{{objs.regdate}}</dd>
                </dl>
            </div>
            <div class="cont">
                {{objs.content}}
            </div>
        </div>
        <div class="bt_wrap">
            <a href="/board" class="on">목록</a>
            {% if request.session.sessionid == objs.cust.id%}
            <a href="/board/uv/{{objs.id}}">수정</a>
            <a href="#" onclick="clickDelete();">삭제</a>
            {% endif %}
        </div>
    </div>
</div>

    2) CSS

      - /static/css/style.css + media.css

    3) JavaScript

      - 삭제 버튼 클릭 시, 확인창 띄우고, 확인 시에는 해당 글의 ID를 URL로 가지고 Application으로 이동하여 삭제

/templates/boardget.html -> <script>태그

function clickDelete(){
    c = confirm('[주의]\n삭제 후에는 복구할 수 없습니다.\n정말로 삭제하시겠습니까?');
    if(c == true){
        location.href = '/board/d/' + {{objs.id}}
    }
}

    4) Application

      ① get(pk) : URL로 받은 글 ID(pk)로 테이블에서 객체를 가져와서 상세 화면(boardget.html)에 context로 보내줌

      ② delete(pk)

        - URL로 받은 글 ID(pk)와 sessionid를 함께 이용하여 객체를 가져와서 삭제 실행

        - sessionid까지 이용하는 것은 아무나 URL을 통해 삭제 실행하지 못하도록 하기 위함

        - 삭제에 성공하거나, 임의로 URL로 삭제 시도 시, 리스트 화면으로 이동

/web/views_board.py -> class BoardView(View)

@request_mapping("/g/<int:pk>", method="get")
def get(self, request, pk):
    objs = Board.objects.get(id=pk)
    context = {
        'objs': objs
    }
    return render(request, 'boardget.html', context)
# ============================================================================================
@request_mapping("/d/<int:pk>", method="get")
def delete(self, request, pk):
    # 해당 리뷰가 로그인된 사람이 쓴 것일 경우에만 삭제 실행, 아닌 경우 게시판 목록으로 이동
    try:
        obj = Board.objects.get(id=pk, cust_id=request.session['sessionid'])    # 로그인 기능 추가시 sessionid로 변경
    except:
        return redirect('/board')
    obj.delete()
    return redirect('/board')

  4. 업데이트 화면

    1) HTML

      - 기본적인 틀은 등록 화면과 유사

      - Application에서 받아온 context 내 데이터로 기존 데이터를 input 태그에 출력

      - 등록 버튼 클릭 시 /board/u/{{objs.id}} 를 통해 글의 ID를 넘겨서 업데이트

      - 취소 버튼 클릭 시 clickCancel() 함수 실행(JavaScript에서)

/templates/boardupdate.html

<div class="board_wrap">
    <div class="board_title">
        <strong>문의 수정</strong>
        <p>답변을 빠르고 정확하게 안내해드립니다.</p>
    </div>
    <form action="/board/u/{{objs.id}}" method="get">
        <div class="board_write_wrap">
            <div class="board_write">
                <div class="title">
                    <dl>
                        <dt>제목</dt>
                        <dd><input type="text" placeholder="제목 입력" name="title" value="{{objs.title}}" required></dd>
                    </dl>
                </div>
                <div class="cont">
                    <textarea placeholder="내용 입력" name="content" required>{{objs.content}}</textarea>
                </div>
            </div>
            <div class="bt_wrap">
                <a href="#" class="on"><button type="submit">등록</button></a>
                <a href="#" onclick="clickCancel();">취소</a>
            </div>
        </div>
    </form>
</div>

    2) CSS

      - /static/css/style.css + media.css

    3) JavaScript

      - clickcancel() : 취소 버튼 클릭 시 확인창 띄우고, 확인 클릭 시, 해당 글 상세 화면으로 이동

/templates/boardupdate.html -> <script>태그

function clickCancel(){
    c = confirm('[주의]\n작성한 내용이 저장되지 않았습니다.\n정말 질문 등록을 취소하시겠습니까?');
    if(c == true){
        location.href = '/board/g/'+{{objs.id}};
    }
}

    4) Application

      ① updateview(pk) : 글 ID를 URL로 받아서 해당 객체를 context에 담아서 boardupdate.html과 함께 전송

      ② update(pk)

        - URL로 받은 게시판 ID와 sessionid를 함께 이용하여 객체를 찾아서 업데이트 된 내용을 저장

        - sessionid를 함께 이용한 것은 임의로 URL 입력 시, 알 수 없는 이유로 데이터가 변경되는 것을 막기 위함

        - 임의로 URL 입력 시 오류화면이 뜨기 때문에 예외 처리를 통해 리스트 화면으로 이동

        - 업데이트가 완료되면, 해당 글의 상세 화면으로 이동

/web/views_board.py -> class BoardView(View)

@request_mapping("/uv/<int:pk>", method="get")
def updateview(self, request, pk):
    objs = Board.objects.get(id=pk)
    context = {
        'objs': objs
    }
    return render(request, 'boardupdate.html', context)

@request_mapping("/u/<int:pk>", method="get")
def update(self, request, pk):
    # 임의로 url 입력하여 접근 시 에러 화면 대신 게시판 목록으로 이동
    try:
        objs = Board.objects.get(id=pk, cust_id=request.session['sessionid'])
    except:
        return redirect('/board')
    title = request.GET['title']
    content = request.GET['content']
    objs.title = title
    objs.content = content
    objs.save()
    return redirect('/board/g/'+str(pk))

- 끝 -

728x90