Web/Django

Django로 웹 개발하기: todo_list

arsenic-dev 2025. 10. 29. 12:44

권태형 저자님의 '백엔드를 위한 DJANGO REST FRAMEWORK with 파이썬'을 기반으로 정리한 글입니다.

 

Chapter 3: Django로 Todo 목록 웹 서비스 만들기

1. Todo 목록 웹 서비스 시작하기

프로젝트 생성하기

가상 환경 세팅 -> 장고 설치 -> 장고 프로젝트 생성 -> todo 앱 생성

 

Todo 프로젝트 설정하기

settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todo',
]

TIME_ZONE = 'Asia/Seoul'

 

그리고 이후 개발에 필요한 파일들을 미리 만들어놓는다.

  • todo 앱 폴더 안에 templates 폴더를 만들고 그 안에 다시 todo 폴더 생성
  • todo 앱 폴더 안에 forms.py, models.py, urls.py 생성

 

마지막으로 관리자 계정을 생성한다.

 

Django가 내부 데이터베이스 테이블을 만들지 못한 상태에서 createsuperuser 명령을 실행하면 오류가 발생한다. 때문에 해당 오류 방지를 위해 migration을 적용해 Django가 필요한 모든 DB 테이블을 자동으로 생성할 수 있도록 해야 한다.

 

Todo 모델 생성하기

프로젝트의 첫 단계는 언제나 모델을 생성하는 것이다.

 

현재 필요한 필드를 가진 모델을 구현한 결과는 다음과 같다.

from django.db import models

class Todo(models.Model):
    title = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    created = models.DateTimeField(auto_now_add=True)
    completed = models.BooleanField(default=False)
    important = models.BooleanField(default=False)

    def __str__(self):
        return self.title

 

이제 아래 코드를 통해 모델에 대한 마이그레이션을 진행하면 모델 생성이 완료된다.

python manage.py makemigrations
python manage.py migrate

 

그리고나서 관리자 페이지에서 모델을 확인할 수 있도록, todo/admin.py에서 Todo 모델을 등록한다.

from django.contrib import admin
from .models import Todo

admin.site.register(Todo)

 

관리자 페이지에 접속하려면 url이 설정되어야 하기 때문에, mytodo/urls.py에서 path가 아래처럼 잘 설정되어 있는지 확인한다.

from django.contrib import admin
from django.urls import path, include

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

 

2. Todo 전체 조회 기능 만들기

Todo 전체 조회 기능 컨셉

첫 번째 페이지인 Todo 전체 조회 기능에서는 완료되지 않은, 즉 진행 중인 Todo만 보여주고 완료된 Todo는 다른 페이지에서 보여주도록 한다.

 

Bootstrap으로 좀 더 멋진 템플릿 만들기

Bootstrap은 가장 유명하고 오래된 CSS 프레임워크이다. 미리 스타일이 정의되어 있으며, 사용법이 간단하고 정갈한 결과물이 만들어지기 때문에 지금처럼 간단하게 꾸미고 싶은 경우 유용하게 활용할 수 있다.

 

Template

todo_list.html

<html>
    <head>
        <title>TODO 목록 앱</title>
        <Link
            rel="stylesheet"
            href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
        />
        <Link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/boot-strap-icons.css" />
    </head>
    <body>
        <div class="container">
            <h1>TODO 목록 앱</h1>
            <p>
                <a href=""><i class="bi-plus"></i>Add Todo</a>
                <a href="" class="btn btn-primary" style="float:right">완료한 TODO목록</a>
            </p>
            <ul class="list-group">
                {% for todo in todos %}
                <li class="list-group-item">
                    <a href="">{{todo.title}}</a>
                    {% if todo.important %}
                    <span class="badge badge-danger">!</span>
                    {% endif %}
                    <div style="float:right">
                        <a href="" class="btn btn-danger">완료</a>
                        <a href="" class="btn btn-outline-primary">수정하기</a>
                    </div>
                </li>
                {% endfor %}
            </ul>
        </div>
    </body>
