WeniVooks

검색

Django 베이스캠프

URL 처리와 템플릿 태그 실습

URL 처리는 웹사이트에서 사용자가 특정 주소를 입력했을 때, 그에 맞는 페이지를 보여주는 방법입니다. 공통되고 반복적인 구조를 단순화하여 이해하기 쉬운 웹 주소 구조를 만들 수 있습니다.

템플릿 태그는 HTML 페이지 안에서 파이썬 문법과 유사한 반복문, 조건문 등을 사용할 수 있게 해줍니다. 이렇게 하면 웹 페이지가 단순히 고정된 내용을 보여주는 것이 아니라, 상황에 따라 다양한 내용을 보여줄 수 있게 됩니다.

그럼 실습으로 이 개념을 직접 적용해 보겠습니다.

Django 환경 설정과 프로젝트 구조에 익숙해지기 위해, 매 실습마다 새로운 디렉토리와 가상 환경을 생성합니다. 실습마다 새로 같은 이름의 폴더(config)를 생성하니, 아래 예시와 같이 각 실습별 디렉토리를 따로 만들어 주세요.

<!-- 디렉토리 예시 -->
Django_실습/

┣━ 📁01_3_basic/
┃   ┣━ 📁venv/
┃   ┗━ 📁config/

┗ 📁02_1_url/
    ┣━ 📁venv/
    ┗━ 📁config/
<!-- 디렉토리 예시 -->
Django_실습/

┣━ 📁01_3_basic/
┃   ┣━ 📁venv/
┃   ┗━ 📁config/

┗ 📁02_1_url/
    ┣━ 📁venv/
    ┗━ 📁config/

1. 개발 환경 설정

  1. Visual Studio Code를 실행하고 작업할 폴더를 엽니다. File > Open Folder에서 Django 프로젝트 폴더를 선택합니다.

  2. 상단의 메뉴 터미널 > 새 터미널 을 선택해 터미널을 엽니다.

  3. 터미널에서 다음 명령어를 실행하여 프로젝트 폴더를 생성하고 이동합니다.

    mkdir 02_1_url
    cd 02_1_url
    mkdir 02_1_url
    cd 02_1_url
  4. 가상 환경을 생성하고 활성화합니다. 앞서 했던 것처럼 Window와 Mac환경의 명령어가 다릅니다. # 뒤에는 주석입니다.

    python -m venv venv
    .\venv\Scripts\activate  # Windows
    source venv/bin/activate  # macOS/Linux
    python -m venv venv
    .\venv\Scripts\activate  # Windows
    source venv/bin/activate  # macOS/Linux
  5. Django를 설치합니다.

    pip install django
    pip install django

2. Django 프로젝트 생성

  1. Django 프로젝트를 생성합니다.

    django-admin startproject config .
    django-admin startproject config .
  2. 코드를 DB에 반영합니다.

    python manage.py migrate
    python manage.py migrate

3. Django 앱 생성

  1. main 앱을 생성합니다.

    python manage.py startapp main
    python manage.py startapp main
  2. config > settings.py 파일을 열어, ALLOWED_HOSTS 리스트에 "*"을 입력하고, INSTALLED_APPS 리스트에는 'main'을 추가합니다.

    # config > settings.py
    ALLOWED_HOSTS = ["*"]
    INSTALLED_APPS = [
        ...
        'main',
    ]
    # config > settings.py
    ALLOWED_HOSTS = ["*"]
    INSTALLED_APPS = [
        ...
        'main',
    ]

4. URL 설정

  • URL 구조
