본문 바로가기
파이썬/장고(django)

[Django] 장고를 통해 웹 사이트 만들기

by 책 읽는 개발자_테드 2022. 4. 2.
반응형

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.pysingle_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로 수정한다.

 

문제를 해결하고 웹 브라우저는 새로고침하면, 정상적으로 상세 페이지를 볼 수 있다.

 

반응형

댓글