</html>

 

 
 

Bootstrap 사용 법

1. <head> 안에 <link> 태그가 있고 여기에서 Bootstrap을 불러온다.

Bootstrap을 사용하기 위한 방법은 두 가지, 첫 번째 방법은 Bootstrap 홈페이지에서 css 파일들을 받아와 프로젝트 폴더에 집어넣는 방식이고, 두 번째 방식은 웹 링크 형태로 제공되는 css 파일을 가져다 쓰도록 참조시키는 방식이다.

 

성능 면에서는 첫 번째 방법이 당연히 더 좋겠지만 간단히 적용할 땐 웹 링크 방식이 편리하다. 그리고 이런 방식을 cdn(content delivery network) 방식이라고 한다.

 

2. 각 태그마다 class를 작성한다.

Bootstrap은 결국 미리 작성된 CSS이므로, CSS의 기본 작성 단위인 class를 따르고 있다. class를 쓰면 여러 태그를 한꺼번에 스타일링 가능한데, 이러한 class를 통해 Bootstrap에서 작성한 스타일을 각 태그마다 적용하고 있으며 이 class 이름은 Bootstrap이 미리 정의해놓은 값이다.

 

<a></a> vs <link />

  • <a>: 사용자가 클릭하는 링크
  • <link>: 브라우저가 참고하는 외부 리소스 연결

 

<link>의 rel 속성은 현재 문서와 외부 리소스 간의 관계(relationship)를 나타내는 속성이다.

  • stylesheet: 외부 css 파일을 연결

 

list 태그 예시

<ul>
  <li>사과</li>
  <li>바나나</li>
  <li>딸기</li>
</ul>

 

기타 태그 및 css 속성

  • <i></i>: icon 삽입시 사용하는 태그
  • <span></span>: inline으로 특정 부분만 스타일이나 클래스를 적용할 때 쓰는 태그
  • style="float: right": 요소를 오른쪽으로 띄워 배치하고, 다른 내용이 왼쪽으로 감싸지게 하는 CSS 속성

 

※ HTML Tag auto close 적용 안 될시, vscode 하단에 언어가 HTML로 잘 설정되어 있는지 확인하고 안 되어 있으면 HTML로 설정해야 한다.

 

View

완료되지 않은 Todo만 전달해야 하기 때문에 completed=False 옵션으로 필터링한다.

from django.shortcuts import render, redirect
from .models import Todo

def todo_list(request):
    todos = Todo.objects.filter(completed=False)
    return render(request, 'todo/todo_list.html', {'todos': todos})

 

URL

메인 페이지에서 Todo 목록을 보여줄 수 있도록 todo/urls.py를 아래와 같이 작성한다.

from django.urls import path
from . import views

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

 

또한, todo 앱 url을 프로젝트 url에 연결해야 하기에, mytodo/urls.py에 아래와 같이 작성한다.

from django.contrib import admin
from django.urls import path, include

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

 

3. Todo 상세 조회 기능 만들기

Todo 상세 조회 기능 컨셉

Todo를 선택했을 때 조회할 수 있는 기능으로, Todo 제목과 설명을 나타내도록 구현한다.

 

Template

todo_detail.html

<html>
    <head>
        <title>TODO 목록 앱</title>
        <Link
            rel="stylesheet"
            href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
        />
        <Link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/boot-strap-icons.css" />
    </head>
    <body>
        <div class="container">
            <h1>TODO 상세보기</h1>
            <div class="container">
                <div class="row">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-body">
                                <h5 class="card-title">{{todo.title}}</h5>
                                <p class="card-text">{{todo.description}}</p>
                                <a href="{% url 'todo_list' %}" class="btn btn-primary">목록으로</a>
                            </div>
                        </div>
                    </div>  
                </div>
            </div>
        </div>
    </body>
</html>

 

View

def todo_detail(request, pk):
    todo = Todo.objects.get(id=pk)
    return render(request, 'todo/todo_detail.html', {'todo': todo})

 