www.hojun.com/admin
www.hojun.com/   
www.hojun.com/blog           # blog_list (게시물 목록 보는 URL)
www.hojun.com/blog/1         # blog_details (게시물 상세)
www.hojun.com/blog/2         # blog_details (게시물 상세)
www.hojun.com/blog/3         # blog_details (게시물 상세)
www.hojun.com/accounts/hojun # accounts_details (유저 상세)
www.hojun.com/accounts/junho
www.hojun.com/admin
www.hojun.com/   
www.hojun.com/blog           # blog_list (게시물 목록 보는 URL)
www.hojun.com/blog/1         # blog_details (게시물 상세)
www.hojun.com/blog/2         # blog_details (게시물 상세)
www.hojun.com/blog/3         # blog_details (게시물 상세)
www.hojun.com/accounts/hojun # accounts_details (유저 상세)
www.hojun.com/accounts/junho
  1. 위의 url 구조에 맞춰 config > urls.py 파일을 다음과 같이 수정합니다.
    # config > urls.py
    from django.contrib import admin
    from django.urls import path
    from main.views import index, blog_list, blog_details, accounts_details
    #view.py에 정의할 함수들을 사용할 수 있게 불러옵니다.
     
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('', index),
        path('blog/', blog_list),
        path('blog/<int:pk>/', blog_details),
        path('accounts/<str:username>/', accounts_details),
    ]
    # config > urls.py
    from django.contrib import admin
    from django.urls import path
    from main.views import index, blog_list, blog_details, accounts_details
    #view.py에 정의할 함수들을 사용할 수 있게 불러옵니다.
     
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('', index),
        path('blog/', blog_list),
        path('blog/<int:pk>/', blog_details),
        path('accounts/<str:username>/', accounts_details),
    ]

path(URL, 함수명)는 Django에서 웹사이트의 주소와 그 주소에 해당하는 작업을 연결해 주는 역할을 합니다. 여기서 <int:pk>는 정수형 데이터를 pk라는 변수로 받아 함수로 전달하겠다는 의미이고, <str:username>은 문자열 데이터를 username이라는 변수로 받아 함수로 전달하겠다는 의미입니다. 이렇게 사용자의 입력 URL을 통해 원하는 함수에 전달할 수 있습니다.

예를 들어, www.hojun.com/blog/1에 접속하면 blog_details 함수에 pk=1이라는 데이터가 전달됩니다. www.hojun.com/accounts/hojun에 접속하면 accounts_details 함수에 username='hojun'이라는 데이터가 전달됩니다.

5. 뷰 함수 구현

설계한 URL에 접속했을 때 실제 보게될 화면을 main > view.py에서 구현할 수 있습니다. 이 부분에서는 HttpResponse를 사용한 방식과, render를 사용한 방식을 보여드리고자 합니다.

  • HttpResponse 방식
# main > view.py
from django.shortcuts import render
from django.http import HttpResponse
 
blog_list_db = [
    {
        "id": 1,
        "title": "장고는 너무 재미있어!!!",
        "content": "This is the content of blog 1",
        "author": "Author 1",
    },
    {
        "id": 2,
        "title": "파이썬도 너무 재미있어!!!!",
        "content": "This is the content of blog 2",
        "author": "Author 2",
    },
    {
        "id": 3,
        "title": "Life is too short, You need python!",
        "content": "This is the content of blog 3",
        "author": "Author 3",
    },
]
 
user_list_db = [
    {
        "id": 1,
        "username": "hojun",
        "email": "hojun@gmail.com",
        "password": "1234",
    },
    {
        "id": 2,
        "username": "jihun",
        "email": "jihun@gmail.com",
        "password": "1234",
    },
    {
        "id": 3,
        "username": "junho",
        "email": "junho@gmail.com",
        "password": "1234",
    },
]
 
def index(request):
    return HttpResponse("index")
 
def blog_list(request):
    blog_list_html = ""
    for blog in blog_list_db:
        blog_list_html += f'<li><a href="/blog/{blog['id']}">{blog['title']}</a></li>'
    return HttpResponse(f"""
    <h1>Blog List</h1>
    <ul>
        {blog_list_html}
    </ul>
""")
 
def blog_details(request, pk):
    blog = blog_list_db[pk-1]
    return HttpResponse(f"""
    <h1>{blog['title']}</h1>
    <p>{blog['content']}</p>
    <p>{blog['author']}</p>
""")
 
