2월 01, 2022

CRUD구현

  *생활코딩 Django 편을 개인 공부를 목적으로 정리한 글임을 밝힙니다*

출처:https://www.youtube.com/watch?v=pbKhn2ten9I&list=PLuHgQVnccGMDLp4GH-rgQhVKqqZawlNwG


아무리 복잡한 웹 애플리케이션도 결국 4가지 작업 안에 속합니다.

C reate

R ead

U pdate

D elete

전체적인 구성을 보고 자세히 분석해보겠습니다.

파일 구성은 이러합니다.



myproject.urls.py 입니다. admin접속이 아닌 접속은 myapp으로 위임하는 것을 알 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
"""myproject URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from xml.etree.ElementInclude import include
from django.contrib import admin
from django.urls import path, include

# http://127.0.0.1/
# http://127.0.0.1/app/

# http://127.0.0.1/create/
# http://127.0.0.1/read/1/

urlpatterns = [
    path('admin/', admin.site.urls),
    path('',include('myapp.urls'))
]


이후 routing을 담당하는 myapp.urls.py 입니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
"""myproject URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from xml.etree.ElementInclude import include
from django.contrib import admin
from django.urls import path
from myapp import views

urlpatterns = [
    path('', views.index), 
    #사용자가 아무것도 없는 경로로 들어왔다면, views.index 실행해라
    path('create/',views.create), #사용자가 create로 들어왔다면 #views.create실행
    path('read/<id>/',views.read), #사용자가 read/<id>으로 들어왔다면 views.read 실행
    path('update/<id>/',views.update), #사용자가 read/<id>으로 들어왔다면 views.read 실행
    path('delete/', views.delete)
]

myapp.urls.py에서 처리를 위임받은 views.py입니다.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
from glob import glob
from typing import NewType
from django.shortcuts import render, HttpResponse, redirect
from django.views.decorators.csrf import csrf_exempt # 장고보안기능면제

nextId = 4 #id값이 없는 create함수의 NewTopic을 위해 새로운 id를 만드는 과정

topics = [
    {'id':1, 'title':'routing', 'body':'Routing is...'},
    {'id':2, 'title':'view', 'body':'View is..'},
    {'id':3, 'title':'Model', 'body':'Model is..'},
]

def HTMLTemplate(articleTag, id=None):     #html을 함수화함으로서 재활용의 기틀 마련 
    global topics
    contextUI = ''
    if id != None:   #홈에서는 delete버튼이 뜨면안되니깐. #상세보기 페이지에서만 뜨도록
        contextUI = f'''
            <li>
                <form action="/delete/" method="post">
                    <input type="hidden" name="id" value={id}>
                    <input type="submit" value="delete">
                </form>
            </li>
            <li><a href="/update/{id}">update</a></li>
        '''
    ol = ''
    for topic in topics:
        ol += f'<li><a href="/read/{topic["id"]}">{topic["title"]}</a></li>'
    return f'''
    <html>
    <body>
        <h1><a href="/">Django</a></h1>
        <ol>
            {ol}
        </ol>
        {articleTag}
        <ul>
            <li><a href="/create/">create</a></li>
            {contextUI}
        </ul>
    </body>
    </html>
    '''
#hidden타입의 input: 눈에 보이지 않지만 서버로 데이터를 전송하는 폼

@csrf_exempt
def update(request,id):
    global topics
    if request.method == 'GET': #사용자가 get으로 접속하면 update라는 텍스트를 출력
        for topic in topics:
            if topic['id'] == int(id):
                selectedTopic = {
                    'title':topic['title'],
                    'body':topic['body'],
                    }
        article = f'''
        <form action="/update/{id}/" method="post">
            <p><input type="text" name="title" placeholder="title" value={selectedTopic["title"]}></p>
            <p><textarea name="body" placeholder="body">{selectedTopic['body']}</textarea></p>
            <p><input type="submit"></p>
        </form>
        ''' 
        return HttpResponse(HTMLTemplate(article,id))
    elif request.method == 'POST': #post로 데이터를 수정하면
        title = request.POST['title']
        body = request.POST['body']
        for topic in topics:
            if topic['id'] == int(id):     #일치한다면 값을 변경
                topic['title'] = title
                topic['body'] = body
        return redirect(f'/read/{id}')



