Django와 mongoDB를 이용한 RESTful API 제작 튜토리얼

이동건·2021년 4월 2일
4

Django

목록 보기
1/4

Django(장고)

Django의 역사

장고(Django)는 2003년과 2004년에 로렌스 저널-월드라는 신문사의 인턴 웹 프로그래머였던 에이드리안 홀로바티와 사이먼 윌리슨이 파이썬 이용해 애플리케이션을 만들기 시작하면서 처음 개발되었다. 홀로바티와 윌리슨은 PHP가 규모가 큰 웹사이트에 적합하지 않다고 생각하였고 이를 계기로 파이썬을 웹 개발 언어로 사용하기로 결심했습니다. 하지만 규모가 큰 웹 개발에 적합한 파이썬 도구가 없다는 것을 깨닫고 그들은 장고를 개발하게 되었습니다.

Django의 특징

MTV

장고의 개발 방식은 MTV (Model-Template-View)에 따라 진행된다.
MTV는 자바의 MVC 방식과 거의 동일한 개념이다.
테이블을 정의하는 모델(Model), 사용자가 보게 될 화면을 정의하는 템플릿(Template), 애플리케이션의 제어 흐름 및 처리 로직을 정의하는 뷰(View)로 구분한다.
여기서 MVC와의 차이점을 살펴보자면

MVC의 뷰가 템플릿으로, 컨트롤러가 뷰로 바뀐 것이라는 걸 알 수 있다.

MTV로 나눠서 개발을 진행하면 각자의 독립성 유지가 가능하고 소프트웨어 개발의 중요한 원칙인 느슨한 결합 설계의 원칙에도 부합한다.

장고에서 startproject, 혹은 startapp을 실행하면 자동으로 프로젝트 뼈대에 해당하는 디렉토리와 파일들을 만들어준다.
모델은 model.py에, 템플릿은 templates 디렉토리 하위의 html파일에, 뷰는 view.py파일에 작성된다.

애플리케이션을 MTV 방식으로 개발할 수 있도록 장고가 배려해 주는 것이다.
즉, 개발자는 개발만 하면 된다는 것이다.

MTV 코딩 순서

무엇부터 코딩해야 하는가에 대한 정해진 순서는 없다. 하지만 화면 설계는 뷰와 템플릿이 필요하고 테이블은 모델 코딩에 반영되므로 독립적으로 개발할 수 있는 모델을 먼저 하고 뷰와 템플릿은 이후에 같이 하는 것이 일반적이다.

프로젝트 뼈대 만들기 - 모델 코딩하기 - URLconf 코딩하기 - 뷰 코딩하기 - 템플릿 코딩하기

순서가 일반적이다.

settings.py

settings.py는 프로젝트 설정 파일이다.
데이터베이스 설정, 템플릿 항목 설정, 정적 파일 항목 설정, 애플리케이션 등록, 타임존 지정 등을 수정해 주어야 한다.

models.py

models.py는 테이블을 정의하는 파일이다. 장고는 데이터베이스 처리를 ORM(Object Relation Mapping) 기법을 사용한다. 테이블을 클래스로 매핑해서 테이블에 대한 CRUD 기능을 클래스 객체에 대해 수행하면, 장고가 내부적으로 데이터베이스에 반영해주는 방식이다.

장고에서는 테이블을 하나의 클래스로 정의하고, 테이블 컬럼은 클래스의 변수(속성)로 매핑한다.
테이블 클래스는 django.db.models.Model 클래스를 상속받아 정의하고, 각 클래스 변수의 타입도 장공에서 미리 정의한 필드 클래스를 사용한다.

테이블의 생성이나 정의 변경 등 models.py 파일에서 데이터베이스 변경 사항이 발생하면 이를 실제로 반영해주는 작업이 필요한데, 이것이 마이그레이션(migration)이다.

마이그레이션은 테이블 및 필드의 생성, 삭제, 변경 등과 같이 데이터베이스에 대한 변경 사항을 알려주는 정보이다.

