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 하위
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)
View 라는 Class 로 정의된 method 는 .as_view()로 접근이 가능하다.
url.py
urlpatterns = [
...
# path('', views.index, name="index"),
path('', PlaylistView.as_view(), name="index"),
]
views.py
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 로 빠져나오기
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
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
...
<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>
...
매번 해당 에러에 대해서 해결되었던 방법은 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 코드
{% 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 %}
메인 화면
할 일 추가 클릭 시