장고 프로젝트를 하면서 이제는 장고를 어느정도 다룰 줄 아는 실력이 된 것이 아닌가 싶었다. 그런데 다른 새로운 프로젝트를 시작하려고 봤더니 괜히 불안했다. ‘처음 시작은 어떻게 하더라?’, ‘사용했던 패키지를 다 기억할 수 있을까?’ 싶은 생각이 들었기 때문인 것 같다. 그러다 필자가 예전에 작성한 장고 시작하기!라는 글을 보는데 당시에는 이런 개발 순서가 맞다고 생각했지만 의식의 흐름으로 전개하는 것 같아서 너무 어수선해 보였다.

장고를 입문하기 어려워하는 사람들이 많은데 (입문만 좀 어려웠지 좀만 하다보면 실력이 확 늘어난다) 이번 기회에 개인적으로 공부도하고 입문하는 사람들에게 전체적인 맥락을 보여주기 위해서 다시 글을 써보고자 한다.


장고, 분노의 질주

1
2
3
[ terminal ]
pip install virtualenv
pip install venv # 위 명령어가 실패하는 경우

우선 파이썬의 pip로 장고가 아닌 virtualenv(이하 venv)를 먼저 설치해야 한다. 이유는 간단한데 장고의 버전은 많다. 앞으로는 더 많아 질 것이다. venv는 해당 프로젝트가 개발된 환경과 똑같은 환경을 만들기 위함이라 보면된다.

1
2
[ terminal ]
python -m venv mvenv

위 명령어로 가상환경을 만들고 윈도우의 경우에는 위 명령어로 리눅스의 경우에는 아래 명령어로 가상환경을 실행한다.

1
2
3
4
5
[ windows - terminal ]
call mvenv\Scripts\activate

[ linux - terminal ]
source mvenv/bin/activate

이후 장고를 설치한다.

1
2
3
[ terminal ]
pip install django~=2.0.0
django-admin startproject main

장고를 설치하면 위처럼 django-admin이라는 명령어를 사용할 수 있다. 해당 명령어를 사용하여 프로젝트를 생성할 수 있다. 이제 생성된 main 디렉터리로 이동하자. 해당 디렉터리에는 manage.py가 존재할 것이다. 그럼 아래 명령어로 서버를 실행해보자.

1
2
[ terminal ]
python manage.py runserver

이제 자신의 웹브라우저를 실행하여 아래 주소를 검색창에 입력하자.

1
2
[ chrome > url ]
http://127.0.0.1:8000

아마도 반가운 장고 페이지가 여러분을 맞이해 줄 것이다!


몸풀기는 끝났고

본격적으로 시작하자. 이 글에선 간단한 ToDoList 페이지를 생성할 것이다.

장고 프로젝트를 생성하면 기어코 가장 먼저 해주어야 할 일들은 셋팅 파일을 설정하는 것이다. 아래 소스와 같이 시간은 아시아-서울로 변경하고 언어는 한국어로 변경해주자.

1
2
3
4
5
6
[ main/settings.py ]
TIME_ZONE='Asia/Seoul'
LANGUAGE_CODE='ko'
...
STATIC_URL='/static/'
STAITC_ROOT=os.path.join(BASE_DIR,'static')

STATICcssjs와 같은 정적 파일을 의미하는데 현재 글에서는 다루지 않겠지만 반드시! 반드시! 알아두어야 하는 설정이다. 본격적으로 자신의 앱을 생성하자. 실질적으로 투두리스트가 구동될 소스를 생성하는 과정이다.

1
2
[ terminal ]
django-admin startapp todolist

이후 다시 셋팅을 설정해 주어야 한다.

1
2
3
4
5
6
7
8
9
10
[ main/settings.py ]
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todolist',
]

셋팅 파일을 살펴보면 INSTALLED_APPS이라는 항목이 존재하는데 위 코드처럼 자신이 생성한 앱을 삽입해 주면 끝난다. 이제 자신의 앱에 사용자가 접속할 수 있도록 url을 만들어 줄 것이다. 먼저 main 디렉터리에 존재하는 urls.py 파일을 아래와 같이 설정한다.