# Create your views here.
def index(request):
    article = '''
    <h2>Welcome</h2>
    Hello, Django
    '''
    return HttpResponse(HTMLTemplate(article))

def read(request, id):
    global topics
    article = ''
    for topic in topics:
        if topic['id'] == int(id):
            article = f'<h2>{topic["title"]}</h2>{topic["body"]}'
    return HttpResponse(HTMLTemplate(article, id))

@csrf_exempt #장고의 보안기능 면제 
def create(request):
    global nextId
    if request.method == "GET":  #get방식이냐 post방식이냐에 따라서 다르게 처리
        article = '''
        <form action="/create/" method="post">
            <p><input type="text" name="title" placeholder="title"></p>
            <p><textarea name="body" placeholder="body"></textarea></p>
            <p><input type="submit"></p>
        </form>
        ''' # action 속성: submit을 눌렀을 때 보내고 싶은 서버의path 
        return HttpResponse(HTMLTemplate(article))
    elif request.method =="POST": #post들어오는 처리일때
        title = request.POST["title"]   #서버로부터 들어온 데이터 이름붙이기
        body = request.POST["body"] 
        NewTopic = {"id":nextId, "title":title, "body":body} #그를 통해 새로운 데이터 만들기 
        # 이 NewTopic을 위의 topics리스트에 추가하려는데, 얘한테 id값이 없음 (6번라인이동) - nextId 값 만듬
        topics.append(NewTopic)
        url = '/read/'+str(nextId)
        nextId = nextId + 1 #nextId값이 계속 4에 머무르는것을 막아야 함
        return redirect(url)
        #작업이 끝나고 서버가 웹 브라우저한테: 지금 생성한 글의 상세보기 페이지로 redirect해 ! 라고 해야함

@csrf_exempt
def delete(request):
    global topics
    if request.method == 'POST':
        id = request.POST['id']
        newTopics = []
        for topic in topics:
            if topic['id'] != int(id):  #topics리스트의 topic의 id값이 post로 보내준 id값가 같지않다면 newtopics에 추가
                newTopics.append(topic)
        topics = newTopics   #이렇게함으로써 post로 보내준 id값을가진 topic은 새로운리스트에서 빠지게 됨(삭제)
        return redirect('/') #과정이 끝나면사용자는 홈으로 보내버림
        

views.py의 코드를 자세히 뜯어보며 각 기능을 분석해보겠습니다.

index함수는 홈화면을 구현하는 함수입니다

1
2
3
4
5
6
def index(request):
    article = '''
    <h2>Welcome</h2>
    Hello, Django
    '''
    return HttpResponse(HTMLTemplate(article))

장고는 request와 response 객체로 상태를 server와 client가 주고 받습니다.

HttpResponse가... 뭐죠..?

a. 이때 특정 페이지가 Request되면, 장고는 metadata(데이터에 관한 데이터)를 포함하는 HttpRequest 객체를 생성합니다

b. 장고는 urls.py에 정의한대로 특정 views의 클래스 또는 함수의 첫번째 인자로 위의 HttpRequest 객체를 전달합니다. (함수의 들어가는 request가 이것)

c. 해당 views는 결과값을 HttpResponse(또는 JsonResponse) 객체에 담아 전달합니다. 

HttpResponse는 HttpRequest을 받은 views안의 함수의 결과값을 전달하는 객체이군요! 


