본문 바로가기

programming/Gukbi

국비 교육 103일차 - CURD 프로그램 (게시판) 만들기

728x90
반응형

장고로 본격적인 프로젝트를 만들기 위해 실습을 하는 중이다. 가장 기본인 게시판을 만들어 봤다. 복습할겸 직접 만들어보면서 블로깅을 하려고 한다. 

 

 

일단 장고 프로젝트 구조는 이렇게 생겼다. 다시 만들때 참고하기 위해 띄워놓고 시작하겠다. 

 

urls.py파일까지 만들어주고 셋팅을 먼저 시작한다

 

 

1. views.py에서 임시로 controller 생성

from django.shortcuts import render
from boardapp import models
# Create your views here.
def boardList(request):
    pass

def boardDetail(request):
    pass

def boardInsert(request):
    pass

def boardUpdate(request):
    pass

def boardDelete(request):
    pass

 urls에서 화면을 이동할때 views를 import 해와야 하기 때문에 일단은 임시로 함수들을 만들어 놓는다. 

 

그리고 urls.py에서 등록해주기

from boardapp import views
from django.urls import path

urlpatterns=[
    path('',views.boardList),
    path('detail/',views.boardDetail),
    path('insert/',views.boardInsert),
]

 일단은 list와 detail만 띄우려고 path를 두개만 잡아줬는데, 수정삭제 기능 사용하게 되면 더 추가해야한다. 

 

 

그러면 화면을 본격적으로 띄우기 위해서 models.py에서 dao를 만들어준다. 

from django.db import models
import cx_Oracle
# Create your models here.
def getConnection():
    try:
        conn=cx_Oracle.connect("hr/happy@localhost:1521/xe")
    except Exception as e:
        print(e)
    return conn

def boardListData(page):
    conn=getConnection()
    cursor=conn.cursor()

    #페이지 나누기
    rowSize=10
    start=(rowSize*page)-(rowSize-1)
    end=(rowSize*page)
    sql=f"""
        SELECT no,subject,name,TO_CHAR(regdate,'YYYY-MM-DD'),hit,num
        FROM (SELECT no,subject,name,regdate,hit,rownum as num
        FROM (SELECT no,subject,name,regdate,hit
        FROM spring_freeboard ORDER BY no DESC))
        WHERE num BETWEEN {start} AND {end}
        """
    cursor.execute(sql)
    board_list=cursor.fetchall()
    #print(board_list)
    cursor.close()
    conn.close()
    return board_list
#boardListData(1)
def boardTotalPage():
    conn=getConnection()
    cursor=conn.cursor()
    sql="SELECT CEIL(COUNT(*)/10.0) FROM spring_freeboard"
    cursor.execute(sql)
    totalpage=cursor.fetchone()
    #print(totalpage[0])
    cursor.close()
    conn.close()
    return totalpage[0]
#boardTotalPage()


일단은 리스트만 출력해보고자 list 가져오는 코드와 totalpage가져오는 코드만 만들어봤다. 

models에서 값을 다 받아와서 리턴하는 함수를 만들었으면 다음은 views에서 받아온 값들을 처리해주면 된다. 

 

def boardList(request):
    page=request.GET['page']
    curpage=int(page)
    board_list=models.boardListData(curpage)
    totalpage=models.boardTotalPage()
    list=[]
    for row in board_list:
        data = {"no":row[0],"name":row[2],"subject":row[1],"regdate":row[3],"hit":row[4]}
        list.append(data)
    return render(request,'board/board_list.html',{"list":list,"curpage":curpage,"totalpage":totalpage})

 값을 받아오는 부분을 잘 봐야하는데, models에서 넘겨주는 값은 ()안에 있는 튜플의 형태로 넘어오고 있다. 리스트 배열값으로 변환을 해주어야 하기 때문에 빈 배열 list를 만들고 받아온 딕트를 (board_list) for문을 돌려서 data에 임시로 받아온다.

 

그리고 이걸 다시 append를 통해 list에 추가하고, html쪽으로 값을 넘겨주면 된다. 

 

