WeniVooks

검색

Django 베이스캠프

템플릿 상속과 정적 파일

앞서 살펴본 것처럼 하나의 앱에서 모든 URL을 처리하진 않습니다. 이번 시간에는 여러개의 앱을 통해 어떻게 URL들이 분기되는지 살펴보도록 하겠습니다. 또한, 템플릿 폴더 변경, 템플릿 상속과 정적 파일을 사용하는 방법에 대해 알아보겠습니다.

템플릿 폴더는 settings.py에 정의된 폴더로 데이터를 읽습니다. 템플릿 폴더를 변경하면 Django는 해당 폴더에서 템플릿 파일을 찾아 읽어옵니다. 템플릿 폴더를 변경하면, 템플릿 파일을 관리하기가 편리해집니다.

템플릿 상속은 웹사이트의 공통 디자인을 한 곳에 모아두고 다른 페이지들이 이를 가져다 쓰는 방법입니다. 파이썬 클래스의 상속처럼, 각 페이지가 공통 디자인을 상속받아 사용하죠. 이렇게 하면 같은 코드를 여러 번 쓰지 않아도 됩니다.

정적(static) 파일은 웹사이트를 꾸미고 기능을 추가하는 데 필요한 고정된 파일들입니다. 예를 들어, CSS, JavaScript, 그리고 사진이나 로고, 영상, 이미지 파일들이 여기에 속합니다. render 함수를 사용하게 되면 이 경로가 settings.py에 있는 경로로 변경이 되기 때문에 학습이 필요합니다.

1. 개발 환경 설정

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

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

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

    mkdir 02_2_template
    cd 02_2_template
    mkdir 02_2_template
    cd 02_2_template
  4. 가상 환경을 생성하고 활성화합니다.

    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, blog 앱을 생성합니다.

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

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

4. URL 설정

4.1 메인 urls.py 설정

아래는 URL 구조를 어떻게 설정할 지에 대해 작성한 것입니다. 이처럼 기획 단계에서 url의 구조를 어떻게 구성할 것인지에 대해서 먼저 작성해보는 것이 좋습니다. 전체적인 구조를 파악하기 쉽고, 복잡한 코드에서도 헷갈리지 않을 수 있습니다.

  • main 앱
URL views 함수이름 html 파일이름 비고
' ' index index.html
'about/' about about.html
'contact/' contact contact.html
  • blog 앱
URL views 함수이름 html 파일이름 비고
'blog/' blog_list blog_list.html
'blog/<int:pk>' blog_detail blog_detail.html 게시물이 없을 경우에는 404로 연결

이렇게 기획단에서 URL을 구성하면, 사용자가 웹사이트에 접속했을 때 어떤 페이지로 이동할지 쉽게 파악할 수 있습니다. 코드를 작성하기에도 편리하고, 나중에 유지보수를 할 때에도 편리합니다.

# config > urls.py
from django.contrib import admin
from django.urls import path, include
 
urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("main.urls")),
    path("blog/", include("blog.urls")),
]
# config > urls.py
from django.contrib import admin
from django.urls import path, include
 
urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("main.urls")),
    path("blog/", include("blog.urls")),
]

이전에는 각 앱의 views.py로 바로 연결을 했지만, 이번에는 include를 사용했습니다. URL 구조가 복잡해지면, 각 앱에서 URL구조를 별도로 관리할 수 있도록 include를 사용하는 것이 편리합니다. 이 함수를 이용하면 include() 안에 있는 파일로 URL을 연결할 수 있습니다. include("main.urls")라면 main 폴더의 urls.py 파일로 URL을 연결하겠다는 의미입니다.

4.2 main 앱 urls.py 설정

각 앱의 폴더에는 urls.py가 없습니다. app을 설치할 때 기본적으로 생성이 되는 파일이 아닙니다. 각 폴더마다 urls.py을 만들어주세요.

# main > urls.py
from django.urls import path
from . import views
 
urlpatterns = [
    path("", views.index, name="index"),
    path("about/", views.about, name="about"),
    path("contact/", views.contact, name="contact"),
]
# main > urls.py
from django.urls import path
from . import views
 