애플리케이션 디렉토리 별로 migrations/ 디렉토리 하위에 마이그레이션 파일들이 존재한다.

url.py

URLconf는 URL과 뷰를 매핑해주는 urls.py파일을 말한다.
일반적으로 프로젝트 전체 URL을 정의하는 프로젝트 URL과 앱마다 정의하는 앱 URL의 2계층으로 나누는 방식을 사용한다.

views.py

views.py는 뷰 로직을 코딩하는 파일이다.
장고에서는 함수로 코딩할 것이지, 클래스로 코딩한 것이지에 따라 함수형 뷰와 클래스형 뷰로 구분된다. 클래스 사용을 권장한다.

templates

웹 화면 별로 템플릿 파일이 하나씩 필요하다. 그래서 여러 템플릿 파일을 작성하게 되고, 이런 템플릿 파일들을 한 곳에 모아두기 위한 템플릿 디렉터리가 필요하게 된다.

템플릿 디렉토리는 프로젝트 디렉토리와 앱 디렉토리로 구분해서 사용한다. 프로젝트 디렉토리는 TEMPLATES 설정의 DIRS 항목에 지정된 디렉토리가 된다. 앱 템플릿 디렉토리는 각 애플리케이션 디렉토리마다 존재하는 templates/ 디렉토리가 된다.

프로젝트 디렉토리에는 base.html 등 전체 프로젝트의 룩앤필에 관련된 파일들을 모아두고, 각 앱에서 사용되는 템플릿 파일들은 앱 템플릿 디렉토리에 위치시킨다.

장고의 템플릿 서치 순서는 템플릿 디렉토리 다음 앱 템플릿 디렉토리의 순서이며, 앱 템플릿 디렉토리 내에서도 INSTALLED_APPS 설정 항목 순서대로 서치하게 된다.

Admin 사이트

Admin 사이트는 테이블의 내용을 열람하고 수정하는 기능을 제공한다. 테이블의 내용들을 컨텐츠라고 하는데, 이 컨텐츠를 편집하는 기능을 제공한다.

이 Admin 사이트에서 테이블에 대한 입력, 수정, 삭제 작업이 가능한다. 기본적으로 User와 Group 테이블이 보이는 것은 이미 settings.py에 django.contrib.auth 애플리케이션이 등록되어있기 때문이다.

Admin 기능은 자주 사용되는 편리한 기능으로, SQL 없이 테이블을 확인하고 레코드 입력 수정이 가능해진다.
Admin 사이트에 원하는 테이블을 등록하기 위해서는 admin.py 파일에 작업하면 된다.

개발용 웹 서버 - runserver

개발용 웹 서버는 runserver를 통해 웹 서버를 제공한다.
하지만 실제 상용화를 위해서는 Apache 또는 Nginx 등의 상용 웹 서버를 사용해야 한다.
runserver는 웹 서버에 비해 처리능력이 떨어지며 보안도 취약하다.

앱을 만들기 위해서 설계를 진행해야 한다.

우선 테이블 설계를 진행한다.
필드명, 타입, 제약조건, 설명을 생각한다.

그 후 로직을 설계한다.
URL은 어떤 걸 쓰고 해당 URL은 어떤 View를 거치며 어떤 Template으로 생성될 것인가를 생각한다.

Django와 mongoDB를 이용한 RESTful API 제작 튜토리얼

개발 환경은 centos7 이다.

우선 python3를 설치한다.

[root@dg-django ~]# yum install -y python3

그 후 django를 실습할 디렉토리를 생성해 준다.

[root@dg-django ~]# cd /root/
[root@dg-django ~]# mkdir django
[root@dg-django ~]# cd django/
[root@dg-django django]#

그 안에서 가상 환경을 생성해 준다.

[root@dg-django django]# python3 -m venv env
[root@dg-django django]# ls
env