1
2
3
4
5
6
7
8
[ main/urls.py ]
from django.contrib import admin
from django.urls import path, include

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

urls.py는 해당 url로 접근하면 어떤 동작을 수행할 것인가를 기록하는 것이다. 루트로 접근하면 투두리스트의 urls.py에 명시한대로 처리한다. 현재는 admin을 제외한 모든 접근이 해당 파일로 넘어가게 된다. 그리고 다음은 투두리스트의 urls.py에서 실질적인 동작을 명시하자.

1
2
3
4
5
6
7
8
[ todolist/urls.py ]
from django.contrib import admin
from django.urls import path, include
from . import views

urlpatterns = [
    path('', views.index, name='index'),
]

루트 페이지에 접근했을때 views.pyindex 함수를 실행시킨다고 설정하였다. 그럼 해당 파일에서 index 함수를 정의하도록 하자.

1
2
3
4
5
[ todolist/views.py ]
from django.shortcuts import render

def index(request):
    return render(request, 'todo/index.html', {})

index 함수는 해당 url에 접근한 사용자에게 todo/index.html이라는 파일을 그려주는 역할을 한다. 하지만 해당 파일은 존재하지 않으므로 생성이 필요하다.

1
2
3
4
5
6
7
8
9
10
11
12
[ todolist/templates/todo/index.html ]
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello</title>
</head>
<body>
    <h1>HELLO</h1>
</body>
</html>

투두리스트 앱의 디렉터리의 templates라는 디렉터리를 생성했는데 사용자에게 그려줄 모든 html 파일(템플릿)은 이곳에 넣는다. 그럼 다시 서버를 실행하여 해당 페이지가 잘 뜨는지 확인해보자.

1
2
[ terminal ]
python manage.py runserver


잠시 쉬는 타임

뭐가 뭔지 잘 모르는 것 같더라도 처음부터 마냥 쉬운 프레임워크 혹은 라이브러리는 없다. 쓰다보면 익숙해 진다. 여하지간 위 과정을 잘 이해하면 장고는 점점 쉬워질 것이다.

  • 사용자가 접근할 URL 생성
  • 해당 URL이 수행할 함수 생성
  • 해당 함수의 반응(RENDER, RIDRECT, RESPONS) 생성


모델 만들기

CRUDCREATE, READ, UPDATE, DELETE를 의미한다. 장고에선 이 모든 과정이 정말 쉬운데 이 글에서도 간단히 CREATE, DELETE를 다뤄볼 것이다. 먼저 사용할 모델이 필요하다.

투두리스트의 경우에는 내용과 생성된 날짜, 완료된 날짜, 해당 리스트가 완료되었는지 점검할 필요가 있다. (사실 이 글에선 내용과 생성 날자만 사용하고 있음)

1
2
3
4
5
6
7
8
9
10
11
12
[ todolist/models.py ]
from django.db import models
from django.utils import timezone

class TodoItem(models.Model):
    content = models.CharField(max_length=200)
    created_date = models.DateTimeField(default=timezone.now)
    clear_date = models.DateTimeField(default=timezone.now)
    is_clear = models.BooleanField(default=False, blank=True)
    
    def __str__(self):
        return self.content

필자의 경우에는 위처럼 생성했는데 장고에서 모델의 필드가 정말 다양하고 많으며 옵션도 다양하다. 이는 SQL 지식과 연계되며 너무 정보가 많아 장고의 공식 문서를 참고하면 좋을 듯 하다. https://docs.djangoproject.com/en/2.2/ref/models/fields/

여하지간 모델을 생성하면 장고에서는 마이그레이션이라는 작업이 필요하다. 장고에서는 데이터 베이스를 ORM으로 알아서 매핑해 주는데 이것이 마이그레이션이다. ORM은 데이터를 객체처럼 다룰 수 있게 해주는 도구다.

1
2
3
[ terminal ]
python manage.py makemigrations todolist
python manage.py migrate todolist

모델을 추가한 경우 위처럼 makemigrations로 모델의 변경을 알려주고 migrate로 모델을 추가한다.


관리자 페이지