def accounts_details(request, username):
    finduser = None
    for user in user_list_db:
        if user['username'] == username:
            finduser = user
    if finduser is None:
        return HttpResponse("User not found")
    return HttpResponse(f"""
    <h1>{finduser['username']}</h1>
    <p>{finduser['email']}</p>
""")
# main > view.py
from django.shortcuts import render
from django.http import HttpResponse
 
blog_list_db = [
    {
        "id": 1,
        "title": "장고는 너무 재미있어!!!",
        "content": "This is the content of blog 1",
        "author": "Author 1",
    },
    {
        "id": 2,
        "title": "파이썬도 너무 재미있어!!!!",
        "content": "This is the content of blog 2",
        "author": "Author 2",
    },
    {
        "id": 3,
        "title": "Life is too short, You need python!",
        "content": "This is the content of blog 3",
        "author": "Author 3",
    },
]
 
user_list_db = [
    {
        "id": 1,
        "username": "hojun",
        "email": "hojun@gmail.com",
        "password": "1234",
    },
    {
        "id": 2,
        "username": "jihun",
        "email": "jihun@gmail.com",
        "password": "1234",
    },
    {
        "id": 3,
        "username": "junho",
        "email": "junho@gmail.com",
        "password": "1234",
    },
]
 
def index(request):
    return HttpResponse("index")
 
def blog_list(request):
    blog_list_html = ""
    for blog in blog_list_db:
        blog_list_html += f'<li><a href="/blog/{blog['id']}">{blog['title']}</a></li>'
    return HttpResponse(f"""
    <h1>Blog List</h1>
    <ul>
        {blog_list_html}
    </ul>
""")
 
def blog_details(request, pk):
    blog = blog_list_db[pk-1]
    return HttpResponse(f"""
    <h1>{blog['title']}</h1>
    <p>{blog['content']}</p>
    <p>{blog['author']}</p>
""")
 
def accounts_details(request, username):
    finduser = None
    for user in user_list_db:
        if user['username'] == username:
            finduser = user
    if finduser is None:
        return HttpResponse("User not found")
    return HttpResponse(f"""
    <h1>{finduser['username']}</h1>
    <p>{finduser['email']}</p>
""")

위에서는 가짜 데이터 blog_list_db와 user_list_db를 만들어 두었습니다. 이 데이터를 사용하여 각 URL에 접속했을 때 보여줄 화면을 만들고 있습니다. 아래 URL에 접속해보세요. 이를 통해 홈페이지가 어떻게 완성되는지를 확인할 수 있습니다.

http://127.0.0.1:8000/
http://127.0.0.1:8000/blog/
http://127.0.0.1:8000/blog/1/
http://127.0.0.1:8000/accounts/hojun/
http://127.0.0.1:8000/
http://127.0.0.1:8000/blog/
http://127.0.0.1:8000/blog/1/
http://127.0.0.1:8000/accounts/hojun/

그런데 이렇게 홈페이지가 만들어진다면 views.py 파일이 너무 길어지고, 유지보수가 어려워질 수 있습니다. 이런 문제를 해결하기 위해 render 방식을 사용합니다.

  • render 방식
# main > view.py
from django.shortcuts import render
from django.http import HttpResponse
 
blog_list_db = [
    {
        "id": 1,
        "title": "장고는 너무 재미있어!!!",
        "content": "This is the content of blog 1",
        "author": "Author 1",
    },
    {
        "id": 2,
        "title": "파이썬도 너무 재미있어!!!!",
        "content": "This is the content of blog 2",
        "author": "Author 2",
    },
    {
        "id": 3,
        "title": "Life is too short, You need python!",
        "content": "This is the content of blog 3",
        "author": "Author 3",
    },
]
 
user_list_db = [
    {
        "id": 1,
        "username": "hojun",
        "email": "hojun@gmail.com",
        "password": "1234",
    },
    {
        "id": 2,
        "username": "jihun",
        "email": "jihun@gmail.com",
        "password": "1234",
    },
    {
        "id": 3,
        "username": "junho",
        "email": "junho@gmail.com",
        "password": "1234",
    },
]
 