HttpResponse 의 전달인자인  HTMLTemplate(article)은 또 무엇일까요?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def HTMLTemplate(articleTag, id=None):     #html을 함수화함으로서 재활용의 기틀 마련 
    global topics
    contextUI = ''
    if id != None:   #홈에서는 delete버튼이 뜨면안되니깐. #상세보기 페이지에서만 뜨도록
        contextUI = f'''
            <li>
                <form action="/delete/" method="post">
                    <input type="hidden" name="id" value={id}>
                    <input type="submit" value="delete">
                </form>
            </li>
            <li><a href="/update/{id}">update</a></li>
        '''
    ol = ''
    for topic in topics:
        ol += f'<li><a href="/read/{topic["id"]}">{topic["title"]}</a></li>'
    return f'''
    <html>
    <body>
        <h1><a href="/">Django</a></h1>
        <ol>
            {ol}
        </ol>
        {articleTag}
        <ul>
            <li><a href="/create/">create</a></li>
            {contextUI}
        </ul>
    </body>
    </html>
    '''
#hidden타입의 input: 눈에 보이지 않지만 서버로 데이터를 전송하는 폼

HttpResponse는 결과값으로 client에게 html값을 전달해야겠죠! 

이때, 제가 구현하고자 하는 페이지는 전반적인 기틀이 비슷하기에 html을 HTMLTemplate 함수를 사용하여 모듈화 시킨것입니다. article은 HTML의 본문부분을 담당하는 매개변수입니다. 

위의 사진이 index가 구현하는 홈페이지입니다.
1.routing ~ 3으로 된 부분이 HTMLTemplate의 {ol}변수 부분입니다. 뒤에서 자세히 알아보겠습니다.

아무튼 index함수가 article변수를 통해 본문부분에 "Welcome" "Hello, Django"를 표시한것을 볼 수 있습니다. 


다음으로 Read입니다

1
2
3
4
5
6
7
def read(request, id):
    global topics
    article = ''
    for topic in topics:
        if topic['id'] == int(id):
            article = f'<h2>{topic["title"]}</h2>{topic["body"]}'
    return HttpResponse(HTMLTemplate(article, id))

우선, topics 전역변수를 사용하는 것을 볼 수 있습니다. topics전역변수는 각 상세페이지(1.routing , 2.view.....) 등의 정보를 담아놓은 리스트입니다. 

HTMLTemplate의 {ol}변수는 topics안의 각 딕셔너리인 topic의 id값을 활용한 /read/링크가 삽입된 리스트를 만듭니다.

read함수는 매개변수로 들어오는 id

(ol변수에 id값에 따라 다른 /read/id 링크를 생성하고, urls.py 에서  path('update/<id>/',views.update)

의 형식으로 id를 확인합니다. ) 를 확인하고, 만약 인수로 주어진 id와 topic의 id 값이 같다면,

본문인 article 부분에 topic의 title과 body를 표시합니다.


다음은 Create입니다

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@csrf_exempt #장고의 보안기능 면제 
def create(request):
    global nextId
    if request.method == "GET":  #get방식이냐 post방식이냐에 따라서 다르게 처리
        article = '''
        <form action="/create/" method="post">
            <p><input type="text" name="title" placeholder="title"></p>
            <p><textarea name="body" placeholder="body"></textarea></p>
            <p><input type="submit"></p>
        </form>
        ''' # action 속성: submit을 눌렀을 때 보내고 싶은 서버의path 
        return HttpResponse(HTMLTemplate(article))
    elif request.method =="POST": #post들어오는 처리일때
        title = request.POST["title"]   #서버로부터 들어온 데이터 이름붙이기
        body = request.POST["body"] 
        NewTopic = {"id":nextId, "title":title, "body":body} #그를 통해 새로운 데이터 만들기 
        # 이 NewTopic을 위의 topics리스트에 추가하려는데, 얘한테 id값이 없음 (6번라인이동) - nextId 값 만듬
        topics.append(NewTopic)
        url = '/read/'+str(nextId)
        nextId = nextId + 1 #nextId값이 계속 4에 머무르는것을 막아야 함
        return redirect(url)
        #작업이 끝나고 서버가 웹 브라우저한테: 지금 생성한 글의 상세보기 페이지로 redirect해 ! 라고 해야함

