Django - Todo List로 친해지기

정윤수·2022년 8월 30일
0

Todo List

프로젝트 생성

python -m venv myvenv
source myvenv/bin/activate
pip install django

django-admin startproject config
config/config2 변경 -> cofig내용전체 바깥으로 뺴고 기존 config 삭제
django-admin startapp playapp

python manage.py runserver

settings.py

playapp.py 에 static 폴더안에 있는 파일들을 읽어서 static/이라는 곳으로 전달되도록 설정

INSTALLED_APPS = [
		...
    'playapp',
]
...
STATIC_URL = 'static/'
STATICFILES_URL = [
    BASE_DIR / "static"
]

playapp 하위

  • static 폴더 생성
    • style.css 파일 생성
  • templates/pages 폴더 생성
    • common: 메뉴나 헤더를 모아두는 곳

프로젝트 구성

playapp
├── __init__.py
├── __pycache__
│   ├── __init__.cpython-39.pyc
│   ├── admin.cpython-39.pyc
│   ├── apps.cpython-39.pyc
│   ├── forms.cpython-39.pyc
│   ├── models.cpython-39.pyc
│   └── views.cpython-39.pyc
├── admin.py
├── apps.py
├── forms.py
├── models.py
├── static
│   └── styles.css
├── templates
│   ├── common.html
│   └── pages
│       ├── index.html
│       ├── play_create.html
│       └── play_list.html
├── tests.py
└── views.py

common.html

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}할 거{% endblock %}</title>
		<link rel="stylesheet" href="{% static 'style.css' %}">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
		<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct" crossorigin="anonymous"></script>
</head>
<body>
    <div class="container">
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
            <div class="container-fluid">
                <a href="/"><span class="navbar-brand mb-0 h1">PLAY</span></a>
            </div>
        </nav>

        {% if messages %}
        <div class="mt-2">
            {% for message in messages %}
                <div class="alert alert-{{message.tags}}" role="alert">
                    {{ message }}
                </div>
            {% endfor %}
        </div>
        {% endif %}

        <div class="mt-2">
            {% block content %}
            {% endblock %}
        </div>
    </div>
</body>
</html>

bootstrap: https://getbootstrap.com/docs/4.6/getting-started/introduction/

gitignore: https://www.toptal.com/developers/gitignore/api/django

urls.py

from django.urls import path
from playapp import views
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('', views.index, name="index"),
    path('previous/', views.index, name="task-previous"),
    path('play/', views.index, name="play-create"),
    path('play/<int:play_id>/', views.index, name="view-play"),
    path('play/<int:play_id>/delete/', views.index, name="play-delete"),
    path('play/<int:play_id>/item/', views.index, name="create-item"),
    path('play/<int:play_id>/item/<int:item_id>/', views.index, name="check-item"),
    path('play/<int:play_id>/item/<int:item_id>/delete/', views.index, name="delete-item"),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

장고 어플리케이션을 통해서 업로드된 파일들이, 알아서 urlpatterns 조인되면서 제공되도록 설정 시켜준다.

models.py

from django.db import models
from django.utils.translation import gettext_lazy as _

# Create your models here.
class Play(models.Model):
    class PlayType(models.TextChoices):
        JOB = 'JOB', _('업무')
        SOCIAL = 'SOCIAL', _('사회')
        OWN = 'OWN', _('개인')
    title = models.CharField(max_length=90, null=False)
    playType = models.CharField(
        choices=PlayType.choices,
        max_length=25,
        default=PlayType.JOB
    )
    due = models.DateTimeField(null=False)
    created_at = models.DateTimeField(auto_now_add=True, null=False)

class ChecklistItem(models.Model):
    play = models.ForeignKey(Play, on_delete=models.CASCADE)
    content = models.CharField(max_length=150, null=False)
    checked = models.BooleanField(null=False, default=False)
    created_at = models.DateTimeField(auto_now_add=True, null=False)