urlpatterns = [
    path("", views.index, name="index"),
    path("about/", views.about, name="about"),
    path("contact/", views.contact, name="contact"),
]

path의 세번째 파라미터인 name은 URL의 고유 별칭입니다. 템플릿 태그를 사용할 때, name을 이용해 이 URL에 접근할 수 있습니다. name을 어떻게 사용하는지에 대해서는 추후 템플릿 부분에서 다시 설명하겠습니다.

4.3 blog 앱 urls.py 설정
# blog > urls.py
from django.urls import path
from . import views
 
urlpatterns = [
    path("", views.blog_list, name="blog_list"), # 'blog/'로 접속했을 때
    path("<int:pk>/", views.blog_detail, name="blog_detail"), # 'blog/글번호/'로 접속했을 때
]
# blog > urls.py
from django.urls import path
from . import views
 
urlpatterns = [
    path("", views.blog_list, name="blog_list"), # 'blog/'로 접속했을 때
    path("<int:pk>/", views.blog_detail, name="blog_detail"), # 'blog/글번호/'로 접속했을 때
]

blog > urls.py 에서의 기본 URL은 'blog/'입니다. config > urls.py에 작성했던 path("blog/", include("blog.urls")) 에서 확인할 수 있습니다.

5. view.py 설정

5.1 main 앱 views.py
# main > views.py
from django.shortcuts import render
 
def index(request):
    return render(request, "main/index.html")
 
def about(request):
    return render(request, "main/about.html")
 
def contact(request):
    return render(request, "main/contact.html")
# main > views.py
from django.shortcuts import render
 
def index(request):
    return render(request, "main/index.html")
 
def about(request):
    return render(request, "main/about.html")
 
def contact(request):
    return render(request, "main/contact.html")

여기서 'main/'은 템플릿 디렉토리 내의 하위 폴더를 나타냅니다. 이렇게 폴더명을 명시하는 것이 좋습니다. 왜냐하면 여러 앱에서 같은 이름의 템플릿 파일이 있을 경우 Django는 settings.py > INSTALLED_APPS의 리스트 순으로 템플릿 디렉토리를 검색하므로, 다른 템플릿 파일이 사용되는 것을 방지할 수 있습니다.

5.2 blog 앱 views.py
# blog > views.py
from django.shortcuts import render
 
blog_database = [
    {
        "id": 1,
        "title": "제목1",
        "content": "내용1",
        "created_at": "2021-02-22",
        "updated_at": "2021-02-22",
        "author": "홍길동",
        "category": "일상",
        "tag": ["태그1", "태그2"],
        "view_count": 0,
        "thumbnail": "https://picsum.photos/200/300",
        "like_count": 3,
        "like_user": [10, 20, 21],
    },
    {
        "id": 2,
        "title": "제목2",
        "content": "내용2",
        "created_at": "2021-02-23",
        "updated_at": "2021-02-23",
        "author": "김철수",
        "category": "일기",
        "tag": ["태그1", "태그3"],
        "view_count": 0,
        "thumbnail": "https://picsum.photos/200/300",
        "like_count": 10,
        "like_user": [10, 20, 21, 22, 23, 24, 25, 26, 27, 28],
    },
    {
        "id": 3,
        "title": "제목3",
        "content": "내용3",
        "created_at": "2021-02-24",
        "updated_at": "2021-02-24",
        "author": "이영희",
        "category": "맛집",
        "tag": ["태그1", "태그3"],
        "view_count": 0,
        "thumbnail": "https://picsum.photos/200/300",
        "like_count": 20,
        "like_user": [10, 20, 21, 22, 23, 24, 25, 26, 27, 28],
    },
    {
        "id": 4,
        "title": "제목4",
        "content": "내용4",
        "created_at": "2021-02-25",
        "updated_at": "2021-02-25",
        "author": "박민수",
        "category": "여행",
        "tag": ["태그1", "태그3"],
        "view_count": 0,
        "thumbnail": "https://picsum.photos/200/300",
        "like_count": 30,
        "like_user": [10, 20, 21, 22, 23, 24, 25, 26, 27, 28],
    },
]
 