{% for row in list %}
                 <tr>
                     <td class="text-center" width="10%">{{row.no}}</td>
                     <td width="45%">
                         <a href="detail/?no={{row.no}}">{{row.subject}}</a>
                     </td>
                     <td class="text-center" width="15%">{{row.name}}</td>
                     <td class="text-center" width="20%">{{row.regdate}}</td>
                     <td class="text-center" width="10%">{{row.hit}}</td>
                 </tr>
                 {% endfor %}

 for문을 사용해서 list안에 있는 값들을 받아오면 된다. 

 

 

페이지값도 넘겼기때문에, 이렇게 페이지를 나눌 수 있다. 

 

 

그러면 이제 detail에 들어가서 내용을 확인할 수 있도록 만들어 준다. 

역시 먼저 models.py파일부터 만들어 준다.

def boardDetailData(no):
    conn=getConnection()
    cursor=conn.cursor()
    sql=f"""
        SELECT no,subject,name,TO_CHAR('YYYY-MM-DD'),hit,content,pwd
        FROM spring_freeboard
        WHERE no={no}
        """
    cursor.execute(sql)
    board_detail=cursor.fetchone()
    #print(board_detail)
    data=(board_detail[0],board_detail[1],board_detail[2],board_detail[3],board_detail[4],board_detail[5].read(),board_detail[6])
    print(data)
    cursor.close()
    conn.close()
    return data
boardDetailData(23)

 여기서 list를 받아올때와 다른점은 한가지 row만 받아오기 때문에 fetchone을 썼다는 점이다. 

다만 data라는 튜플형 변수를 새로 만들어서 값을 하나씩 받아온다. 이유가 있는데, clob을 사용한 content변수는 그냥 받아오면 에러가 나기 때문이다. 그래서 꼭 ,read()를 써서 받아와준뒤 튜플형 변수 data를 리턴하게 만들어 준다. 

 

 

++) 조회수 증가하는거 깜빡해서 추가함

#조회수 먼저 증가
    sql=f"""
        UPDATE spring_freeboard SET
        hit=hit+1
        WHERE no={no}
        """
    cursor.execute(sql)
    conn.commit()
    cursor.close()

 

 

이 값을 views.py로 넘겨주고 html에서 받을 수 있도록 만들어 준다. 

 

def boardDetail(request):
    no=request.GET['no']
    board_detail=models.boardDetailData(int(no))
    data={"no":board_detail[0],"subject":board_detail[1],"name":board_detail[2],
          "regdate":board_detail[3],"content":board_detail[5],"hit":board_detail[4],"result":False}
    return render(request,'board/board_detail.html',data)

 

 

이번에는 처음부터 딕트형 변수인 data로 하나씩 받아왔다. 

 

내용까지 잘 출력하고 있다. 

 

그러면 이제 새로 글을 써서 올릴 수 있도록 inser를 만들어보겠다. 

 

데이터를 띄울건 없고 받아와야하기 때문에 html부터 만들어 준다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
    <style type="text/css">
        .row{
           margin:0px auto;
           width:700px;
        }
        h1{
           text-align:center
        }
    </style>
</head>
<body>
   <div style="height:50px"></div>
   <div class="container">
       <div class="row">
           <h1>글쓰기</h1>
           <form method="post" action="../insert_ok">
           {% csrf_token %}
           <table class="table">
               <tr>
                   <th width="15%" class="success text-right">이름</th>
                   <td width="85%">
                       <input type="text" name="name" size="15" class="input-sm" required>
                   </td>
               </tr>
               <tr>
                   <th width="15%" class="success text-right">제목</th>
                   <td width="85%">
                       <input type="text" name="subject" size="50" class="input-sm" required>
                   </td>
               </tr>
               <tr>
                   <th width="15%" class="success text-right">내용</th>
                   <td width="85%">
                       <textarea rows="8" cols="55" name="content" required></textarea>
                   </td>
               </tr>
               <tr>
                   <th width="15%" class="success text-right">비밀번호</th>
                   <td width="85%">
                       <input type="password" name="pwd" size="10" class="input-sm" required>
                   </td>
               </tr>
               <tr>
                   <td colspan="2" class="text-center">
                       <input type="submit" value="글쓰기" class="btn btn-sm btn-primary">
                       <input type="button" value="취소" class="btn btn-sm btn-danger"
                              onclick="javascript:history.back()">
                   </td>
               </tr>
           </table>
           </form>
       </div>
   </div>
</body>
</html>

 post방식으로 넘기기 때문에 해킹을 방지하기 위한 코드인 