def index(request):
    return HttpResponse("index")
 
def blog_list(request):
    context = {"blog_list": blog_list_db, "hello": [10, 20, 30]}
    return render(request, "main/blog_list.html", context)
 
def blog_details(request, pk):
    blog = blog_list_db[pk - 1]
    context = {"blog": blog}
    return render(request, "main/blog_details.html", context)
 
def accounts_details(request, username):
    finduser = None
    for user in user_list_db:
        if user["username"] == username:
            finduser = user
            break
    if finduser is None:
        return HttpResponse("User not found")
    context = {"user": finduser}
    return render(request, "main/accounts_details.html", context)
# main > view.py
from django.shortcuts import render
from django.http import HttpResponse
 
blog_list_db = [
    {
        "id": 1,
        "title": "장고는 너무 재미있어!!!",
        "content": "This is the content of blog 1",
        "author": "Author 1",
    },
    {
        "id": 2,
        "title": "파이썬도 너무 재미있어!!!!",
        "content": "This is the content of blog 2",
        "author": "Author 2",
    },
    {
        "id": 3,
        "title": "Life is too short, You need python!",
        "content": "This is the content of blog 3",
        "author": "Author 3",
    },
]
 
user_list_db = [
    {
        "id": 1,
        "username": "hojun",
        "email": "hojun@gmail.com",
        "password": "1234",
    },
    {
        "id": 2,
        "username": "jihun",
        "email": "jihun@gmail.com",
        "password": "1234",
    },
    {
        "id": 3,
        "username": "junho",
        "email": "junho@gmail.com",
        "password": "1234",
    },
]
 
def index(request):
    return HttpResponse("index")
 
def blog_list(request):
    context = {"blog_list": blog_list_db, "hello": [10, 20, 30]}
    return render(request, "main/blog_list.html", context)
 
def blog_details(request, pk):
    blog = blog_list_db[pk - 1]
    context = {"blog": blog}
    return render(request, "main/blog_details.html", context)
 
def accounts_details(request, username):
    finduser = None
    for user in user_list_db:
        if user["username"] == username:
            finduser = user
            break
    if finduser is None:
        return HttpResponse("User not found")
    context = {"user": finduser}
    return render(request, "main/accounts_details.html", context)

render 함수를 사용하면 HTML 템플릿과 데이터(context)를 분리할 수 있어 유지보수가 편리해집니다. render에 대한 자세한 이야기는 02-4 에서 다루도록 하겠습니다. 이 실습에서는 render 방식을 사용해 주시면 됩니다.

6. 템플릿 생성

  1. main 앱 폴더 안에 templates 폴더를 생성합니다. templates 폴더 안에 다시 한번 main 폴더를 만들어 주세요.

  2. templates/main 폴더 안에 아래 3가지 HTML 파일들을 생성합니다.

    • blog_list.html
    • blog_details.html
    • accounts_details.html
  3. 하드 코딩 코드와 템플릿 태그 사용 코드 비교

  • blog_list.html 하드코딩
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>blog_list</title>
</head>
<body>
    <h1>blog_list 페이지 입니다.</h1>
    <ul>
        <li><a href="/blog/1">게시물1</a></li>
        <li><a href="/blog/2">게시물2</a></li>
        <li><a href="/blog/3">게시물3</a></li>
    </ul>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>blog_list</title>
</head>
<body>
    <h1>blog_list 페이지 입니다.</h1>
    <ul>
        <li><a href="/blog/1">게시물1</a></li>
        <li><a href="/blog/2">게시물2</a></li>
        <li><a href="/blog/3">게시물3</a></li>
    </ul>
</body>
</html>

하드코딩은 코드에 직접 데이터를 넣는 방식입니다. 예를 들어, 위의 코드에서 /blog/1처럼 URL을 직접 입력하는 것을 말합니다. 하드코딩은 간단해 보이지만, 나중에 URL이 바뀌면 모든 곳을 일일이 수정해야 해서 번거롭고 실수하기 쉽습니다.