def blog_list(request):
    # blog_database = Blog.objects.all() # 실제 DB 사용하는 코드
    context = {"object_list": blog_database}
    return render(request, "blog/blog_list.html", context)
 
def blog_detail(request, pk):
    # blog = Blog.objects.get(pk=pk) # 실제 DB 사용하는 코드
    context = {"object": blog_database[pk - 1]}
    return render(request, "blog/blog_detail.html", context)
# blog > views.py
from django.shortcuts import render
 
blog_database = [
    {
        "id": 1,
        "title": "제목1",
        "content": "내용1",
        "created_at": "2021-02-22",
        "updated_at": "2021-02-22",
        "author": "홍길동",
        "category": "일상",
        "tag": ["태그1", "태그2"],
        "view_count": 0,
        "thumbnail": "https://picsum.photos/200/300",
        "like_count": 3,
        "like_user": [10, 20, 21],
    },
    {
        "id": 2,
        "title": "제목2",
        "content": "내용2",
        "created_at": "2021-02-23",
        "updated_at": "2021-02-23",
        "author": "김철수",
        "category": "일기",
        "tag": ["태그1", "태그3"],
        "view_count": 0,
        "thumbnail": "https://picsum.photos/200/300",
        "like_count": 10,
        "like_user": [10, 20, 21, 22, 23, 24, 25, 26, 27, 28],
    },
    {
        "id": 3,
        "title": "제목3",
        "content": "내용3",
        "created_at": "2021-02-24",
        "updated_at": "2021-02-24",
        "author": "이영희",
        "category": "맛집",
        "tag": ["태그1", "태그3"],
        "view_count": 0,
        "thumbnail": "https://picsum.photos/200/300",
        "like_count": 20,
        "like_user": [10, 20, 21, 22, 23, 24, 25, 26, 27, 28],
    },
    {
        "id": 4,
        "title": "제목4",
        "content": "내용4",
        "created_at": "2021-02-25",
        "updated_at": "2021-02-25",
        "author": "박민수",
        "category": "여행",
        "tag": ["태그1", "태그3"],
        "view_count": 0,
        "thumbnail": "https://picsum.photos/200/300",
        "like_count": 30,
        "like_user": [10, 20, 21, 22, 23, 24, 25, 26, 27, 28],
    },
]
 
def blog_list(request):
    # blog_database = Blog.objects.all() # 실제 DB 사용하는 코드
    context = {"object_list": blog_database}
    return render(request, "blog/blog_list.html", context)
 
def blog_detail(request, pk):
    # blog = Blog.objects.get(pk=pk) # 실제 DB 사용하는 코드
    context = {"object": blog_database[pk - 1]}
    return render(request, "blog/blog_detail.html", context)

6. 템플릿 설정

이번에는 템플릿을 앱 아래 두는 것이 아닌, 프로젝트 최상위 폴더에 템플릿을 두는 방법을 알아보겠습니다. 이렇게 하면 앱별로 템플릿을 관리하기가 편리합니다.

📁02_2_template/
┣━ 📁main/
┣━ 📁blog/
┗━ 📁templates/
    ┗━ 📁blog/
        ┣━ 📄blog_list.html
        ┗━ 📄blog_detail.html
📁02_2_template/
┣━ 📁main/
┣━ 📁blog/
┗━ 📁templates/
    ┗━ 📁blog/
        ┣━ 📄blog_list.html
        ┗━ 📄blog_detail.html

이렇게 html 파일을 만들어 주세요.

  • templates > blog > blog_list.html
  • templates > blog > blog_detail.html
6.1 템플릿 설정 변경

기본 템플릿 폴더를 변경합니다. 앞으로는 mysite > templates라는 폴더에서 통합 관리합니다. 폴더의 위치가 달라졌으니, 설정이 달라져야 합니다. settings.pyTEMPLATES안에 있는 DIRS의 경로를 [BASE_DIR / "templates"] 로 수정해 줍니다.