env를 이용해 가상환경을 activate 시켜준다.

[root@dg-django django]# source env/bin/activate
(env) [root@dg-django django]#

그 후 django와 djangorestframework를 설치해 준다.

(env) [root@dg-django django]# yum install -y django 
(env) [root@dg-django django]# pip install djangorestframework

그 후 startapp을 이용해 프로젝트를 생성해 준다.

(env) [root@dg-django django]# django-admin startproject DjangoRestApiMongoDB
(env) [root@dg-django django]# ls
DjangoRestApiMongoDB  env
(env) [root@dg-django django]# cd DjangoRestApiMongoDB/
(env) [root@dg-django DjangoRestApiMongoDB]# ls
DjangoRestApiMongoDB  manage.py

다음과 같이 DjangoRestApiMongoDB 프로젝트가 생성된 걸 확인할 수 있다.

그 후 INSTALLED_APPS를 수정해 준다.
'rest_framework'를 추가해 준다.

(env) [root@dg-django DjangoRestApiMongoDB]# cd DjangoRestApiMongoDB/
(env) [root@dg-django DjangoRestApiMongoDB]# ls
__init__.py  settings.py  urls.py  wsgi.py
(env) [root@dg-django DjangoRestApiMongoDB]# vi settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
]

그리고 settings를 변경하는 김에 time_zone도 바꿔준다.

TIME_ZONE = 'Asia/Seoul'

이어서 장고용 mongoDB인 djongo를 설치해 준다.

(env) [root@dg-django DjangoRestApiMongoDB]# pip install djongo

setting에서 database 부분을 변경해 준다.

vi settings
 DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

이 항목을 다음과 같이 변경한다.

DATABASES = {
    'default': {
        'ENGINE': 'djongo',
        'NAME': 'test_db',
        'HOST': '127.0.0.1',
        'PORT': 27017,
    }
}

이제 RESTful API를 만들 앱을 생성한다.

(env) [root@dg-django DjangoRestApiMongoDB]# cd ..
(env) [root@dg-django DjangoRestApiMongoDB]# python manage.py startapp rest_api

현재 다음과 같은 디렉토리 구조이다.

(env) [root@dg-django DjangoRestApiMongoDB]# tree .
.
├── DjangoRestApiMongoDB
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-36.pyc
│   │   └── settings.cpython-36.pyc
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
└── rest_api
    ├── admin.py
    ├── apps.py
    ├── __init__.py
    ├── migrations
    │   └── __init__.py
    ├── models.py
    ├── tests.py
    └── views.py

4 directories, 14 files

INSTALLED_APPS에 추가하기 위해 클래스 이름을 확인해 본다.

(env) [root@dg-django DjangoRestApiMongoDB]# cd rest_api/
(env) [root@dg-django rest_api]# ls
admin.py  apps.py  __init__.py  migrations  models.py  tests.py  views.py
(env) [root@dg-django rest_api]# vi apps.py

from django.apps import AppConfig


class RestApiConfig(AppConfig):
    name = 'rest_api'

settings에 추가해 준다.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_api.apps.RestApiConfig',
]

API 통신을 위해선 CORS가 필수적이다.
django-cors-headers를 설치해 준다.

(env) [root@dg-django DjangoRestApiMongoDB]# pip install django-cors-headers

그 후 이번에는 settings의 middleware에 추가해 준다.
CORS 부분을 추가해 준다.

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',
    # CORS
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
]

setting의 끝 부분에 다음의 사항을 추가로 작성해 준다.
모든 포트로 허용하고 싶다면 굳이 안 작성하여도 된다.

CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = (
    'http://localhost:8081',
)

CORS_ORIGIN_ALLOW_ALL: True면 모든 포트로 허용이 되며 밑의 whitelist가 필요 없다.
CORS_ORIGIN_WHITELIST: 다음의 포트로 오는 request는 허락해 준다는 뜻이 된다.