이와 반대로 만약 유동적으로 변하는 데이터를 그대로 넣을 수 있다면, 데이터나 URL 구조가 변경되어도 코드를 거의 수정할 필요가 없어집니다. 코드 관리가 훨씬 쉬워지고, 실수할 가능성도 줄어든다는 것이죠. 결국 더 유연하고 유지보수가 쉬운 코드를 만들 수 있습니다. 아래는 장고에서 지원하는 템플릿 태그로 작성한 코드입니다.

  • blog_list.html 템플릿 태그
<!-- blog_list.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>blog_list</title>
</head>
<body>
    <h1>blog_list 페이지 입니다.</h1>
    <ul>
        {% for blog in blog_list %}
        <li><a href="/blog/{{blog.id}}">{{blog.title}}</a></li>
        {% endfor %}
    </ul>
 
    {% for i in hello %}
    <p>{{i}}</p>
    {% endfor %}
</body>
</html>
<!-- blog_list.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>blog_list</title>
</head>
<body>
    <h1>blog_list 페이지 입니다.</h1>
    <ul>
        {% for blog in blog_list %}
        <li><a href="/blog/{{blog.id}}">{{blog.title}}</a></li>
        {% endfor %}
    </ul>
 
    {% for i in hello %}
    <p>{{i}}</p>
    {% endfor %}
</body>
</html>

지금 이 프로젝트에서 필요한 템플릿 태그를 몇 가지 소개하겠습니다.

  • {# #}: 주석입니다.
  • {{ }}: 변수로 사용할 수 있습니다.
  • {% %}: 파이썬의 문법을 사용할 수 있습니다.

템플릿 태그에 대한 자세한 이야기는 02-5에서 다루도록 하겠습니다.

  1. 각 HTML 파일에 아래 내용을 추가합니다. 위에서 언급했던 blog_list.html도 함께 넣어두었습니다. 같은 코드입니다.
  • blog_list.html
<!-- blog_list.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>blog_list</title>
</head>
<body>
    <h1>blog_list 페이지 입니다.</h1>
    <ul>
        {% for blog in blog_list %}
        <li><a href="/blog/{{blog.id}}">{{blog.title}}</a></li>
        {% endfor %}
    </ul>
 
    {% for i in hello %}
    <p>{{i}}</p>
    {% endfor %}
</body>
</html>
<!-- blog_list.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>blog_list</title>
</head>
<body>
    <h1>blog_list 페이지 입니다.</h1>
    <ul>
        {% for blog in blog_list %}
        <li><a href="/blog/{{blog.id}}">{{blog.title}}</a></li>
        {% endfor %}
    </ul>
 
    {% for i in hello %}
    <p>{{i}}</p>
    {% endfor %}
</body>
</html>
  • blog_details.html
<!-- blog_details.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>blog_details</title>
</head>
<body>
    <h1>blog_details</h1>
    <h2>{{ blog.title }}</h2>
    <p>{{ blog.author }}</p>
    <p>{{ blog.content }}</p>
</body>
</html>
<!-- blog_details.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>blog_details</title>
</head>
<body>
    <h1>blog_details</h1>
    <h2>{{ blog.title }}</h2>
    <p>{{ blog.author }}</p>
    <p>{{ blog.content }}</p>
</body>
</html>
  • account_details.html
<!-- account_details.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>userinfo</title>
</head>
<body>
    <h1>userinfo 페이지</h1>
    <p>{{user.username}}</p>
    <p>{{user.email}}</p>
</body>
</html>
<!-- account_details.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>userinfo</title>
</head>
<body>
    <h1>userinfo 페이지</h1>
    <p>{{user.username}}</p>
    <p>{{user.email}}</p>
</body>
</html>

7. 서버 실행 및 테스트

  1. 서버를 실행합니다.

    python manage.py runserver
    python manage.py runserver
  2. 브라우저에서 URL들을 테스트해보세요.

2장 URL 처리와 템플릿 태그2.2 템플릿 상속과 정적 파일