Do it! 장고+부트스트랩 파이썬 웹 개발의 정석를 읽고, 정리한 글입니다.
목록
· 장고 앱 만들기
· 모델 만들기
· 관리자 페이지에서 첫 포스트 작성하기
· 포스트 개선하기
· 장고 셸 사용하기
· URL 설정하기
· FBV로 페이지 만들기
· CBV로 페이지 만들기
시작에 앞서 장고를 위한 여러 프로그램의 설치 및 환경 설정이 필요하다. https://scshim.tistory.com/561
· 모든 장고 프로젝트는 1개 이상의 앱으로 구성된다. 앱은 특정한 기능을 수행하는 단위 모듈로 생각하면 된다.
장고 앱 만들기
· 블로그 기능을 위한 blog 앱과 대문과 자기소개 역할을 하는 single_pages 앱을 만들어보자.
1. 가상환경에서 'python manage.py startapp blog'를 입력하여 blog 앱을 생성한다.
- 가상환경 설정 방법: https://scshim.tistory.com/561
2. 가상환경에서 'python manage.py startapp single_pages'를 입력하여 single_pages 앱을 생성한다.
3. 파이참을 통해 프로젝트를 열면, blog와 single 앱 폴더가 생성된 것을 확인할 수 있다.
3. 장고 프로젝트 폴더(do_it_django_prj) 안에 settings.py 파일에 두 앱을 등록한다. settings.py에 INSTALLED_APPS 리스트에 'blog', 'single_pages' 앱을 추가한다.
settings.py
...생략...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog',
'single_pages'
]
...생략...
모델 만들기
· 장고는 모델을 이용해 장고 웹 프레임워크 안에서 데이터베이스를 관리할 수 있다.
· 모델은 데이터를 저장하기 위한 하나의 단위다.
· 장고의 모델을 이용하면 SQL 언어를 배우지 않고도, 파이썬만으로 CRUD 기능을 쉽게 구현할 수 있다. 또한 관리자 페이지, 입력 폼 등도 쉽게 만들 수 있다.
Post 모델 만들기
· 블로그의 포스트의 형태를 정의하는 Post 모델을 만든다.
1. blog/models.py를 열어 다음과 같이 입력한다.
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=30)
content = models.TextField()
created_at = models.DateTimeField()
- Post 모델은 models 모듈의 Model 클래스를 확장해서 만든 파이썬 클래스다.
- title 필드는 CharField 클래스(문자를 담는 필드 생성)로 만들고, 최대 길이가 30이 되도록 설정한다.
- content 필드는 문자열의 길이 제한이 없는 TextField를 사용해 만든다.
- created_at 필드는 월,일,시,분,초를 기록할 수 있는 DateTimeField로 만든다.
2. Post 모델을 데이터베이스에 반영하여 실제 테이블을 생성하자. 터미널에 'python manage.py makemigrations'를 입력한다.
blog/migrations 폴더에 0001_initial.py 파일이 생성된다.
아직 데이터베이스에 모델이 적용된 상태는 아니다.
3. 모델을 실제 데이터베이스에 적용하기 위해 터미널에 'python manage.py migrate' 명령을 실행한다.
관리자 페이지에서 첫 포스트 작성하기
1. 관리자 페이지에 Post 모델을 등록한다. blog/admin.py 파일에 다음과 같이 작성한다.
from django.contrib import admin
from .models import Post
admin.site.register(Post)
2. 서버를 실행하고, 관리자 페이지를 연다. 관리자페이지에 [BLOG]섹션과 [Posts] 메뉴가 생긴걸 확인할 수 있다.
3. 새로운 포스트를 생성하자. [Posts] 메뉴를 클릭한다. 이동한 페이지에서 <Add Post> 또는 [Posts] 옆에 <+Add> 버튼을 클릭한다.
4. 다음 처럼 포스트를 생성할 수 있는 페이지가 열린다. 내용을 채워 넣고 <Save> 버튼을 클릭하면 포스트가 생성된다.
- models.py에 지정했던, title, content,, created_at 필드에 값을 넣을 수 있게 각각의 양식에 맞춰 입력란이 만들어진다.
포스트 개선하기
· 포스트를 하나 더 생성한다.
· 문제점 1: 생성된 포스트 목록을 보면 제목이 나타나지 않는다. 따라서 글에 직접 들어가 보지 않으면 어떤 내용인지 알 수 없다.
· 해결책 1: Post 모델에 __str__() 함수를 선언한다.
blog/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=30)
content = models.TextField()
created_at = models.DateTimeField()
def __str__(self):
return f'[{self.pk}]{self.title}'
- 장고의 모델을 만들면 기본적으로 pk 필드가 민들어 진다. pk는 각 레코드에 대한 고유값이다.
- pk 값을 이용하여 포스트의 번호({self.pk})와 제목({self.title})을 문자열로 표현한다.
포스트 목록 페이지를 갱신하면, 다음과 같이 변경된 것을 확인할 수 있다.
· 문제점 2: 앞서 생성한 포스트를 클릭해보자. Created at 부분을 보면 Now라는 버튼이 있다. 이것을 누르면, 현재 시간이 입력된다. 하지만, 입력되는 시간은 실제 시간과 다를 것이다. 또한 그 아래는 'Note: You are 9 hours ahead of server time'이라는 문구 또한 볼 수 있다. 이것은 그리니치 표준시에 맞춘 시각이라 생기는 문제다. 세계인을 위한 웹사이트가 있다면 이대로 놔둬도 무방하지만, 여기서는 한국을 기준으로 만든 웹 사이트라 가정한다.
· 해결책 2: 서울을 기준으로 작성 시간이 설정되도록 수정하자. do_it_django_prj/settings.py를 다음과 같이 수정한다.
...생략...
TIME_ZONE = 'Asia/Seoul'
USE_I18N = True
USE_TZ = False
...생략...
- TIME_ZONE = 'UTC'라는 항목을 Asia/Seoul로 수정한다.
- USE_TZ는 False로 설정한다.
앞서 생성한 포스트를 다시 확인해보면, 작성시간이 서울 기준으로 바뀐 걸 확인할 수 있다.
· 문제점 3: 포스트를 수정할 때 따로 수정 시간을 변경하지 않으면, 기존 작성 시간이 그대로 저장된다.
· 해결책 3: 포스트를 수정하면, 작성 시간도 자동으로 변경되어 저장되도록 수정하자. blog/models.py를 다음과 같이 수정한다.
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=30)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f'[{self.pk}]{self.title}'
- 수정 시각을 저장할 updated_at 필드를 DateTimeField로 생성한다.
- DateTimeField는 auto_now, auto_now_add라는 설정이 있어서 처음 레코드가 생성된 시점, 마지막으로 저장된 시점을 자동으로 저장할 수 있다.
▶ created_at 필드를 auto_now_add=True로 설정해서 처음 레코드가 생성될 현재 시각이 자동으로 저장되게 한다.
▶ updated_at 필드를 만들어 auto_now=True로 설정하여 다시 저장할 때 그 시각이 저장되도록 한다.
수정한 모델을 makemigrations로 장고로 알려주고, migrate로 데이터베이스에 반영한다. 그리고 서버를 재실행한다.
다시 포스트를 작성해보면, 작성 시간이 자동으로 저장되므로 Created at 입력란이 더 이상 보이지 않는다.
장고 셸 사용하기
· 파이썬은 스크립트 기반 언어로 컴파일 과정 없이 터미널에서 한 줄씩 코드를 실행시킬 수 있다. 장고 셸로 이런 장점을 그대로 활용할 수 있다.
1. 터미널에 'python manage.py shell'을 입력하면 장고 셸이 실행된다.
2. blog 앱의 models.py에서 Post 모델을 임포트한다. 그리고 Post.objects.last() 명령어로 데이터베이스에서 Post 모델의 레코드 중 마지막 레코드를 가져와 p에 저장한다.
>>> from blog.models import Post
>>> p = Post.objects.last()
3. 최근 포스트의 제목, 작성 시간, 수정 시간을 확인하고, 셸에서 빠져나오자.
>>> p.title
>>> p.created_at
>>> p.updated_at
>>> exit()
URL 설정하기
· 웹 사이트 방문자가 여러 정보에 접근할 수 있도록 모든 페이지마다 각각 URL을 지정하자.
1. 장고 프로젝트의 urls.py를 연다. 장고 프로젝트를 만들 때 다음과 같이 자동으로 생성된다.
- urls.py: 장고로 개발한 웹 사이트에 방문했을 때 어떤 페이지로 들어가야 하는지 알려준다.
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
2. 방문자가 blog/로 접속할 때 blog 앱 폴더의 urls.py을 참고하도록 설정한다. urls.py 파일을 다음과 같이 수정한다.
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('blog/', include('blog.urls')),
path('admin/', admin.site.urls),
]
3. blog 앱 폴더에 urls.py를 새로 만들고 다음처럼 작성한다.
from django.urls import path
from . import views
urlpatterns = [
]
앞으로 urls.py에도 urlpatterns 리스트에 URL과 그 URL이 들어올 떄 어떻게 처리할지 명시해 준다. 이제 포스트 목록 페이지를 만들어보자.
FBV로 페이지 만들기
· urls.py에 들어갈 함수나 클래스 등은 views.py에 정의한다. view.py를 만들 때는 FBV, CBS라는 2가지 선택지가 있다.
· FBV(Function Based View): 함수에 기반을 둔 방법이다. 함수를 직접 만들어서 원하는 기능을 직접 구현할 수 있다.
· CBV(Class Based View): 장고가 제공하는 클래스를 활용해 구현하는 방법이다. 장고는 웹 개발을 할 때 반복적으로 많이 구현하는 것들을 클래스로 미리 만들어서 제고하고 있다.
FBV로 포스트 목록 페이지 만들기
1. blog/urls.py 내용을 다음과 같이 추가한다.
from django.urls import path
from . import views
urlpatterns = [
path('', views.index),
]
- from . import view는 현재 폴더에 있는 views.py를 사용할 수 있게 가져오라는 뜻이다.
- 입력된 URL이 'blog/'로 끝난다면 임포트한 views.py에 정의된 index() 함수를 실행하도록 코드를 작성한다.
2. blog/views.py에 index() 함수를 정의한다.
from django.shortcuts import render
from .models import Post
def index(request):
posts = Post.objects.all()
return render(
request,
'blog/index.html',
{
'posts': posts,
}
)
- models.py에 정의된 Post 모델을 임포트하고, index() 함수에서 Post.objects.all()로 모든 Post 레코드를 가져와 posts에 저장한다.
- 장고가 기본으로 제공하는 render() 함수를 사용해 템플릿 폴더에서 blog 폴더의 index.html 파일을 찾아 방문자에게 보내준다. 그리고 render() 함수 안에 posts를 딕셔너리 형태로 추가한다.
3. blog/templates/blog 폴더에 index.html 파일을 만든다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Blog</title>
</head>
<body>
<h1>Blog</h1>
{% for p in posts %}
<hr/>
<h2>{{p.title}}</h2>
<h4>{{p.created_at}}</h4>
<p>{{p.content}}</p>
{% endfor %}
</body>
</html>
- for 문을 통해 인스턴스화된 Post 모델의 필드는 닷(.) 기호로 접글할 수 있다.
4. http://127.0.0.1:8000/blog에 접속하면, Post 레코드가 모두 나열된다.
5. 블로그의 최신 글부터 맨 위에 배치하도록 변경해보자. blog/views.py를 다음과 같이 수정한다.
from django.shortcuts import render
from .models import Post
def index(request):
posts = Post.objects.all().order_by('-pk')
return render(
request,
'blog/index.html',
{
'posts': posts,
}
)
- order_by를 사용하여 pk 값을 역순으로 정렬하자.
웹 브라우저를 새로고침하면 가장 최근에 만든 포스트부터 나열된다.
FBV로 포스트 상세 페이지 만들기
1. 포스트 상세 페이지 URL을 정의한다. blog/urls.py를 다음과 같이 수정한다.
from django.urls import path
from . import views
urlpatterns = [
path('<int:pk>/', views.single_post_page),
path('', views.index),
]
- 추가된 의미는 /blog/ 뒤에 정수(int) 형태의 값이 붙는 URL이면 blog/views.py의 single_post_page() 함수에 정의된 대로 처리하라는 의미다.
- <int:pk>는 정수 형태의 값을 pk라는 변수로 담아 single_post_page() 함수로 넘기겠다는 의미다.
2. blog/views.py에 single_post_page() 함수를 정의한다.
from django.shortcuts import render
from .models import Post
...생략...
def single_post_page(request, pk):
post = Post.objects.get(pk=pk)
return render(
request,
'blog/single_post_page.html',
{
'post': post,
}
)
- Post.objects.get(pk=pk): Post 모델의 pk 필드 값이 single_post_page() 함수의 매개변수로 받은 pk와 같은 레코드를 가져오기
- 이후에 Post 레코드 하나를 blog/single_post_page.html에 담아 렌더링한다.
3. 템플릿 파일을 만들어보자. blog/template/blog 폴더에 single_post_page.html을 만든다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>{{post.title}} - Blog</title>
</head>
<body>
<nav>
<a href="/blog/">Blog</a>
</nav>
<h1>{{ post.title }}</h1>
<h4>{{ post.created_at }}</h4>
<p>{{ post.content }}</p>
<hr/>
</body>
</html>
웹 브라우저에서 127.0.0.1:8000/blog/1로 접속하면 아래와 같이 상세 페이지를 볼 수 있다.
4. 포스트 제목에 링크를 만든다. index.html을 수정한다.
기존 {{ p.title }}을 <a> 태그로 감싸고, href="{{ p.get_absolute_url }}"로 지정한다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Blog</title>
</head>
<body>
<h1>Blog</h1>
{% for p in posts %}
<hr/>
<h2><a href="{{ p.get_absolute_url }}">{{ p.title }}</a></h2>
<h4>{{ p.created_at }}</h4>
<p>{{ p.content }}</p>
{% endfor %}
</body>
</html>
5. /blog/models.py의 Post 모델에 get_absolute_url() 함수를 추가한다.
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=30)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f'[{self.pk}]{self.title}'
def get_absolute_url(self):
return f'/blog/{self.pk}/'
6. models.py 파일의 수정을 완료하고, 관리자 페이지에서 포스트를 하나 열어보면 <VIEW ON SITE> 버튼이 생긴걸 확인할 수 있다.
이것을 클릭하면, 해당 포스트의 상세 페이지로 이동한다.
7. 포스트 목록 페이지로 이동하면, 다음과 같이 제목이 클릭할 수 있는 형태로 변경되었다. 이것을 클릭하면, 각 포스트의 상세 페이지로 이동한다.
대문 페이지와 자기소개 페이지 만들기
· single_pages 앱의 대문 페이지와 자기소개 페이지를 만들자.
1. single_pages 앱을 위한 URL을 지정한다. 장고 프로젝트의 urls.py를 수정한다.
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('blog/', include('blog.urls')),
path('admin/', admin.site.urls),
path('', include('single_pages.urls')),
]
2. 대문 페이지와 자기소개 페이지'의 URL을 지정한다. single_pages 앱 폴더에 urls.py를 만들고 2가지 URL 패턴에 대한 명령을 추가한다.
from django.urls import path
from . import views
urlpatterns = [
path('about_me/', views.about_me),
path('', views.landing),
]
3. 도메인 뒤에 아무 것도 없을 때는 views.py에 있는 landing() 함수를 실행해 대문 페이지를 보여주고, 도메인 뒤에 about_me/가 붙어 있을 때는 about_me() 함수를 실행해 자기소개 페이지를 보여준다.
from django.shortcuts import render
def landing(request):
return render(
request,
'single_pages/landing.html'
)
def about_me(request):
return render(
request,
'single_pages/about_me.html'
)
4. landing(), about_me() 함수에서 사용하는 템플릿을 만들자. single_pages/templates/single_pages 폴더를 생성하고, 그 안에 landing.html과 about_me.html을 만든다.
landing.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>ted blog</title>
</head>
<body>
<nav>
<a href="/blog">Blog</a>
<a href="/about_me/">About me</a>
</nav>
<h1>안녕하세요. Ted입니다.</h1>
<h2>대문페이지</h2>
<h3>아직 만들지 않음</h3>
</body>
</html>
about_me.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>개발자 Ted입니다.</title>
</head>
<body>
<nav>
<a href="/blog/">Blog</a>
<a href="/about_me/">About me</a>
</nav>
<h1>안녕하세요. Ted입니다.</h1>
<h2>이력</h2>
<h2>Portfolio</h2>
<h3>아직 만들지 않음</h3>
</body>
</html>
코드를 모두 작성했다면 http://127.0.0.1:8000, http://127.0.0.1:8000/about_me/ 에 접속하여 페이지가 잘 열리는지 확인한다.
CBV로 페이지 만들기
· 장고는 웹 개발할 때 사람들이 반복해서 사용하는 기능들을 클래스 형태로 제공해 주기 때문에 CBV를 이용하면 더 간편하게 페이지를 만들 수 있는 경우가 많다.
CBV로 포스트 목록 페이지 만들기
· 장고에서는 여러 포스트를 나열할 때 ListView 클래스를 활용할 수 있다.
1. 앞서 blog/views.py에 index() 함수를 주석 처리하거나 지우고, 다음 코드를 추가한다.
from django.shortcuts import render
from django.views.generic import ListView
from .models import Post
class PostList(ListView):
model = Post
# def index(request):
# posts = Post.objects.all().order_by('-pk')
#
# return render(
# request,
# 'blog/index.html',
# {
# 'posts': posts,
# }
# )
#
#
def single_post_page(request, pk):
post = Post.objects.get(pk=pk)
return render(
request,
'blog/single_post_page.html',
{
'post': post,
}
)
- 앞서 포스트 목록 페이지를 위해 만들었던 FBV 스타일의 index() 함수를 대체하는 PostList 클래스를 ListView 클래스를 상속하여 만들었다.
2. blog/urls.py 파일을 URL 끝이 /blog/일 때 PostList 클래스로 처리하도록 수정한다. 기존 path('', views.index)는 주석 처리하거나 삭제한다.
from django.urls import path
from . import views
urlpatterns = [
path('', views.PostList.as_view),
# path('', views.index),
path('<int:pk>/', views.single_post_page),
]
3. 템플릿 파일 지정한다.
웹 브라우저에 127.0.0.1:8000/blog/를 입력하면, 다음과 같이 TemplateDoesNotExist 오류가 발생한다. 그리고 blog/post_list.html이 필요하다는 메세지가 보인다.
장고가 제공하는 ListView는 모델명 뒤에 '_list'가 붙은 html 파일을 기본 템플릿으로 사용한다. 즉, Post 모델을 사용하면 post_list.html 파일이 필요하다.
문제를 해결하는 방법은 두 가지다.
1) template_name 지정하기
- PostList 클래스를 다음과 같이 수정한다.
class PostList(ListView):
model = Post
template_name = 'blog/index.html'
2) post_list.html 파일 생성하기
- 기존의 index.html 파일의 이름을 post_list.html로 변경하고 다음과 같이 수정한다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Blog</title>
</head>
<body>
<h1>Blog</h1>
{% for p in post_list %}
<hr/>
<h2><a href="{{ p.get_absolute_url }}">{{ p.title }}</a></h2>
<h4>{{ p.created_at }}</h4>
<p>{{ p.content }}</p>
{% endfor %}
</body>
</html>
변경 후에는 다음과 같이 정상적으로 페이지가 보인다.
4. 앞서 웹 사이트의 최신 글을 맨 위에 배치했다. 장고의 ListVIew에도 이런 기능이 있다. PostList 클래스에 ordering = '-pk'를 추가한다.
class PostList(ListView):
model = Post
ordering = '-pk'
- Post 레코드 중 pk 값이 작은 순서대로 보여달라는 의미다.
페이지를 새로고침하면, 최신 글이 맨 위에 배치되는 걸 볼 수 있다.
CBV로 포스트 상세 페이지 만들기
· 장고에서 한 레코드를 자세히 보여줄 때는 DetailView를 이용한다.
1. blog/views.py 파일에서 single_post_page 함수를 주석 처리 하거나, 지우고 다음과 같이 수정한다.
# from django.shortcuts import render
from django.views.generic import ListView, DetailView
from .models import Post
class PostList(ListView):
model = Post
ordering = '-pk'
class PostDetail(DetailView):
model = Post
- render() 함수 또한 사용하지 않으므로 render를 임포트하는 코드도 삭제한다.
2. blog/urls.py를 수정한다.
from django.urls import path
from . import views
urlpatterns = [
path('<int:pk>/', views.PostDetail.as_view()),
path('', views.PostList.as_view()),
# path('', views.index),
# path('<int:pk>/', views.single_post_page),
]
- 이전에 만들어둔 path('<int:pk>/', views.single_post_page)는 주석 처리하거나 삭제한다.
3. 템플릿 파일 지정한다.
서버를 실행하고, 웹 브라우저에 127.0.0.1:8000/blog/1/을 입력하면, 다음과 같이 TemplateDoesNotExist 오류가 발생한다. 그리고 blog/post_detail.html이 필요하다는 메세지가 보인다.
장고가 제공하는 DetailView는 모델명 뒤에 '_detail'가 붙은 html 파일을 기본 템플릿으로 사용한다. 즉, Post 모델을 사용하면 post_detail.htm 파일이 필요하다.
앞서 ListView의 경우와 같이 두 가지 해결 방법이 있다. template_name을 수정하거나 템플릿 폴더에 있는 single_post_page.html의 파일명을 post_detail.html로 수정한다.
문제를 해결하고 웹 브라우저는 새로고침하면, 정상적으로 상세 페이지를 볼 수 있다.
'파이썬 > 장고(django)' 카테고리의 다른 글
[Django] 튜토리얼1. 요청과 응답(Requests and responses) (0) | 2022.05.29 |
---|---|
[Django] 다대일 관계와 다대다 관계 (0) | 2022.04.06 |
[Django] 장고를 통한 테스트 주도 개발(TDD) (0) | 2022.04.06 |
[Django] 장고 설치 및 환경설정하기 (0) | 2022.03.31 |
[Django] 장고란? - 장고를 사용하는 이유 및 작동 구조를 알아보자 (0) | 2022.03.30 |
댓글