새로운 항목을 만드는 함수입니다.

요청이 get방식이냐, post 방식이냐에 따라 분기하여 다르게 처리합니다.

브라우저가 서버로부터 데이터를 요청하면 GET

브라우저가 서버의 데이터를 변경하고자 하면 POST입니다 

get 방식의 request가 들어왔을때 부터 살펴보면, article 변수에 form을 만듦으로써 새로운 입력을 받을 수 있는 환경을 구현합니다. form의 method를 "post"로 설정하여 form의 전송 방식을 post로 설정해놓은 것을 확인할 수 있습니다.

제출을 누름으로써 post로 들어오는 처리가 발생하면, 폼에 작성된 데이터를 처리합니다.

이 과정에서, 서버로부터 들어온 데이터에 id값을 붙여주기 위해 전체코드의 처음부분에 nextId 변수를 설정해놓았습니다. 

코드에 따른 작업이 끝나고, 새롭게 create된 topic의 상세보기 페이지를 return합니다

create를 통해 새로운 topic을 추가한 모습입니다


만들었으면 삭제를 해야겠죠? Delete입니다

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@csrf_exempt
def delete(request):
    global topics
    if request.method == 'POST':
        id = request.POST['id']
        newTopics = []
        for topic in topics:
            if topic['id'] != int(id):  #topics리스트의 topic의 id값이 post로 보내준 id값가 같지않다면 newtopics에 추가
                newTopics.append(topic)
        topics = newTopics   #이렇게함으로써 post로 보내준 id값을가진 topic은 새로운리스트에서 빠지게 됨(삭제)
        return redirect('/') #과정이 끝나면사용자는 홈으로 보내버림

HTMLTemplate 함수를 보시면, id가 None이 아닐때, 즉 홈 화면이 아닐때는 delete버튼과 update버튼이 생기도록 설정해놓은 코드를 보실 수 있습니다

사용자가 delete를 통해 post 방식으로 처리를 요청하면, newTopics리스트에 현재 id값을 제외한 나머지 id값을 가진 topic을 추가하여, 해당 newTopics리스트를 topics리스트로 교체해줌으로써 현재 topic을 삭제합니다.


마지막으로 Update입니다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@csrf_exempt
def update(request,id):
    global topics
    if request.method == 'GET': #사용자가 get으로 접속하면 update라는 텍스트를 출력
        for topic in topics:
            if topic['id'] == int(id):
                selectedTopic = {
                    'title':topic['title'],
                    'body':topic['body'],
                    }
        article = f'''
        <form action="/update/{id}/" method="post">
            <p><input type="text" name="title" placeholder="title" value={selectedTopic["title"]}></p>
            <p><textarea name="body" placeholder="body">{selectedTopic['body']}</textarea></p>
            <p><input type="submit"></p>
        </form>
        ''' 
        return HttpResponse(HTMLTemplate(article,id))
    elif request.method == 'POST': #post로 데이터를 수정하면
        title = request.POST['title']
        body = request.POST['body']
        for topic in topics:
            if topic['id'] == int(id):     #일치한다면 값을 변경
                topic['title'] = title
                topic['body'] = body
        return redirect(f'/read/{id}')

 마찬가지로 get방식 접속과 post방식 접속을 분기해두었습니다.

사용자가 get으로 접속하게 되면 현재 topic의 title과 body값을 update폼에 표시합니다

사용자가 submit을 통해 post방식으로 전송을 요청하면 topics리스트의 값을 POST된 값으로 교체하고, 사용자를 read/{id} , 즉 topic의 상세페이지로 redirect 시킵니다.


django를 처음써보기도 했고, 각 요소 요소가 물고 물리는 관계여서 이 정도 페이지를 구현하는데도 어려움을 많이 겪었습니다.. 익숙해지도록 노력해야겠습니다