# config > settings.py
 
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"], # 이 부분 입니다.
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]
# config > settings.py
 
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"], # 이 부분 입니다.
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

템플릿 파일의 위치는 Django 프로젝트의 구조에 따라 달라질 수 있습니다. 항상 동일한 방식으로 템플릿을 관리하지 않습니다.

앱 내부에 템플릿이 있다면, 모듈화에 유리하고, 앱별로 템플릿을 관리할 수 있습니다. 그러나 프로젝트가 커진다면, 유지보수가 어려울 수 있습니다. 템플릿이 앱 내부가 아닌 외부에 있다면, 전체 프로젝트의 템플릿을 한 곳에서 관리할 수 있어, 유지보수에 좋습니다. 그러나 앱별로 모듈화를 하기에 어려울 수 있습니다.

BASE_DIR / "templates" 이 부분은 처음 보셨을 수도 있습니다. 3.3버전까지는 os.path.join(BASE_DIR, 'templates')로 작성했지만, 3.4버전부터는 Path를 사용해 BASE_DIR / 'templates'로 작성하는 것이 권장됩니다. 이렇게 하면 코드가 간결해지고, 가독성이 좋아집니다. 또한 os 모듈의 경우 window와 linux의 경로 구분자가 다르기 때문에, 이를 통한 문제가 종종 발생이 되었기에, 이를 방지하기 위해 Path를 사용하는 것이 좋습니다.

아래 실습을 통해 간단하게 Path를 사용하는 방법을 알아보겠습니다.

python
>>> from pathlib import Path
>>> file_path = './hello/world'
>>> p = Path(file_path)
>>> p / 'templates'
>>> p / 'templates' / 'hojun'
>>> Path(__name__).resolve()
>>> Path(__name__).resolve().parent 
# __file__은 현재 파일의 경로, 지금은 python shell이기 때문에 __name__을 사용합니다.
python
>>> from pathlib import Path
>>> file_path = './hello/world'
>>> p = Path(file_path)
>>> p / 'templates'
>>> p / 'templates' / 'hojun'
>>> Path(__name__).resolve()
>>> Path(__name__).resolve().parent 
# __file__은 현재 파일의 경로, 지금은 python shell이기 때문에 __name__을 사용합니다.
6.2 템플릿 내용

템플릿 경로를 설정했기 때문에 templates폴더 아래에 각각의 파일을 만들어주세요.

  • blog_list.html
<!-- templates/blog/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></title>
</head>
<body>
    <h1>bloglist</h1>
    <ul>
        {% for blog in object_list %}
        <li>
            {{ forloop.counter }}
            <a href="{% url 'blog_detail' blog.id %}">{{ blog.title }}</a>
        </li>
        {% endfor %}
    </ul>
</body>
</html>
<!-- templates/blog/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></title>
</head>
<body>
    <h1>bloglist</h1>
    <ul>
        {% for blog in object_list %}
        <li>
            {{ forloop.counter }}
            <a href="{% url 'blog_detail' blog.id %}">{{ blog.title }}</a>
        </li>
        {% endfor %}
    </ul>
</body>
</html>

위 코드<a href="{% url 'blog_detail' blog.id %}">{{ blog.title }}</a>에서 'blog_detail'urls.py에서 작성했던 세번째 파라미터 name입니다. 아래 코드를 참고해주세요.

# blog > urls.py
path("<int:pk>/", views.blog_detail, name="blog_detail"),
# blog > urls.py
path("<int:pk>/", views.blog_detail, name="blog_detail"),

위와 같이 이 name를 가진 URL 패턴을 찾아 현재 blog의 id 값(글 번호)을 넣어 URL을 완성합니다.
예를 들어 blog.id가 5라면 'blog/5/'와 같은 URL이 만들어집니다. 이 URL을 링크에 넣어 사용자가 클릭하면 해당 id의 블로그 글 상세 페이지로 이동하게 됩니다. 이때 이 링크의 텍스트가 blog.title 입니다.

  • blog_detail.html
