Session #6 Django

HufsGlobal·2021년 5월 30일
0

HUFSLION 9th 이야기

목록 보기
5/7
post-thumbnail

2021.06.02. (수)

세션 담당자 : 박용훈

여섯번째 세션에서는 시작하기에 앞서 각 팀별로 지금까지 진행한 프로젝트를 가볍게 리뷰를 진행하였습니다!

📝 Django Session - TodoApp


TodoApp 제작

  1. TodoApp 생성 & crispy install
python manage.py startapp tasks
pip install django-crispy-forms
  1. 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'
  1. 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 매핑을 참고해서 처리하라는 의미이다.

URL 연결 참고용 자료

  1. tasks에 urls.py 생성 후 내용 작성

# tasks/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name="list"), # 루트 url
]
  1. 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)을 받아들입니다.

render 와 redirect 구분

두 함수를 헷갈려 혼동하는 경우가 많습니다. 특히 장고가 익숙하지 않을 때는 둘다 return 뒤에 위치하여 함수를 종료할 시 사용되니 그럴만 합니다.

생각 외로 둘의 차이는 명확합니다. render 는 템플릿을 불러오고, redirect 는 URL로 이동합니다. URL 로 이동한다는 건 그 URL 에 맞는 views 가 다시 실행될테고 여기서 render 를 할지 다시 redirect 할지 결정할 것 입니다.

  1. tasks에 templates 폴더 생성

그 후 templates 폴더 안에 tasks 폴더 하나 더 생성 하고 그 안에 list.html 생성

이와 같이 폴더를 구성하는 이유는, Django가 HTML파일을 템플릿을 읽을 때 templates 내부에 프로젝트명과 동일한 이름(tasks)의 폴더를 읽고 그 안의 HTML파일을 읽도록 구성되었기 때문이다.

  1. 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>
                        &nbsp;
                    </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>
  1. 중간 점검
python manage.py makemigrations
python manage.py migrate
python manage.py runserver

  1. 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에 저장하는 역할을 합니다.

  1. 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__'
  1. 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 }}

                    &nbsp;

                    <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>
  1. 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)을 받아들입니다.

render 와 redirect 구분

두 함수를 헷갈려 혼동하는 경우가 많습니다. 특히 장고가 익숙하지 않을 때는 둘다 return 뒤에 위치하여 함수를 종료할 시 사용되니 그럴만 합니다.

생각 외로 둘의 차이는 명확합니다. render 는 템플릿을 불러오고, redirect 는 URL로 이동합니다. URL 로 이동한다는 건 그 URL 에 맞는 views 가 다시 실행될테고 여기서 render 를 할지 다시 redirect 할지 결정할 것 입니다.

  1. 중간 점검
python manage.py runserver

여기까지 하셨다면 메인 화면은 완성되셨습니다. Create, Read 기능까지 만드신겁니다.

  1. 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 }}

                    &nbsp;

                    <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일까지

  1. 페이지의 디자인을 마음대로 바꿔보세요 (선택 사항)
    • Django template language를 사용하다보면 디자인 바꾸는 것조차 쉽지 않을 것입니다. 한번 디자인을 바꾸면서 어느 부분에서 어려움을 느꼈는지 찾아보세요.
  2. 실습에서 진행한 결과물을 바탕으로 Update(Edit) 기능 만들기
    • 아직 우리가 만든 프로그램은 완성본이 아닙니다. Update 기능이 빠져있기 때문이죠.
    • 조건 : title, complete state 수정이 가능 해야함. (complete state가 True일 경우 아래 예시와 같이 메인 화면에서 todo가 취소선이 그어져 있어야함.)

  1. 소스코드를 organization에 submodule로 등록한 본인의 repo에 올리고, 제출테이블의 체크박스 체크
profile
한국외국어대학교 글로벌캠퍼스 멋쟁이 사자처럼

0개의 댓글