https://s3.us-west-2.amazonaws.com/secure.notion-static.com/d209db1e-bc47-4a27-99a6-43fdf651538f/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220830%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220830T122149Z&X-Amz-Expires=86400&X-Amz-Signature=f0bce4e16e553e1aa571ccdb48117bb28e1972b56d871ba9a84d74666a980923&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22Untitled.png%22&x-id=GetObject

View 라는 Class 로 정의된 method 는 .as_view()로 접근이 가능하다.

url.py

urlpatterns = [
		...
    # path('', views.index, name="index"),
    path('', PlaylistView.as_view(), name="index"),
]

views.py

  • View를 (상속받아서) 더 genric 하게 쓸 수있게 특화된 버전이 TemplateView이다.
  • TemplateView쪽을 보면 get_context_data쪽만 작성해서 보내주면 알아서 랜더링을 해주는 걸 확인할 수 있다.
  • template_name은 TemplateView 내 TemplateResponseMixin 에 꺼를 오버라이딩(상속) 받아서 쓰는 것을 확인할 수 있다.
from django.shortcuts import render
from django.views.generic import TemplateView
from .models import Play

# Create your views here.
def index(request):
    context = {}
    return render(request, 'pages/index.html', context)

class PlaylistView(TemplateView):
    template_name = 'pages/play_list.html'

    def get_context_data(self, **kwargs):
        plays = Play.objects.all()
        return {
            'plays': plays
        }

playlist.html

{% extends "common.html" %}

{% block content %}
<div class="row row-cols-2 row-cols-md-4 g-4 mt-2">
    {% for item in plays %}
    <div class="col">
        <div class="card text-white bg-primary mb-3">
            <div class="card-header">{{item.playType}}</div>
            <div class="card-body">
                <h5 class="card-title">{{item.title}}</h5>
                <p class="card-text">
                    <span class="badge bg-light text-dark">
                        {{  item.due|date:'Y년 m월 d일 H시 i분' }}
                    </span>
                </p>
            </div>
        </div>
    </div>

    {% endfor %}
</div>
{% endblock %}

만든 models 를 시험하는 방법

서버 실행 상태로,

python manage.py shell
from playapp.models import Play
from django.utils import timezone
play = Play.objects.create(title="하나파워온", type=Play.PlayType.JOB, dead_line=timezone.now())
play = Play.objects.create(title="빨래", type=Play.PlayType.JOB, dead_line=timezone.now())
play = Play.objects.create(title="책 읽기", type=Play.PlayType.JOB, dead_line=timezone.now())

ctrl+d 로 빠져나오기

Form

forms.py

: 요청을 만들 model 폼을 생성

from django import forms
from .models import Play

class PlayForm(forms.ModelForm):
    class Meta:
        model = Play
        fields = ['title', 'type', 'dead_line']

: 알아서 request를 읽어서 파싱을 해준다 (form의 필드들에 request.data를 다 넣어준다)

views.py

  • modelForm 자체가 알아서 request.data를 form으로 잘 빚어준다음에,
  • 유효한 값을 확인 후, save만 해주면 실제디비로 save()가 이루어진다
  • FormView의 부모의 부모 즉 할아버지 클래스인 FormMixin 쪽 메서드를 확인해보면, 여기에서 정의된 form_valid를 가져다 쓰는 구조당
class PlayCreateView(FormView):
    template_name = 'pages/play_create.html'
    success_url='/'
    form_class = PlayForm
    
		//해당 form 이 유효한 데이터가 검증이 된 뒤에,
    def form_valid(self, form):
				//DB에 저장
        form.save()
				//할아버지의 form_valid를 그대로 호출하겠다
        return super().form_valid(form)

play_create.html

{% extedns "common.html" %} {% block content %}
<form method="post">
  <div class="row">
    <div class="col-12">
      {% csrf_token %} {{ form }}
      <button type="submit">추가하기</button>
    </div>
  </div>
</form>