<!-- templates > blog > blog_detail.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>blogdetail</title>
</head>
<body>
    <h1>blogdetail</h1>
    <h2>{{ object.title }}</h2>
    <p>{{ object.content }}</p>
    <p>{{ object.created_at }}</p>
    <p>{{ object.updated_at }}</p>
    <a href="{% url 'blog_list' %}">목록</a>
</body>
</html>
<!-- templates > blog > blog_detail.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>blogdetail</title>
</head>
<body>
    <h1>blogdetail</h1>
    <h2>{{ object.title }}</h2>
    <p>{{ object.content }}</p>
    <p>{{ object.created_at }}</p>
    <p>{{ object.updated_at }}</p>
    <a href="{% url 'blog_list' %}">목록</a>
</body>
</html>

7. 템플릿 상속

웹사이트를 만들다 보면 모든 페이지에 반복되는 요소들이 있습니다. 웹사이트들을 보면 있는 메뉴들이나, 페이지 끝에 있는 설명란 같은 것들은 반복되는 요소들 입니다. 이런 요소들을 .html 파일마다 일일이 작성하면 어떻게 될까요? 시간도 많이 걸리고, 나중에 이 부분을 수정해야 할 때는 .html 페이지를 하나하나 고쳐야 해서 규모가 클 수록 유지보수가 힘들어집니다. 예를 들어, 메뉴에 새 항목을 하나 추가하려면 수백 개의 페이지를 모두 수정해야 할 수도 있습니다.

이런 문제를 해결하기 위해 템플릿 상속이라는 방법을 사용합니다. 템플릿 상속을 이용하면 공통된 부분은 한 번만 작성하고, 각 페이지에서는 변경되는 부분만 작성하면 됩니다. 이렇게 하면 작업 시간도 줄이고, 수정할 때도 한 곳만 바꾸면 모든 페이지에 적용되어 관리가 훨씬 쉬워집니다.

부모 코드의 형태는 아래와 같습니다.

<!DOCTYPE html>
<head>
    <title>weniv blog</title>
</head>
<body>
    {% block contents %} 
    {% endblock %}
</body>
</html>
<!DOCTYPE html>
<head>
    <title>weniv blog</title>
</head>
<body>
    {% block contents %} 
    {% endblock %}
</body>
</html>

자식 코드는 아래와 같은 형태가 됩니다.

{% extends 'base/base.html '%}
 
{% block contents %}
<h2>자식 코드입니다</h2>
<p>여기에 여러 템플릿 태그도 함께 들어갑니다.</p>
<p>여기서 block 뒤에 오는 contents는 이름입니다. 여러분이 직접 명명하셔도 됩니다.</p>
{% endblock %}
{% extends 'base/base.html '%}
 
{% block contents %}
<h2>자식 코드입니다</h2>
<p>여기에 여러 템플릿 태그도 함께 들어갑니다.</p>
<p>여기서 block 뒤에 오는 contents는 이름입니다. 여러분이 직접 명명하셔도 됩니다.</p>
{% endblock %}
  1. templates 폴더에 base > base.html 파일을 만들어 주세요.
<!-- templates > base > base.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>weniv blog</title>
</head>
<body>
    <header>
        <h1>weniv blog</h1>
        <nav>
            <ul>
                <li>메뉴1</li>
                <li>메뉴2</li>
                <li>메뉴3</li>
            </ul>
        </nav>
    </header>
 
    <main>
        {% block contents %} 
        <p>test 이어서 써집니다1</p>
        <p>test 이어서 써집니다2</p>
        {% endblock %}
    </main>
 
    {% block binky %}
    {% endblock %}
 
    <footer>
        <p>저작권은 weniv에게 있습니다.</p>
    </footer>
 
    {% block mura %}
    {% endblock %}
