전체적인 어플의 설명을 먼저해볼까 합니다.게시판을 만드는 웹어플 프로젝트입니다.
get,post는 총 4개가 존재합니다.

  • 전체글을 보여주는 get /list
  • 전체글에서 각 글을 클릭하면 각 글을 하나씩 보여주는 get /view
  • 게시글에 정보를 입력후 게시글을 업로드하는 get /write
  • 게시글을 DB에 저장하는 post /save

설명하는 순서로는 get /write -> post /save -> get /list -> get /view가 되겠습니다..

get /write -> post /save -> get /list -> get /view작업이 모두 끝난 후의 디렉토리, 파일트리구조

tutorial
├─community
│  ├─migrations
│  │  └─__pycache__
│  ├─templates
│  │  ├─list.html
│  │  ├─save_complete.html
│  │  ├─save_not_complete.html
│  │  ├─view.html
│  │  └─write.html
│  ├─__init__.py
│  ├─admin.py
│  ├─apps.py
│  ├─forms.py
│  ├─models.py
│  ├─tests.py
│  ├─views.py
│  └─__pycache__
├─tutorial
│  ├─__init__.py
│  ├─settings.py
│  ├─urls.py
│  ├─wsgi.py
│  └─__pycache__
├─db.sqlite3
└─manage.py

get /write작성을 위한 시퀀스도

시퀀스도에 매겨진 번호를 바탕으로 설명합니다.
먼저 get /write에 대해 보겠습니다

get /write개발

1. get: /write/, 2. dispatch url

urls.py

