여섯번째 세션에서는 시작하기에 앞서 각 팀별로 지금까지 진행한 프로젝트를 가볍게 리뷰를 진행하였습니다!
- TodoApp 생성 & crispy install
python manage.py startapp tasks
pip install django-crispy-forms
- INSTALLED_APPS에 tasks 및 crispy_forms 추가
# tasks/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'tasks.apps.TasksConfig',
'crispy_forms',
]
CRISPY_TEMPLATE_PACK = 'bootstrap4'
- urls.py 연결
# TodoApp/urls.py
from django.contrib import admin
from django.urls import path, include # include 추가
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('tasks.urls')) # tasks의 urls.py와 연결
# URL을 분리하는 것임.
# path('', include('tasks.urls'))의 뜻은 tasks/로 시작되는 페이지 요청은 앞으로 모두 tasks/urls.py 파일에 있는 URL 매핑을 참고해서 처리하라는 의미이다.
- tasks에 urls.py 생성 후 내용 작성
# tasks/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name="list"), # 루트 url
]
- Views.py 작성
# tasks/views.py
from django.shortcuts import render
from django.http import HttpResponse
# 수정된 부분
def index(request):
return render(request, 'tasks/list.html')
render(request, template_name, context=None, content_type=None, status=None, using=None)
# render함수는 위와 같은 파라미터를 가집니다. 이때 request와 template_name은 반드시 작성
# 첫번째 파라미터로 request를, 두번째 파라미터로 템플릿(index.html)을 받아들입니다.
두 함수를 헷갈려 혼동하는 경우가 많습니다. 특히 장고가 익숙하지 않을 때는 둘다 return 뒤에 위치하여 함수를 종료할 시 사용되니 그럴만 합니다.
생각 외로 둘의 차이는 명확합니다. render
는 템플릿을 불러오고, redirect
는 URL로 이동합니다. URL 로 이동한다는 건 그 URL 에 맞는 views 가 다시 실행될테고 여기서 render 를 할지 다시 redirect 할지 결정할 것 입니다.
- tasks에 templates 폴더 생성
그 후 templates 폴더 안에 tasks 폴더 하나 더 생성 하고 그 안에 list.html 생성
이와 같이 폴더를 구성하는 이유는, Django가 HTML파일을 템플릿을 읽을 때 templates 내부에 프로젝트명과 동일한 이름(tasks)의 폴더를 읽고 그 안의 HTML파일을 읽도록 구성되었기 때문이다.
- HTML 작성 (Template language 미적용)
<!-- tasks/templates/tasks/list.html -->
{% load crispy_forms_filters %}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.9/angular.min.js"></script>
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
crossorigin="anonymous"
/>
<style>
.app-container {
height: 100vh;
width: 100%;
}
.complete {
text-decoration: line-through;
}
</style>
</head>
<body>
<div
class="app-container d-flex align-items-center justify-content-center flex-column"
ng-app="myApp"
ng-controller="myController"
>
<h3>Todo App</h3>
<div class="d-flex align-items-center mb-3">
<div class="form-group mr-3 mb-0">
<form class="form-inline" method="POST" action="/">
{% csrf_token %}
<div style="display: flex; margin-bottom: 1px">
<label>
<input>
</label>
<button
type="submit"
class="btn btn-primary mb-2"
ng-click="saveTask()"
style="margin-top: 8px;"
>
Save
</button>
</div>
</form>
</div>
</div>
<div class="table-wrapper" style="overflow: auto; height: 250px">
<table class="table table-hover table-bordered">
<thead>
<tr>
<th>No.</th>
<th>Todo item</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>
id
</td>
<td>
<span>task</span>
</td>
<td>
<a class="btn btn-sm btn-info">Update</a>
<a class="btn btn-sm btn-danger">Delete</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>
- 중간 점검
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
- models.py 수정
# tasks/models.py
from django.db import models
class Task(models.Model):
title = models.CharField(max_length=200)
complete = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
수정 후
python [manage.py](http://manage.py) makemigrations
# :models에 적용한 변경 내용을 기반으로 새로운 migrations 파일을 만든다.
python [manage.py](http://manage.py) migrate
# migrations를 적용 및 적용 해제 : 해당 마이그레이션 파일을 DB에 반영!
즉, makimigrations는 장고에서 제공하는 모델의 변경사항들을 감지하고 기록하는 역할을 하고, migrate는 그러한 기록된 파일들과 설정값들을 읽어서 그 변경사항을 db에 저장하는 역할을 합니다.
- forms.py 생성
from django import forms
from django.forms import ModelForm
from crispy_forms.helper import FormHelper
from .models import *
class TaskForm(forms.ModelForm):
title = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Add new task...'}))
## 이 부분은 무시하셔도 됩니다. 작성은 하시고 이해하실 필요는 없어요
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
FormHelper.form_show_labels = False
class Meta:
model = Task
fields = '__all__'
- HTML 수정 (Template language 적용)
<!-- tasks/templates/tasks/list.html -->
{% load crispy_forms_filters %}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.9/angular.min.js"></script>
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
crossorigin="anonymous"
/>
<style>
.app-container {
height: 100vh;
width: 100%;
}
.complete {
text-decoration: line-through;
}
</style>
</head>
<body>
<div
class="app-container d-flex align-items-center justify-content-center flex-column"
ng-app="myApp"
ng-controller="myController"
>
<h3>Todo App</h3>
<div class="d-flex align-items-center mb-3">
<div class="form-group mr-3 mb-0">
<form class="form-inline" method="POST" action="/">
{% csrf_token %}
<div style="display: flex; margin-bottom: 1px">
{{ form.title | as_crispy_field }}
<button
type="submit"
class="btn btn-primary mb-2"
ng-click="saveTask()"
style="margin-top: 8px;"
>
Save
</button>
</div>
</form>
</div>
</div>
<div class="table-wrapper" style="overflow: auto; height: 250px">
<table class="table table-hover table-bordered">
<thead>
<tr>
<th>No.</th>
<th>Todo item</th>
<th>Actions</th>
</tr>
</thead>
{% for task in tasks %}
<tbody>
<tr>
<td>
{{ task.id }}
</td>
<td>
{% if task.complete == True %}
<span style="text-decoration: line-through;">{{ task }}</span>
{% else %}
<span>{{ task }}</span>
{% endif %}
</td>
<td>
<a class="btn btn-sm btn-info">Update</a>
<a class="btn btn-sm btn-danger">Delete</a>
</td>
</tr>
</tbody>
{% endfor %}
</table>
</div>
</div>
</body>
</html>
- Views.py 수정
# tasks/views.py
from django.shortcuts import render, redirect
from django.http import HttpResponse
from .models import *
from .forms import *
# Create your views here.
def index(request):
tasks = Task.objects.all()
form = TaskForm()
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()
return redirect('/')
context = {'tasks': tasks, 'form': form}
return render(request, 'tasks/list.html', context)
render(request, template_name, context=None, content_type=None, status=None, using=None)
# render함수는 위와 같은 파라미터를 가집니다. 이때 request와 template_name은 반드시 작성
# 첫번째 파라미터로 request를, 두번째 파라미터로 템플릿(index.html)을 받아들입니다.
두 함수를 헷갈려 혼동하는 경우가 많습니다. 특히 장고가 익숙하지 않을 때는 둘다 return 뒤에 위치하여 함수를 종료할 시 사용되니 그럴만 합니다.
생각 외로 둘의 차이는 명확합니다. render
는 템플릿을 불러오고, redirect
는 URL로 이동합니다. URL 로 이동한다는 건 그 URL 에 맞는 views 가 다시 실행될테고 여기서 render 를 할지 다시 redirect 할지 결정할 것 입니다.
- 중간 점검
python manage.py runserver
여기까지 하셨다면 메인 화면은 완성되셨습니다. Create, Read 기능까지 만드신겁니다.
- Delete 기능 만들기
14-1. list.html 수정
<!-- tasks/templates/tasks/list.html -->
{% load crispy_forms_filters %}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.9/angular.min.js"></script>
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
crossorigin="anonymous"
/>
<style>
.app-container {
height: 100vh;
width: 100%;
}
.complete {
text-decoration: line-through;
}
</style>
</head>
<body>
<div
class="app-container d-flex align-items-center justify-content-center flex-column"
ng-app="myApp"
ng-controller="myController"
>
<h3>Todo App</h3>
<div class="d-flex align-items-center mb-3">
<div class="form-group mr-3 mb-0">
<form class="form-inline" method="POST" action="/">
{% csrf_token %}
<div style="display: flex; margin-bottom: 1px">
{{ form.title | as_crispy_field }}
<button
type="submit"
class="btn btn-primary mb-2"
ng-click="saveTask()"
style="margin-top: 8px;"
>
Save
</button>
</div>
</form>
</div>
</div>
<div class="table-wrapper" style="overflow: auto; height: 250px">
<table class="table table-hover table-bordered">
<thead>
<tr>
<th>No.</th>
<th>Todo item</th>
<th>Actions</th>
</tr>
</thead>
{% for task in tasks %}
<tbody>
<tr>
<td>
{{ task.id }}
</td>
<td>
{% if task.complete == True %}
<span style="text-decoration: line-through;">{{ task }}</span>
{% else %}
<span>{{ task }}</span>
{% endif %}
</td>
<td>
<a class="btn btn-sm btn-info">Update</a>
<a class="btn btn-sm btn-danger" href="{% url 'delete' task.id %}">Delete</a>
</td>
</tr>
</tbody>
{% endfor %}
</table>
</div>
</div>
</body>
</html>
14-2. delete.html 생성
<!-- tasks/templates/tasks/delete.html -->
<head>
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
crossorigin="anonymous"
/>
<style>
.app-container {
height: 100vh;
width: 100%;
background-color: #202124;
}
.complete {
text-decoration: line-through;
}
</style>
</head>
</head>
<div class="app-container d-flex align-items-center justify-content-center flex-column">
<h2 style="text-align: center; color: white">Are you sure you want to delete "{{ item }}"?</h2>
<div class="row">
<div class="col-sm-12 text-center">
<form method="POST" action="">
{% csrf_token %}
<button type="submit" id="Confirm" class="btn btn-danger">Delete</button>
<button type="button" class="btn btn-secondary" href="{% url 'list' %}">
<a href="{% url 'list' %}" style="color:white">Cancel</a>
</button>
</form>
</div>
</div>
</div>
14-3. urls.py에 delete 추가
# tasks/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name="list"),
path('delete/<str:pk>/', views.deleteTask, name="delete")
]
14-4. views.py에 delete 기능 추가
# tasks/views.py
from django.shortcuts import render, redirect
from django.http import HttpResponse
from .models import *
from .forms import *
# Create your views here.
def index(request):
tasks = Task.objects.all()
form = TaskForm()
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()
return redirect('/')
context = {'tasks': tasks, 'form': form}
return render(request, 'tasks/list.html', context)
def deleteTask(request, pk):
item = Task.objects.get(id=pk)
if request.method == 'POST':
item.delete()
return redirect('/')
context = {'item': item}
return render(request, 'tasks/delete.html', context)
여기까지 하셨다면 여러분은 Django로 CRUD에서 Create, Read, Delete를 완성하셨습니다.
완성한 페이지의 소스코드를 submodule로 등록한 repo에 올리고,
배포한 페이지 주소를 하단 테이블에 올려주세요
기한 : 6월 30일까지