</body>
</html>
<!-- templates > base > base.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>weniv blog</title>
</head>
<body>
    <header>
        <h1>weniv blog</h1>
        <nav>
            <ul>
                <li>메뉴1</li>
                <li>메뉴2</li>
                <li>메뉴3</li>
            </ul>
        </nav>
    </header>
 
    <main>
        {% block contents %} 
        <p>test 이어서 써집니다1</p>
        <p>test 이어서 써집니다2</p>
        {% endblock %}
    </main>
 
    {% block binky %}
    {% endblock %}
 
    <footer>
        <p>저작권은 weniv에게 있습니다.</p>
    </footer>
 
    {% block mura %}
    {% endblock %}
</body>
</html>
  1. templates 폴더에 blog > blog_list.html, blog_detail.html 파일을 수정해주세요.
  • blog_list.html
<!-- templates > blog > blog_list.html -->
{% extends 'base/base.html '%}
 
{% block contents %}
<h2>bloglist</h2>
<ul>
    {% for blog in object_list %}
    <li>
        {{ forloop.counter }}
        <a href="{% url 'blog_detail' blog.id %}">{{ blog.title }}</a>
    </li>
    {% endfor %}
</ul>
{% endblock %}
 
{% block binky %}
<h2>binky test</h2>
{% endblock %}
 
{% block mura %}
<script>
    console.log('mura test');
</script>
{% endblock %}
<!-- templates > blog > blog_list.html -->
{% extends 'base/base.html '%}
 
{% block contents %}
<h2>bloglist</h2>
<ul>
    {% for blog in object_list %}
    <li>
        {{ forloop.counter }}
        <a href="{% url 'blog_detail' blog.id %}">{{ blog.title }}</a>
    </li>
    {% endfor %}
</ul>
{% endblock %}
 
{% block binky %}
<h2>binky test</h2>
{% endblock %}
 
{% block mura %}
<script>
    console.log('mura test');
</script>
{% endblock %}
  • blog_detail.html
<!-- templates > blog > blog_detail.html -->
{% extends 'base/base.html '%}
 
{% block contents %}
<h2>{{ object.title }}</h2>
<p>{{ object.content }}</p>
<p>{{ object.created_at }}</p>
<p>{{ object.updated_at }}</p>
<a href="{% url 'blog_list' %}">목록</a>
{% endblock %}
<!-- templates > blog > blog_detail.html -->
{% extends 'base/base.html '%}
 
{% block contents %}
<h2>{{ object.title }}</h2>
<p>{{ object.content }}</p>
<p>{{ object.created_at }}</p>
<p>{{ object.updated_at }}</p>
<a href="{% url 'blog_list' %}">목록</a>
{% endblock %}

자식코드에서 블록의 위치가 어디에 있는지에 상관 없이 최종적으로 보여지는 템플릿은 자식과 부모 코드가 합쳐진 모습으로 보여집니다.

템플릿 상속

8. static 파일

static 폴더는 웹사이트를 꾸미고 기능을 추가하는 데 필요한 고정된 파일들입니다. 예를 들어, CSS, JavaScript, 그리고 사진이나 로고, 영상, 이미지 파일들이 여기에 속합니다. 이 파일들은 서버에서 사용자에게 전달되어 웹사이트를 꾸며주거나, 기능을 추가해줍니다.

render함수를 이용하면 연결된 .html 파일, 템플릿 파일의 경로가 settings.py에 있는 경로로 변경이 되기 때문에, static 파일을 사용할 때 주의가 필요합니다. 이번 시간에는 static 파일을 사용하는 방법에 대해 알아보겠습니다.

  1. 프로젝트 최상위에 'static' 폴더를 생성합니다.
  2. 'static' 폴더 안에 'css'와 'js' 폴더를 만듭니다.
  3. 각 폴더에 'custom.css'와 'custom.js' 파일을 생성합니다.

최종적으로 아래와 같은 구조가 됩니다.

📁02_2_template/
┣━ 📁main/
┣━ 📁blog/
┣━ 📁config/
┣━ 📁templates/
┗━ 📁static/
    ┣━ 📁css/
    ┃   ┗━ 📄custom.css
    ┗━ 📁js/
        ┗━ 📄custom.js