"""tutorial URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.2/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 django.contrib import admin
from django.urls import path, re_path
from community.views import *

urlpatterns = [
    path('admin/', admin.site.urls),
    path('write/', write, name='write'),
]

클라이언트로부터 write/로 요청이 들어오면 가장 먼저, urls.py를 보고 url을 dispatch합니다.
위 코드에서 path('write/', write, name='write') 의 부분으로

  • 첫번째 파라미터로 url주소를
  • 두번재 파라미터로 받은 요청을 어디로 전달할지
  • 세번재로는 내부적으로 사용되는 path명
    을 기입한다.

위 코드는 클라이언트로부터 받은 요청을 컨트롤러(views.py)의 write함수로 전달합니다.

3. send request

views.py

from django.shortcuts import render
from community.forms import *
# from django.views.decorators.csrf import csrf_exempt
# Create your views here.
# 사용자에게 들어온 요청을 받고, response를 반환함

def write(request):
    form = Form()
    return render(request, 'write.html', {'form':form})

path('write/', write, name='write') 에 따라 urls.py에서 dispatch한 request가 도착하는 곳이 바로 write게 되겠네요.

4. create form, 5. call models to create form, 6.return form, 7. return form

views.py

from django.shortcuts import render
from community.forms import *
# from django.views.decorators.csrf import csrf_exempt
# Create your views here.
# 사용자에게 들어온 요청을 받고, response를 반환함

def write(request):
    form = Form()
    return render(request, 'write.html', {'form':form})

forms.py

from django.forms import ModelForm
from community.models import *

class Form(ModelForm):
    class Meta:
        model = Article
        fields = ['name', 'title', 'contents', 'url', 'email']

models.py

from django.db import models

# Create your models here.
class Article(models.Model):
    # Override Fields
    # 이게 데이터베이스에서 테이블의 컬럼을 만드는 것임
    name = models.CharField(max_length=50)
    title = models.CharField(max_length=50)
    contents = models.TextField()
    url = models.URLField()
    email = models.EmailField()
    cDate = models.DateTimeField(auto_now_add=True)

write함수 내의 form = Form() 을 보도록 합시다.

forms.py의 Form 클래스가 하는 일은, ModelForm을 상속받아 딱히 리턴이 없어도 생성한 db에 맞는 input form을 생성해 주는 모양입니다.. 디버깅을 해본 결과 form = Form()은 form object입니다. 다해주네요 장고는..

 

forms.py의 Form 클래스에서 주목할 부분이, model = Article 인데요. Article은 models.py에서 생성한 적이 있는, 테이블 정보에 관한 클래스입니다. 모든 모듈에서 models내부에 있는 클래스명들은 딱히 import하지 않아도 불리는 모양이더라구요.

 

너무 다해주네요.

다음으로 넘어가기 전에 templates폴더를 생성하고 그 아래 view가 될 html들을 생성해 줍시다

tutorial
├─community
│  ├─migrations
│  │  └─__pycache__
│  ├─templates
│  └─__pycache__
└─tutorial
    └─__pycache__
  • 자동으로 생성된 디렉토리 트리에서 templates라는 디렉토리를 앱명으로 만들어진 디렉토리 직하에 생성한 후
  • 그 아래 view를 구성하게될 html파일들을 넣어줍니다.
  • templates디렉토리 안에 write.html을 생성해주세요.

8. parsing form to html, 9. return parsed html, 10. render html as response

자그럼 이제 생성된 form을 html에 어떻게 넣냐.

views.py

from django.shortcuts import render
from community.forms import *
# from django.views.decorators.csrf import csrf_exempt
# Create your views here.
# 사용자에게 들어온 요청을 받고, response를 반환함

def write(request):
    form = Form()
    return render(request, 'write.html', {'form':form})

return render(request, 'write.html', {'form':form}): 세번째 파라미터인 {'form':form}을 통해 form을 html로 전달합니다.

write.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>write</title>

</head>
<body>
    <form action="/save/" method="post">
        {% comment %}
            {{ form.as_table }}
            {{ form.as_p }}
            {{ form.as_ul }}
        {% endcomment %}
        {{ form.as_p }}
        {% csrf_token %}

        <button type="submit" class="btn btn-primary">저장</button>
    </form>
</body>
</html>

write.html을 위처럼 코딩해주면 됩니다. form파일을 다양한 방법으로 html화 할 수 있는 것 같습니다. html파일의 comment안에 있는 것들이 그 예라고 볼수 있는데요. table로도, p태그로도, ul코드로도 바꿀수 있는 모양입니다.

 

이때 주의해야 할점 이 빈칸도 반드시 위의 문법대로 지켜주셔야 한다는 점입니다. {{ }} 사이에 빈칸을 무시했다가는 컴파일에 실패하게 됩니다.

 

제가 궁금했던게 하나 있는데요. views.py의 return render(request, 'write.html', {'form':form})에서 어떻게 write.html을 바로 찾아서 파싱을 할 수 있느냐 하는 겁니다.

 

찾아보니, 정답은 settings.py에 있었습니다. 디폴트로써 templates디렉토리는 [appName]디렉토리 직하에 있는 것을 가정하므로, 별다른 디렉토리를 적어주지 않아도 write.html만으로 파싱이 가능했던 겁니다.

 

templates디렉토리의 구조를 커스터마이징하고 싶다면,

 

settings.py

...
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
...

TEMPLATES['DIRS']에 적어주면 됩니다. 가령 TEMPLATES['DIRS'] = [templates]으로 한다면,루트 디렉토리 직하에 templates디렉토리가 위치하게 되겠네요.

 

post save/개발

post /save작성을 위한 시퀀스도

1. get: /write/, 2. dispatch url

urls.py

"""tutorial URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.2/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 django.contrib import admin
from django.urls import path, re_path
from community.views import *

urlpatterns = [
    path('admin/', admin.site.urls),
    path('write/', write, name='write'),
    path('save/', save, name='save'),
]

path('save/', save, name='save'): urlpatterns리스트에추가합니다. path안의 각 파라미터 갖는 의미는 write때와 동일합니다.

  • 첫번째 파라미터로 url주소를
  • 두번재 파라미터로 받은 요청을 어디로 전달할지
  • 세번재로는 내부적으로 사용되는 path명
    을 기입한다.

위 코드는 클라이언트로부터 받은 요청을 컨트롤러(views.py)의 save함수로 전달합니다.

3. send request

views.py

from django.shortcuts import render
from community.forms import *
# from django.views.decorators.csrf import csrf_exempt
# Create your views here.
# 사용자에게 들어온 요청을 받고, response를 반환함

def write(request):
    form = Form()
    return render(request, 'write.html', {'form':form})