이제 모델을 작성한다.

(env) [root@dg-django DjangoRestApiMongoDB]# cd ..
(env) [root@dg-django DjangoRestApiMongoDB]# cd rest_api/
(env) [root@dg-django rest_api]# vi models.py

다음과 같이 튜토리얼이라는 클래스를 생성하고 그 안에 변수를 설정해 필드를 만들어 준다.

from django.db import models


class Tutorial(models.Model):
    title = models.CharField(max_length=70, blank=False, default='')
    description = models.CharField(max_length=200,blank=False, default='')
    published = models.BooleanField(default=False)

그 후 migration을 해 준다.

물론 이 전에 mongodb가 설치되어 있어야 한다. mongodb가 설치되어있지 않으면 27017 포트와 연결이 되지 않아 에러가 발생한다.

(env) [root@dg-django rest_api]# cd ..
(env) [root@dg-django DjangoRestApiMongoDB]# ls
DjangoRestApiMongoDB  manage.py  rest_api
(env) [root@dg-django DjangoRestApiMongoDB]# python manage.py makemigrations rest_api
Migrations for 'rest_api':
  rest_api/migrations/0001_initial.py
    - Create model Tutorial
(env) [root@dg-django DjangoRestApiMongoDB]#

rest_api의 migrations에 들어가 생성된 걸 확인해 본다.

(env) [root@dg-django DjangoRestApiMongoDB]# cd rest_api/
(env) [root@dg-django rest_api]# cd migrations/
(env) [root@dg-django migrations]# ls
0001_initial.py  __init__.py  __pycache__
(env) [root@dg-django migrations]# cat 0001_initial.py
# Generated by Django 3.0.5 on 2021-04-02 06:40

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Tutorial',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(default='', max_length=70)),
                ('description', models.CharField(default='', max_length=200)),
                ('published', models.BooleanField(default=False)),
            ],
        ),
    ]

다음과 같이 생성된 걸 볼 수 있다.
그리고나면 이제 migrate를 해 준다.

(env) [root@dg-django migrations]# cd ..
(env) [root@dg-django rest_api]# cd ..
(env) [root@dg-django DjangoRestApiMongoDB]# ls
DjangoRestApiMongoDB  manage.py  rest_api
(env) [root@dg-django DjangoRestApiMongoDB]# python manage.py migrate rest_api
Operations to perform:
  Apply all migrations: rest_api
Running migrations:
This version of djongo does not support "NULL, NOT NULL column validation check" fully. Visit https://nesdis.github.io/djongo/support/
  Applying rest_api.0001_initial... OK

그 후 serializer를 통해 모델을 관리하도록 해 준다.

(env) [root@dg-django DjangoRestApiMongoDB]# cd rest_api/
(env) [root@dg-django rest_api]# vi serializers.py
from rest_framework import serializers
from rest_api.models import Tutorial


class TutorialSerializer(serializers.ModelSerializer):

    class Meta:
        model = Tutorial
        fields = ('id',
                  'title',
                  'description',
                  'published')

이제 url 설정을 해 준다.

/api/tutorials: GET, POST, DELETE
/api/tutorials/:id: GET, PUT, DELETE
/api/tutorials/published: GET

다음의 형태로 제작해 보자.
프로젝트의 url을 다음과 같이 정의해 준다.

(env) [root@dg-django rest_api]# vi urls.py
from django.conf.urls import url 
from rest_api import views 
 
urlpatterns = [ 
    url(r'^api/tutorials$', views.tutorial_list),
    url(r'^api/tutorials/(?P<pk>[0-9]+)$', views.tutorial_detail),
    url(r'^api/tutorials/published$', views.tutorial_list_published)
]

urls는 이중 계층으로 나눈다.
프로젝트의 urls도 수정하여 준다.

(env) [root@dg-django rest_api]# cd ..
(env) [root@dg-django DjangoRestApiMongoDB]# cd DjangoRestApiMongoDB/
(env) [root@dg-django DjangoRestApiMongoDB]# vi urls.py
from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include('rest_api.urls')),
]

