이번에는 django를 본격적으로 사용하는 포스트인데 실제 프로젝트를 하기 앞서 먼저 HTTP 통신으로 데이터를 주고받을 수 있는 가능한 간단한 프로젝트를 진행해 보고 알고 있는 내용을 체크해 본다.
([virtualenv_name]) $ django-admin startproject simple_end
(가상환경을 설정할 수 있다면 설정한 상태에서) 위와 같은 명령어로 프로젝트를 만들게 되면
simple_end
├── manage.py
└── simple_end
├── __init__.py
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py
위와 같은 형태의 트리 구조의 django project를 생성할 수 있다. 여기서 대표적으로 project를 만들어 내기 위해 다음 3개의 프로젝트 파일에 대해서 작업을 수행한다.
urls.py
: URLConf 파일(URL부분을 정의해주는 부분), 필자는 여기서 / 위치 때문에 종종 실수(패턴주의)settings.py
: django project 관련 설정 정보를 담고 있는 파일models.py
: Django ORM을 이용하여 DB 테이블을 정의하는 파일views.py
: 화면에 보여줄 수 있도록 데이터의 처리 및 로직을 수행하는 파일(이번 포스트의 핵심이 됨)** 이 프로젝트에서 화면을 나타내는 template을 만들지 않는 이유 : 주로 data의 모델링 HTTP 통신 규격에 맞춰 주고 받는 api를 만들 것이며 화면을 나타내는 부분은 전적으로 Front-end에 의존한다고 가정하기 때문
해당 프로젝트 경로로 들어가서 본격적으로 endpoint app을 만든다.
$ python manage.py startapp endpoint
endpoint
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
views.py
를 열어 다음과 같이 작성한다(class 기반의 view 작성)
# endpoint/views.py
import json
from django.views import View
from django.http import JsonResponse
class MainView(View):
# get 요청 처리를 위한 View 클래스의 get메소드 overriding
def get(self, request):
return JsonResponse({"Hello":"World"}, status=200)
# endpoint/urls.py
from django.urls import path
from main.views import MainView
# django에서 경로를 명시할 때 urlpatterns 리스트의 패턴을 항상 참고한다
urlpatterns = [
# as_view()메소드의 역할 : 해당 view의 주소를 호출하면 HTTP method가 GET인지 POST인지 DELETE인지
# UPDATE인지 판별해서 그에 맞는 함수를 실행
path('', MainView.as_view())
]
# simple_end/urls.py
from django.urls import path, include
urlpatterns = [
path('endpoint', include('endpoint.urls'))
]
class 기반 view안에 get(HTTP GET 역할을 하는 메소드라 생각하면 된다)을 정의하고 일단 url을 통해 해당 view에 GET
요청을 할 시 Hello World가 화면에 보여지게 구성했다 실제 결과는 아래와 같다.
결과를 확인하기 위한 서버 구동
$ python manage.py runserver
실제 localhost:8000(runserver 기본값)에 찍혀진 내용
{"Hello": "World"}
httpie
를 통한 HTTP 통신 요청 예제
$ http -v http://127.0.0.1:8000/endpoint
GET /endpoint HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: 127.0.0.1:8000
User-Agent: HTTPie/2.0.0
HTTP/1.1 200 OK
Content-Length: 18
Content-Type: application/json
Date: Fri, 07 Feb 2020 12:14:39 GMT
Server: WSGIServer/0.2 CPython/3.8.1
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"Hello": "World"
}
결과를 설명하자면 이렇다
MainView
의 get()
메소드를 통해 JSON 형태인 "Hello": "World"를 리턴하게 된 것이다view.py
부분을 통신을 하는 끝단 즉 endpoint라고 정의한다해당 예제는 가장 간단하게 구현할 수 있는 django 기반의 endpoint가 된다.
HTTP메소드를 전송할 수 있는 쉘 프로그램 아래와 같이 설치하고 사용하면 된다
# Ubuntu
$ sudo apt-get install httpie
# Macos(homebrew)
$ brew install httpie
$ http -v <http-전송을 요청할 URL>
$ http -v <http-전송을 요청할 URL> [data1]=[value1] [data2]=[value2] ... ...
이제는 가상이지만 계정을 만들어 로그인 하는 상황을 가정해 보자. 회원 정보를 담을 테이블을 다음과 같이 models.py
에 정의한다. 아래처럼 정의했다.
from django.db import models
class Users(models.Model):
name = models.CharField(max_length=50)
email = models.CharField(max_length=50)
password = models.CharField(max_length=300)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# 그전에 settings.py에서 INSTALLED_APP 항목에 만들어 준 app을 추가해준다
# 그렇지 않으면 새롭게 만들어진 app이 제대로 인식되지 않아 data modeling이 진행되지 않는다
# settings.py 중 일부
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'endpoint', # 이 부분을 추가해 준다.
]
위와 같이 만든 후 아래와 같은 명령어를 통해 migration을 진행해 준다.
$ python manage.py makemigrations
Migrations for 'endpoint':
endpoint/migrations/0001_initial.py
- Create model Users
$ python manage.py migrate endpoint 0001
Operations to perform:
Target specific migration: 0001_initial, from endpoint
Running migrations:
Applying endpoint.0001_initial... OK
다음으로 만든 모델을 기반으로 post()
가 가능한 View제작을 해본다.
import json
from django.views import View
from django.http import JsonResponse
from .models import Users
class MainView(View):
def post(self, request):
# HTTP 통신으로 전달 받은 데이터를 json의 형태로 load하는 함수
data = json.loads(request.body)
Users(
name = data['name'],
email = data['email'],
password = data['password']
).save()
# 제대로 응답처리 했다면 200 status와 함께 성공 메세지 전달
return JsonResponse({'message':'SUCCESS'}, status=200)
def get(self, request):
return JsonResponse({'Hello':'World'}, status=200)
새롭게 제작한 부분이 post()
부분인데 받아온 데이터를 Users라는 모델 클래스의 형태로 모델 객체를 생성하여 저장하고 저장이 제대로 완료 되었다면 성공했다는 메세지를 띄우게 구성했다.
위에 POST 메소드 작성 예제를 참고하여 POST 테스트를 해보았을 때 정상적인 결과가 출력된다면 아래와 같이 나온다
February 07, 2020 - 12:52:28
Django version 3.0.2, using settings 'simple_end.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Forbidden (CSRF cookie not set.): /endpoint
[07/Feb/2020 12:52:53] "POST /endpoint HTTP/1.1" 403 2864
위와 같이 에러가 뜰텐데 CSRF cookie 처리가 되지않은 데이터가 post가 일어나기 때문에 django 웹서버에서 사전 차단한 것이다. 그렇다면 왜 뭐 때문에 저런 설정이 어디 되어 있단 말인가 ? 답은 settings.py
에 있었다.
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware', <- 이 부분 주석처리
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
django는 csrf 보안 사고를 막기 위해 데이터를 받아올 때 'django.middleware.csrf.CsrfViewMiddleware'
라는 middleware 모듈이 해당 문제를 자동으로 처리해 주게 되어 있다. 일단 endpoint 통신 자가 테스트를 하는 와중이므로 과감하게 주석 처리해 주면 된다.
그러고 나서 httpie를 통해 post()
메소드 작동을 체크해보면 다음과 같다.
$ http -v http://127.0.0.1:8000/endpoint name=test email=test@test.com password=1234
POST /endpoint HTTP/1.1
Accept: application/json, */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 62
Content-Type: application/json
Host: 127.0.0.1:8000
User-Agent: HTTPie/2.0.0
{
"email": "test@test.com",
"name": "test",
"password": "1234"
}
HTTP/1.1 200 OK
Content-Length: 22
Content-Type: application/json
Date: Fri, 07 Feb 2020 13:10:36 GMT
Server: WSGIServer/0.2 CPython/3.8.1
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"message": "SUCCESS"
}
정상적으로 POST 응답이 진행되는 것을 확인할 수 있다. 그렇다면 POST(DB에)한 데이터를 get()
메소드를 통해 다시 한 번 가져와 보자.
# endpoint/views.py
# 데이터를 받아오기 위해 아래와 같이 수정
def get(self, request):
user_data = Users.objects.values() # ORM 메소드를 통해 DB에서 데이터를 가져옴
return JsonResponse({'users':list(user_data)}, status=200)
GET /endpoint HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: 127.0.0.1:8000
User-Agent: HTTPie/2.0.0
HTTP/1.1 200 OK
Content-Length: 168
Content-Type: application/json
Date: Fri, 07 Feb 2020 13:20:20 GMT
Server: WSGIServer/0.2 CPython/3.8.1
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"users": [
{
"created_at": "2020-02-07T13:10:36.976Z",
"email": "test@test.com",
"id": 1,
"name": "test",
"password": "1234",
"updated_at": "2020-02-07T13:10:36.976Z"
}
]
}
정상적으로 가져 왔다면 위와 같은 결과를 얻어올 수 있다.