{% csrf_token %}

을 반드시 써준다. 

그리고 action에는 url을 ../insert_ok로 준다. 이걸 당연히 urls.py에 등록해야하는데

urlpatterns=[
    path('',views.boardList),
    path('detail/',views.boardDetail),
    path('insert/',views.boardInsert),
    path('insert_ok',views.boardInsertOk),
]

 창을 띄워줘야 했던 detail, insert와는 다르게 뒤에 /를 써주지 않는다. 이거 써주면 화면처리에서 오류가 발생한다. 

 

 

그러면 값을 받아왔으니 오라클에 넘겨줘서 insert를 시켜주기만 하면 된다. 

당연히 models.py부터 시작

def board_insert(insert_value):
    conn=getConnection()
    cursor=conn.cursor()
    sql="""
           INSERT INTO spring_freeboard VALUES(
            (SELECT NVL(MAX(no)+1,1) FROM spring_freeboard),:1,:2,:3,:4,SYSDATE,0)
         """
    cursor.execute(sql,insert_value)
    print("게시물 등록 완료")
    conn.commit()
    cursor.close()
    conn.close()

 써준 sql문장과 views로부터 받아온 insert_value를 넣고 execute로 실행해줘야 한다. 

그리고 commit()까지 완료해주어야 한다. 

 

 

views.py에서는 이렇게 받아온다. 

def boardInsertOk(request):
    #print("boardInsertOk")
    name=request.POST['name']
    subject=request.POST['subject']
    content=request.POST['content']
    pwd=request.POST['pwd']
    #print(name,subject,content,pwd)
    data=(name,subject,content,pwd)
    models.board_insert(data)
    return redirect('/board/?page=1')

 post방식으로 받아왔기 때문에 하나씩 변수에 저장을 해주고, 튜플 data를 만들어서 models에서 만든 board_insert메소드에 넣어주고 리스트로 돌아가게끔 return 해준다. 

 

 일단 화면을 이렇게 띄워주고, 글쓰기 버튼을 누르면 값이 boardInsertOk 로 넘어간다. 

 새로 글이 올라온걸 확인할 수 있다. 

 

 

그러면 삭제를 만들어보겠다. (수정보다 간단하기 때문에)

삭제는 비밀번호가 맞는지 먼저 체크를 해주고 그 다음에 삭제가 완료되게끔 만들어야 하기때문에 비밀번호를 확인하는 창먼저 만들어 준다. 

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
    <style type="text/css">
        .row{
           margin:0px auto;
           width:350px;
        }
        h1{
           text-align:center
        }
    </style>
</head>
<body>
     <div style="height:50px"></div>
     <div class="container">
         <div class="row">
             <h1>삭제 하기</h1>
             <form method="post" action="../delete_ok">
                 {% csrf_token %}
             <table class="table">
                 <tr>
                     <td class="text-center">
                         비밀번호:<input type="password" name="pwd" size="15" class="input-sm" required>
                         <input type="hidden" name="no" value="{{no}}">
                     </td>
                 </tr>
                 <tr>
                     <td class="text-center">
                         <input type="submit" value="삭제" class="btn btn-sm btn-primary">
                         <input type="button" value="취소" class="btn btn-sm btn-danger"
                           onclick="javascript:history.back()"
                         >
                     </td>
                 </tr>
             </table>
             </form>
         </div>
     </div>
</body>
</html>

 

데이터가 delete_ok로 넘어가고 있기 때문에 delete_ok에 대한 url 경로처리를 해준다. 

path('delete/',views.boardDelete),
path('delete_ok',views.boardDeleteOk)

 ok파일은 redirect로 넘어가기 때문에 역시 / 는 제외하고 써준다. 

 

그럼 다시 models.py부터 작성

def board_delete(no,pwd):
    conn=getConnection()
    cursor=conn.cursor()


    #비밀번호 먼저 확인
    sql=f"""
        SELECT pwd FROM spring_freeboard
        WHERE no={no}
    """
    cursor.execute(sql)
    db_pwd=cursor.fetchone()
    #print(db_pwd)
    #print(pwd)
    #print(no)
    cursor.close()
    check = False

    if pwd == db_pwd[0]:
        check=True
        cursor=conn.cursor()
        sql=f"""
            DELETE FROM spring_freeboard
            WHERE no={no}
            """
        cursor.execute(sql)
        conn.commit()
        cursor.close()
    else:
        check=False
    #print(check) True 나옴
    conn.close()
    return check

 다 맞게 잘 썼는데 마지막에 commit()을 해주지 않아서 삭제가 계속 안되는 오류가 발생했었다..