마지막으로 view를 정의해 준다.

(env) [root@dg-django DjangoRestApiMongoDB]# cd ..
(env) [root@dg-django DjangoRestApiMongoDB]# cd rest_api/
(env) [root@dg-django rest_api]# vi views.py
from django.shortcuts import render

from django.http.response import JsonResponse
from rest_framework.parsers import JSONParser 
from rest_framework import status
 
from rest_api.models import Tutorial
from rest_api.serializers import TutorialSerializer
from rest_framework.decorators import api_view


@api_view(['GET', 'POST', 'DELETE'])
def tutorial_list(request):
    if request.method == 'GET':
        tutorials = Tutorial.objects.all()
        
        title = request.GET.get('title', None)
        if title is not None:
            tutorials = tutorials.filter(title__icontains=title)
        
        tutorials_serializer = TutorialSerializer(tutorials, many=True)
        return JsonResponse(tutorials_serializer.data, safe=False)
        # 'safe=False' for objects serialization
 
    elif request.method == 'POST':
        tutorial_data = JSONParser().parse(request)
        tutorial_serializer = TutorialSerializer(data=tutorial_data)
        if tutorial_serializer.is_valid():
            tutorial_serializer.save()
            return JsonResponse(tutorial_serializer.data, status=status.HTTP_201_CREATED) 
        return JsonResponse(tutorial_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
    elif request.method == 'DELETE':
        count = Tutorial.objects.all().delete()
        return JsonResponse({'message': '{} Tutorials were deleted successfully!'.format(count[0])}, status=status.HTTP_204_NO_CONTENT)
 
 
@api_view(['GET', 'PUT', 'DELETE'])
def tutorial_detail(request, pk):
    try: 
        tutorial = Tutorial.objects.get(pk=pk) 
    except Tutorial.DoesNotExist: 
        return JsonResponse({'message': 'The tutorial does not exist'}, status=status.HTTP_404_NOT_FOUND) 
 
    if request.method == 'GET': 
        tutorial_serializer = TutorialSerializer(tutorial) 
        return JsonResponse(tutorial_serializer.data) 
 
    elif request.method == 'PUT': 
        tutorial_data = JSONParser().parse(request) 
        tutorial_serializer = TutorialSerializer(tutorial, data=tutorial_data) 
        if tutorial_serializer.is_valid(): 
            tutorial_serializer.save() 
            return JsonResponse(tutorial_serializer.data) 
        return JsonResponse(tutorial_serializer.errors, status=status.HTTP_400_BAD_REQUEST) 
 
    elif request.method == 'DELETE': 
        tutorial.delete() 
        return JsonResponse({'message': 'Tutorial was deleted successfully!'}, status=status.HTTP_204_NO_CONTENT)
    
        
@api_view(['GET'])
def tutorial_list_published(request):
    tutorials = Tutorial.objects.filter(published=True)
        
    if request.method == 'GET': 
        tutorials_serializer = TutorialSerializer(tutorials, many=True)
        return JsonResponse(tutorials_serializer.data, safe=False)

이제 실행해 본다.

python manage.py runserver 8080

그 후 8080 포트로 접속하면

다음과 같이 빈 배열이 오는 걸 확인할 수 있다.

데이터를 넣고 테스트를 진행 해 본다.

다음과 같이 postman을 사용하여 post로 데이터를 집어 넣어주면 response가 오는 걸 확인할 수 있다.

get으로 보내면 다음처럼 데이터가 오는 걸 확인할 수 있다.

이렇게 간단하게 Django에서 api를 제작하는 걸 진행해 보았다.
생각보다 스프링과 유사한 점이 많아 신기했고 스크립트 언어라고 무시할 필요가 없다는 사실을 느꼈다.

profile
코드를 통한 세계의 창조

0개의 댓글