장고에선 관리자 페이지를 제공해준다. 더구나 관리자 페이지는 사용하기 충분하게 만들어져 있어서 관리자 페이지 때문에 장고를 사용하는 경우도 있다고 알려져 있다. 우리는 직접 CREATE, DELETE를 구현할 거지만 지금은 관리자 페이지를 통해서 간단하게 데이터를 생성해 볼 것이다.

1
2
3
4
5
[ main/admin.py ]
from django.contrib import admin
from.models import TodoItem

admin.site.register(TodoItem)

관리자 페이지를 통해서 추가, 제거를 할 모델을 위처럼 추가하자. 그리고 또 한번의 migrate가 필요한데 관리자 페이지를 사용할 유저 모델은 장고에 이미 기본적으로 탑재되어 있기 때문에 migrate이후에 슈퍼유저를 생성할 것이다.

1
2
3
4
[ terminal ]
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
1
2
[ chrome > url ]
http://127.0.0.1:8000/admin

이제 아래와 같이 모델을 직접 추가해보자.

어드민 페이지

어드민 페이지

어드민 페이지


모델 오브젝트

쿼리문을 사용하지 않는데 어떻게 모델 데이터를 불러올 수 있을까? 장고에서 정의한 방법을 사용해야 한다. 아래 코드에서는 우리가 생성한 모델의 데이터를 불러오는 과정이다. 아래는 order_by 함수밖에 사용하지 않았지만 filterexclude가 존재하므로 필요할때 검색 고고!

1
2
3
4
5
6
7
[ todolist/views.py ]
from django.shortcuts import render
from .models import TodoItem

def index(request):
    items = TodoItem.objects.order_by('created_date').reverse()
    return render(request, 'todo/index.html', {'items' : items})

또한 'items' : items와 같이 html 템플릿 파일에 파라미터를 넘겨주는 것을 볼 수 있다. 해당 자료구조는 파이썬의 딕셔너리 자료구조를 사용한 것이므로 잘 모르겠다 싶으면 위키 독스 - 점프 투 파이썬 - 딕셔너리를 참고해 보도록 하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[ todolist/templates/todo/index.html ]
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello</title>
</head>
<body>
    {% for item in items %}
        <p>{{ item.content }} / {{ item.created_date }}</p>
    {% endfor %}
</body>
</html>

딕셔너리로 전달한 파라미터는 위와같이 {% 동작 %} {{ 변수 }} {% 동작끝 %}을 통해서 사용할 수 있다.

다시 서버를 실행하여 인덱스 페이지를 열어보면 아래와 같은 결과가 발생했을 것이다.

1
2
django 공부하기 그리고 블로그에 업로드 / 2019년 10월 10일 6:04 오후
django 공부하기 / 2019년 10월 10일 6:03 오후


직접 CREATE 하기

어드민 페이지를 통해서 CREATE를 했었고 솔직히 말하면 어드민 페이지를 통해서 CRUD를 진행하고 템플릿에선 예쁘게 뿌려주기만 해도 될 것 같다. 하지만 투두리스트의 생명은 빠르게 리스트를 생성하고 삭제하는 것이다. 그것도 단일 페이지에서!

먼저 직접 데이터를 CURD 하기 위해서 장고에서 제공해주는 도구들이 정말 많고 유용하다. 애초에 장고가 그런 의미로 태어났으니 말이다. forms.py라는 파일을 생성하여 이곳에서 우리가 사용할 모델을 만들어줄 폼을 생성시킬 수 있다.

1
2
3
4
5
6
7
8
[ todolist/forms.py ]
from django import forms
from .models import TodoItem

class ItemForm(forms.ModelForm):
    class Meta:
        model = TodoItem
        fields = ('content',)

위처럼 모델을 선택하고 필드를 선택하여 간단히 폼을 만들었다.

1
2
3
4
5
6
7
8
9
[ todolist/views.py ]
from django.shortcuts import render
from .models import TodoItem
from .forms import ItemForm

def index(request):
    items = TodoItem.objects.order_by('created_date').reverse()
    form = ItemForm()
    return render(request, 'todo/index.html', {'items' : items, 'form' : form})