하지만 찾아냈으니 다행이지 뭐

 

 

views.py파일은 더욱 간단하다

def boardDelete(request):
    no=request.GET['no']
    return render(request,'board/board_delete.html',{"no":no})
def boardDeleteOk(request):
    no=request.POST['no']
    pwd=request.POST['pwd']
    check=models.board_delete(int(no),pwd)
    return render(request,'board/board_delete_ok.html',{"check":check})

 일단 어떤 게시글을 삭제하는지 그 번호를 계속 저장하면서 가지고 와야 하기 때문에 no를 받는다. 

 

받아온 no와 pwd를 모델의 board_delete에 넣어서 결과값 check에 넣어준다. 

check는 True아니면 False 둘중 하나의 boolean형이다. 일단 이걸 delete_ok.html로 넘겨준다. 

 

 

delete_ok에는 no값과 pwd를 받아서 맞으면 원래 기본 리스트 화면으로, 아니면 다시 돌아가게끔 화면을 만들어 준다.

 

{% if check %}
 <script type="text/javascript">
       location.href="/board/?page=1"
   </script>
{% else %}
<script>
  alert('비밀번호가 틀립니다');
  history.back();
</script>
{% endif %}

 True면 리스트로 돌아가고, 틀리면 alert창을 띄워준 다음 다시 비밀번호를 입력하는 창으로 돌아가게 만든다. 

비밀번호를 맞게 입력하고 리스트로 돌아가면 게시글이 삭제되어 있는것을 볼 수 있다. 

 

삭제 처리도 완료되었으니 가장 까탈스러운 수정을 완성해보겠다. 

 

일단 수정을 하려면 수정하는 창이 띄워져야 하니 그 html부터 만들고 시작해준다. 

insert랑 폼은 같으나 value만 채워줘서 창을 띄우면된다.

그러면 이제 진짜 수정하게끔 만드는 models.py, views.py파일만 완성하면 게시판은 끝이 난다

 

def board_update(update_value):
    conn=getConnection()
    cursor=conn.cursor()
    no=update_value[0]
    name=update_value[1]
    subject=update_value[2]
    content=update_value[3]
    pwd=update_value[4]
    data=(name,subject,content,no)
    #비밀번호 맞는지 확인
    sql = f"""
           SELECT pwd FROM spring_freeboard
           WHERE no={no}
       """
    cursor.execute(sql)
    db_pwd = cursor.fetchone()
    cursor.close()
    check = False

    if pwd == db_pwd[0]:
        check = True
        cursor=conn.cursor()
        sql=f"""
            UPDATE spring_freeboard SET 
            name=:1, subject=:2, content=:3
            WHERE no=:4
            """
        cursor.execute(sql,data)
        conn.commit()
        cursor.close()
    else:
        check = False

    conn.close()
    return check

 update_value를 받아와서 필요한대로 저장을 해줬다. data튜플은 update할때 쓰기 편하게 순서를 정해줬다. 

삭제할때 비번을 확인한 것 처럼 똑같이 확인을 해주고나서 commit()을 날린다. return은 True or False를 나타내는 check만 해준다. 

 

def boardUpdateOk(request):
    no=request.POST['no']
    name=request.POST['name']
    subject=request.POST['subject']
    content=request.POST['content']
    pwd=request.POST['pwd']
    data=(no,name,subject,content,pwd)
    check=models.board_update(data)
    return render(request,'board/board_update_ok.html',{"no":no,"check":check})

 이렇게 값을 받아와서 no와 check를 넘겨주면 된다. 

그럼 이 값을 board_update_ok.html에서 받아서 처리를 해준다.

 

{% if check %}
 <script type="text/javascript">
       location.href="detail/?no={{no}}"
   </script>
{% else %}
<script>
  alert('비밀번호가 틀립니다');
  history.back();
</script>
{% endif %}

 목록으로 넘어가지 않고 detail로 넘어가게 하기

 

수정까지 완료!

길었다..

 

다음은 목록출력+댓글까지 해보겠지..

 

728x90
반응형