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. 개발 환경 설정
-
Visual Studio Code를 실행하고 작업할 폴더를 엽니다.
File > Open Folder
에서 Django 프로젝트 폴더를 선택합니다. -
상단의 메뉴
터미널 > 새 터미널
을 선택해 터미널을 엽니다. -
터미널에서 다음 명령어를 실행하여 프로젝트 폴더를 생성하고 이동합니다.
mkdir 02_1_url cd 02_1_url
mkdir 02_1_url cd 02_1_url
-
가상 환경을 생성하고 활성화합니다. 앞서 했던 것처럼 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
-
Django를 설치합니다.
pip install django
pip install django
2. Django 프로젝트 생성
-
Django 프로젝트를 생성합니다.
django-admin startproject config .
django-admin startproject config .
-
코드를 DB에 반영합니다.
python manage.py migrate
python manage.py migrate
3. Django 앱 생성
-
main
앱을 생성합니다.python manage.py startapp main
python manage.py startapp main
-
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
- 위의 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. 템플릿 생성
-
main
앱 폴더 안에templates
폴더를 생성합니다.templates
폴더 안에 다시 한번main
폴더를 만들어 주세요. -
templates/main
폴더 안에 아래 3가지 HTML 파일들을 생성합니다.blog_list.html
blog_details.html
accounts_details.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>
<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에서 다루도록 하겠습니다.
- 각 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. 서버 실행 및 테스트
-
서버를 실행합니다.
python manage.py runserver
python manage.py runserver
-
브라우저에서 URL들을 테스트해보세요.