선택된 Todo의 pk인 id를 기반으로 Todo 객체를 찾아 todo_detail.html로 전달할 수 있도록 위와 같이 작성하고, todo_list.html의 버튼에 링크를 넣는다.

<a href="{% url 'todo_detail' pk=todo.pk %}">{{todo.title}}</a>

 

URL

url은 todo/urls.py에서 /pk/로 설정하여 해당 Todo를 연결할 수 있도록 한다.

urlpatterns = [
    path('', views.todo_list, name='todo_list'),
    path('<int:pk>/', views.todo_detail, name='todo_detail'),
]

 

4. Todo 생성 기능 만들기

Todo 생성 기능 컨셉

todo/forms.py에 제목, 설명, 중요도를 입력할 수 있도록 입력 폼을 만들어 주어야 한다.

 

Template

todo/forms.py

from django import forms
from .models import Todo

class TodoForm(forms.ModelForm):
    class Meta:
        model = Todo
        fields = ('title', 'description', 'important')

 

이렇게 작성한 폼을 활용하여 템플릿을 작성한다.

 

todo_post.html

<html>
    <head>
        <title>TODO 목록 앱</title>
        <Link
            rel="stylesheet"
            href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
        />
        <Link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/boot-strap-icons.css" />
    </head>
    <body>
        <div class="container">
            <h1>TODO 추가하기</h1>
            <div class="container">
                <div class="row">
                    <div class="col-md-12">
                        <div class="card">
                            <div class="card-body">
                                <form method="POST">
                                    {% csrf_token %} {{form.as_p}}
                                    <button type="submit" class="btn btn-primary">등록</button>
                                </form>
                            </div>
                        </div>
                    </div>  
                </div>
            </div>
        </div>
    </body>
</html>

 

View

Todo를 생성하기 위한 뷰는 POST 요청을 처리할 수 있어야 한다. POST 요청이 들어왔을 때는 폼을 검증하고 데이터를 저장할 수 있도록 하며, GET 요청이 들어왔을 때는 폼을 포함한 템플릿 페이지를 보여주면 된다.

from django.shortcuts import render, redirect
from .models import Todo
from .forms import TodoForm

def todo_list(request):
    todos = Todo.objects.filter(completed=False)
    return render(request, 'todo/todo_list.html', {'todos': todos})

def todo_detail(request, pk):
    todo = Todo.objects.get(id=pk)
    return render(request, 'todo/todo_detail.html', {'todo': todo})

def todo_post(request):
    if request.method == "POST":
        form = TodoForm(request.POST)
        if form.is_valid():
            todo = form.save(commit=False)
            todo.save()
            return redirect('todo_list')
    else:
        form = TodoForm()
    return render(request, 'todo/todo_post.html', {'form': form})

 

그 후, todo_list.html의 버튼에 링크를 넣어준다.

<a href="{% url 'todo_post' %}"><i class="bi-plus"></i>Add Todo</a>

 

URL

urlpatterns = [
    path('', views.todo_list, name='todo_list'),
    path('<int:pk>/', views.todo_detail, name='todo_detail'),
    path('post/', views.todo_post, name='todo_post'),
]

 

5. Todo 수정 기능 만들기

Todo 수정 기능 컨셉

수정 기능을 만들 때는 생성 기능과 동일하게 작성하되, 기존 Todo 데이터를 불러와 함게 템플릿으로 보내는 것만 구현하면 된다.

 

View

def todo_post(request):
    if request.method == "POST":
        form = TodoForm(request.POST)
        if form.is_valid():
            todo = form.save(commit=False)
            todo.save()
            return redirect('todo_list')
    else:
        form = TodoForm()
    return render(request, 'todo/todo_post.html', {'form': form})

def todo_edit(request, pk):
    todo = Todo.objects.get(id=pk)
    if request.method == "POST":
        form = TodoForm(request.POST, instance=todo)
        if form.is_valid():
            todo = form.save(commit=False)
            todo.save()
            return redirect('todo_list')
    else:
        form = TodoForm(instance=todo)
    return render(request, 'todo/todo_post.html', {'form': form})

 