그리고 해당 폼은 뷰에서 import 하여 모델 오브젝트와 마찬가지로 딕셔너리 형태로 보낼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[ todolist/templates/todo/index.html ]
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello</title>
</head>
<body>
    <form method="POST">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">추가</button>
    </form>
    {% for item in items %}
        <p>{{ item.content }} / {{ item.created_date }}</p>
    {% endfor %}
</body>
</html>

그럼 템플릿에선 위와같이 그려준다. 처음엔 장고가 복잡해 보였지만 슬슬 쉬워지고 있지 않나? 다만 폼을 사용할때는 csrf_token이라는 토큰을 사용한다. 이는 csrf 공격을 막기위한 것인데 장고에선 이를 한 줄의 명령어로 막을 수 있게 도와준다!

CSRF 공격이란? CSRF 공격(Cross Site Request Forgery)은 웹 어플리케이션 취약점 중 하나로, 인터넷 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 만드는 공격이다

폼 페이지

이제 위와같은 화면으로 보일텐데 지금은 추가 버튼을 눌러도 아무런 동작을 수행하지 않는다. 우리가 아무런 기능을 추가해주지 않았기 때문이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[ todolist/views.py ]
from django.shortcuts import render
from .models import TodoItem
from .forms import ItemForm

def index(request):
    if request.method == 'POST':
        form = PostForm(request.POST)
        if form.is_valid():
            item = form.save(commit=False)
            item.save()
    items = TodoItem.objects.order_by('created_date').reverse()
    form = ItemForm()
    return render(request, 'todo/index.html', {'items' : items, 'form' : form})

위와같이 POST로 전달되어 index 페이지를 보는 경우에는 다른 수행을 하도록 제공을 해주어야 정상적으로 아이템을 추가할 수 있다.

  1. 폼에 전송된 데이터를 담는다.
  2. 유효한지 검사한다.
  3. 유효하면 아이템에 넣는다.
  4. 아이템을 저장한다.

이제 아래와 같이 내용을 적고 추가를 누르면 아이템이 추가되는 것을 볼 수 있다.


직접 DELETE 하기

각각의 아이템 옆에 ‘삭제’ 버튼을 두고 버튼을 누를 경우 아이템을 삭제할 것이다. URL로 접근했을때 특정 아이템을 삭제하도록 해보자.

1
2
3
4
5
6
7
8
9
[ todolist/urls.py ]
from django.contrib import admin
from django.urls import path, include
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('item/<pk>/delete', views.item_delete, name='item_delete')
]

사용자가 item/1/delete라는 URL로 접근하면 고유키가 1인 아이템을 삭제할 것이다. 장고에서는 위와같이 <int:pk>, <name> 등을 활용해서 URL에서 파라미터를 받을 수 있다. 그럼 해당 URL에 반응하는 함수를 구현하자.

1
2
3
4
5
6
7
8
9
[ todolist/views.py ]
from django.shortcuts import render, redirect

...

def item_delete(request, pk):
    item = TodoItem.objects.get(pk=pk)
    item.delete()
    return redirect('index')

redirect라는 추가적인 라이브러리를 import하고 위와 같이 pk를 파라미터로 전달받고 해당 번호로 아이템을 찾아서 해당 아이템을 delete 함수를 통해 삭제시킬 수 있다. 이후에는 index 페이지로 리다이렉션 해준다. 그럼 이제 각각의 삭제 링크를 아이템 옆에 달아줘야 하는데 이는 템플릿에서 간단하게 생성시킬 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[ todolist/templates/todo/index.html ]
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello</title>
</head>
<body>
    <form method="POST">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">추가</button>
    </form>
    {% for item in items %}
        <p>{{ item.content }} : <a href="{% url 'item_delete' pk=item.pk %}">삭제</a></p>
    {% endfor %}
</body>
</html>

위와같이 아이템의 pk를 넘겨주는 url을 위처럼 간단하게 생성시킬 수 있다.

WRITTEN BY

배진오

소비적인 일보단 생산적인 일을 추구하며, 좋아하는 일을 잘하고 싶어합니다 :D
im@baejino.com