📁02_2_template/
┣━ 📁main/
┣━ 📁blog/
┣━ 📁config/
┣━ 📁templates/
┗━ 📁static/
    ┣━ 📁css/
    ┃   ┗━ 📄custom.css
    ┗━ 📁js/
        ┗━ 📄custom.js
8.1. static 설정

settings.py의 설정을 변경해야합니다. STATICFILES_DIRS = [BASE_DIR / "static"] 코드를 추가합니다.

  • settings.py
# config > settings.py 
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"] #이 부분 입니다.
# config > settings.py 
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"] #이 부분 입니다.
8.2 static 파일 생성
/* custom.css */
h1, h2{
    color: green
}
/* custom.css */
h1, h2{
    color: green
}
// custom.js
console.log('Hello, World!');
// custom.js
console.log('Hello, World!');
8.3 static 파일 적용
  1. templates > base.html 파일 맨 위에 {% load static %} 를 추가합니다.
  2. {% static '파일경로' %} 형식으로 파일 경로를 수정해 줍니다.
  • base.html
<!-- templates > base.html -->
{% load static %}
<!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>weniv blog</title>
    <!-- css 경로 수정 -->
    <link rel="stylesheet" href="{% static 'css/custom.css' %}">
</head>
<body>
    <header>
        <h1>weniv blog</h1>
        <nav>
            <ul>
                <li>메뉴1</li>
                <li>메뉴2</li>
                <li>메뉴3</li>
            </ul>
        </nav>
    </header>
 
    <main>
        {% block contents %}
        <p>test 이어서 써집니다1</p>
        <p>test 이어서 써집니다2</p>
        {% endblock %}
    </main>
 
    {% block binky %}
    {% endblock %}
 
    <footer>
        <p>저작권은 weniv에게 있습니다.</p>
    </footer>
 
    {% block mura %}
    {% endblock %}
    <!-- js 경로 수정-->
    <script src="{% static 'js/custom.js' %}"></script>
</body>
</html>
<!-- templates > base.html -->
{% load static %}
<!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>weniv blog</title>
    <!-- css 경로 수정 -->
    <link rel="stylesheet" href="{% static 'css/custom.css' %}">
</head>
<body>
    <header>
        <h1>weniv blog</h1>
        <nav>
            <ul>
                <li>메뉴1</li>
                <li>메뉴2</li>
                <li>메뉴3</li>
            </ul>
        </nav>
    </header>
 
    <main>
        {% block contents %}
        <p>test 이어서 써집니다1</p>
        <p>test 이어서 써집니다2</p>
        {% endblock %}
    </main>
 
    {% block binky %}
    {% endblock %}
 
    <footer>
        <p>저작권은 weniv에게 있습니다.</p>
    </footer>
 
    {% block mura %}
    {% endblock %}
    <!-- js 경로 수정-->
    <script src="{% static 'js/custom.js' %}"></script>
</body>
</html>

상대 경로로 되어 있는 기존의 모든 파일 경로를 {% static %} 형식으로 바꿔줘야 합니다.

  • 상대경로 : 현재 위치를 기준으로 파일이나 폴더의 위치를 나타내는 경로 ./images/cat.jpg
  • 절대경로 : 최상위 폴더부터 시작하는 전체 경로 https://example.com/images/cat.jpg

주로 수정해야 할 부분으로 아래 4가지가 있습니다.

  1. <script> 태그의 src 속성
  2. <img> 태그의 src 속성
  3. CSS의 background-image: url() 부분
  4. <link> 태그의 href 속성

만약 수정해야 할 파일이 많다면, 자동화 스크립트(gen.py)를 만드는 것도 좋은 방법입니다. 하드코딩(예: /static/css/styles.css)도 작동하지만, 유지보수를 위해 {% static %} 사용을 권장합니다.

9. 실행 및 테스트

  1. 서버를 실행합니다.
    python manage.py runserver
    python manage.py runserver
  2. 브라우저에서 URL을 테스트해보세요.
2.1 URL 처리와 템플릿 태그 실습2.3 URL 추가 설명