그리고 todo_list.html의 버튼에 링크를 다음과 같이 추가한다.

<a href="{% url 'todo_post' %}"><i class="bi-plus"></i>Add Todo</a>

<a href="{% url 'todo_edit' pk=todo.pk %}" class="btn btn-outline-primary">수정하기</a>

 

URL

urlpatterns = [
    path('', views.todo_list, name='todo_list'),
    path('<int:pk>/', views.todo_detail, name='todo_detail'),
    path('post/', views.todo_post, name='todo_post'),
    path('<int:pk>/edit/', views.todo_edit, name='todo_edit'),
]

 

6. Todo 완료 기능 만들기

Todo 완료 기능 컨셉

Todo 완료 기능은 완료 버튼을 눌렀을 때 Todo의 completed을 True로 설정해 주는 기능이다. 또한 완료 Todo 조회 기능은 완료된 Todo만 필터링하여 볼 수 있도록 하는 기능이다.

 

Template

done_list.html

<html>
    <head>
        <title>TODO 목록 앱</title>
        <Link
            rel="stylesheet"
            href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
        />
        <Link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/boot-strap-icons.css" />
    </head>
    <body>
        <div class="container">
            <h1>DONE 목록</h1>
            <p>
                <a href="{% url 'todo_list' %}" class="btn btn-outline-primary">홈으로</a>
            </p>
            <ul class="list-group">
                {% for done in dones %}
                <li class="list-group-item">
                    <a href="{% url 'todo_detail' pk=done.pk %}">{{done.title}}</a>
                    {% if done.important %}
                    <span class="badge badge-danger">!</span>
                    {% endif %}
                </li>
                {% endfor %}
            </ul>
        </div>
    </body>
</html>

 

그리고나서 todo_list.html의 버튼에 링크를 추가한다.

<a href="{% url 'done_list' %}" class="btn btn-primary" style="float:right">완료한 TODO 목록</a>

 

View

Todo 완료 기능에서 필요한 뷰는 두 가지로, 완료된 목록을 보여주는 기능과 Todo를 완료로 바꿔주는 기능이 있다.

 

완료된 목록을 보여주는 것은 앞서 todo_list() 뷰와 동일하게 작성되며 completed=True로 필터링 하는 것만 다르다. 그리고 Todo를 완료로 바꾸기 위해서는 Todo 객체를 가져와 completed을 True로 설정한 다음 저장하면 된다.

def done_list(request):
    dones = Todo.objects.filter(completed=True)
    return render(request, 'todo/done_list.html', {'dones': dones})

def todo_done(request, pk):
    todo = Todo.objects.get(id=pk)
    todo.completed = True
    todo.save()
    return redirect('todo_list')

 

마찬가지로, todo_list.html 내 버튼에 링크를 추가해 준다.

<a href="{% url 'todo_done' pk=todo.pk %}" class="btn btn-danger">완료</a>

 

URL

마지막으로 url을 연결한다.

urlpatterns = [
    path('', views.todo_list, name='todo_list'),
    path('<int:pk>/', views.todo_detail, name='todo_detail'),
    path('post/', views.todo_post, name='todo_post'),
    path('<int:pk>/edit/', views.todo_edit, name='todo_edit'),
    path('done/', views.done_list, name='done_list'),
    path('done/<int:pk>/', views.todo_done, name='todo_done'),
]

 

※ Django에서 pk와 id는 기본 키를 가리키며, 기본 키가 나중에 id가 아닌 다른 필드로 바뀌어도 pk는 항상 작동하므로, URL이나 템플릿에서는 pk로 통일하는 것이 안전하다.

 

결과: 모든 기능 정상 작동

'Web > Django' 카테고리의 다른 글

ForeignKey와 OneToOneField  (0) 2025.11.19
Django로 웹 개발하기: photo_list  (1) 2025.10.29