# @csrf_exempt
def save(request):
    if (request.method == 'POST'):
        form = Form(request.POST)
        if form.is_valid():
            form.save()
            return render(request, 'save_complete.html')
        else:
            return render(request, 'save_not_complete.html')

def save(request) : /save/에서 오는 request를 처리하기 위해 새롭게 추가된 함수입니다.

django는 시큐리티쪽도 알아서 해줍니다. post송신을 할때, csrf토큰을 html로부터 받아 안전하게 post처리를 하게 하는데요. 안전하게 라는 단어의 의미가 애매하므로 csrf토큰이 무슨일을 하는지 다음의 블로그에서 확인해주세요.

 

[https://itstory.tk/entry/CSRF-%EA%B3%B5%EA%B2%A9%EC%9D%B4%EB%9E%80-%EA%B7%B8%EB%A6%AC%EA%B3%A0-CSRF-%EB%B0%A9%EC%96%B4-%EB%B0%A9%EB%B2%95]

 

그럼 html에서 csrf토큰을 언제발행했냐.. /save로 post를 보낸 write.html을 다시한번 보겠습니다.

 

write.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>write</title>

</head>
<body>
    <form action="/save/" method="post">
        {% comment %}
            {{ form.as_table }}
            {{ form.as_p }}
            {{ form.as_ul }}
        {% endcomment %}
        {{ form.as_p }}
        {% csrf_token %}

        <button type="submit" class="btn btn-primary">저장</button>
    </form>
</body>
</html>

{% csrf_token %} 이 라인을 통해 csrf토큰을 발행해 save로 포트스할 수 있었던 겁니다!

 

만약 html쪽에서 csrf토큰을 발행하지 않으면, 렌더링 오류 페이지가 발생하는데요. csrf토큰이 없어서 페이지를 렌더링 못하겠다는 오류가 발생합니다. 이때의 해결방법은 다음과 같습니다.

 

controller의 해당 함수에 @csrf_exempt 어노테이션을 추가하여 csrf토큰이 없어도 렌더링 할 수 있도록한다.(현재 소스코드에서 코멘트아웃되어 있는부분)

4. create form data, 5. return form data

views.py

from django.shortcuts import render
from community.forms import *
# from django.views.decorators.csrf import csrf_exempt
# Create your views here.
# 사용자에게 들어온 요청을 받고, response를 반환함

def write(request):
    form = Form()
    return render(request, 'write.html', {'form':form})

# @csrf_exempt
def save(request):
    if (request.method == 'POST'):
        form = Form(request.POST)
        if form.is_valid():
            form.save()
            return render(request, 'save_complete.html')
        else:
            return render(request, 'save_not_complete.html')

forms.py

from django.forms import ModelForm
from community.models import *

class Form(ModelForm):
    class Meta:
        model = Article
        fields = ['name', 'title', 'contents', 'url', 'email']

form = Form(request.POST) : request로 받아온 post정보를 Form의 인자로 넘기면 form오브젝트를 반환합니다. form오브젝트는 ModelForm은 상속하고 있으므로 그 아래있는 다양함 함수에 접근할 수 있는 인스턴스입니다.

 

6. check form data validation

form.is_valid() : 인스턴스가 접근할 수 있는 편리한 함수중 하나가 is_valid()함수입니다. post로 가져온 request 정보가 Article DB에 insert되게 적합한 정보를 모두 가지고 있는지 아닌지 validation을 수행합니다..

 

7. insert to DB

form.save() : 장고에선 sql을 전혀 사용할 필요가 없다는 걸 깨닳았습니다. 이 라인하나로 post로 가져온 request 정보를 Article DB에 inser해주게 됩니다.

 

8. rendering html as response

작업이 모두 끝났다면, 그에 맞는 html을 반환합니다.

 

save_complete.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>write</title>
</head>
<body>
    <h4>save completes!</h4>
</body>
</html>

save_not_complete.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>write</title>
</head>
<body>
    <h4>save fails</h4>
</body>
</html>

insert작업이 성공적으로 마무리 되었다면, save_complete.html를 아니라면, save_not_complete.html를 반환하도록 두개의 html을 만들어줍시다..

 

여기까지가 get write/와 post save/의 끝입니다.

+ Recent posts