form_valid()는 success_url로 redirect시켜주는 게 다다

이 코드를 그대로 재사용 := super().form_valid()

super():= 부모 클래스에 있는 정의된 필드나 메서드를 접근할 때 쓰는 거당 (없으면 할아버지,증조할아버지까지간닼ㅋㅋㅋ)

urls.py

urlpatterns = [
			...
			path('play/', PlayCreateView.as_view(), name="create-play"),
			...
]

common.html

  • 기존 common에 만들어둔 View쪽으로 이동되도록 url name을 적어준다
  • /play 로 이동하면, CreateView가 작동→Template_name을 호출하는 구조
...
<body>
    <div class="container">
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
            <div class="container-fluid">
                <a href="/"><span class="navbar-brand mb-0 h1">PLAY</span></a>

                **<ul class="nav justify-content-end">
                    <li class="nav-item">
                        <a class="nav-link" href="{% url create-play %}"'></a>
                        <button class="btn btn-dark">
                            할 일 추가
                        </button>
                    </li>
                </ul>**
            </div>
        </nav>
...

발생한 에러

django.db.utils.OperationalError: no such table: playapp_play

매번 해당 에러에 대해서 해결되었던 방법은 migrate할 때 아래와 같은 코드로 실행하는 것이다.

python manage.py migrate --run-syncdb

syncdb라는 것은 settings.py의 Installed apps에서 추가된 앱에 대한 테이블을 처음 db에 만들어주는 command라고 생각할 수 있다.

에러의 메세지가 말하는 것처럼 DB에 해당 table이 없기 때문에 생기는 에러기 때문에 mirgate를 할 때 위 command처럼 --run-syncdb를 붙여서 실행해준 다음 다시

python manage.py runserver

를 해주면 에러가 해결되는 것을 알 수 있다.


forms 좀 예쁘게 꾸미기

참고: https://pypi.org/project/django-flatpickr/

pip install django-crispy-forms

settings.py

INSTALLED_APPS = [
		...
		'crispy_forms',
    'playapp',
]

CRISPY_TEMPLATE_PACK = 'bootstrap4'

pageplay_create.html

form(왼쪽껄) 을 받는 함수를 구현해둔다

| 로 넣어주면 해당 함수가 실행된다

{% extends ...}
{% load crispy_forms_tags %}

...
		<div class="col-12">
      {% csrf_token %} **{{ form|crispy }}**
      <button type="submit">추가하기</button>
    </div>
...

common.html

참고: https://github.com/monim67/django-flatpickr/blob/master/docs/demo/custom-form.html

<link href="https://cdn.jsdelivr.net/npm/flatpickr@4.5.2/dist/flatpickr.min.css" type="text/css" media="all" rel="stylesheet">

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/flatpickr@4.5.2/dist/flatpickr.min.js"></script>

최종 create.html 코드

  • action 을 지정해주고
  • flatpickr JS 코드를 입력해준다 (공식 홈페이지 참고)
{% extends "common.html" %} {% load crispy_forms_tags %} {% block content %}
<form method="post" action="{% url 'create-play' %}">
  <div class="row">
    <div class="col-12">
      {% csrf_token %} {{ form|crispy }}
      <button type="submit">추가하기</button>
    </div>
  </div>
</form>

<script>
  window.onload = function () {
    flatpickr('input[name="dead_line"]', { enableTime: true });
  };
</script>

{% endblock %}

결론

  • FormView로 Form이 구현되는 걸 확인했따
  • generic View 를 쓰면 더 적은 코드로 비즈니스 로직을 구현할 수 있따!!!!

결과 화면

메인 화면

할 일 추가 클릭 시

  • play/ url로 이동되면서 CreateView 가 실행된다
    CreateView는 template을 불러서 아래 화면이 뜨게 되는 흐름
profile
https://www.youtube.com/watch?v=whoDs0KRc7k&t=1s

0개의 댓글

관련 채용 정보