Django를 사용하여 처음부터 블로그 구축 IV

2024. 2. 11. 19:40python/intermediate

In [ ]:
%cd django-blog
!source venv/bin/activate
!python -m pip install Django
!python manage.py runserver​
In [ ]:
!python manage.py startapp blog

 

템플릿 구축

템플릿은 Django 뷰에서 전송된 동적 콘텐츠를 렌더링하는 기능이 있는 HTML 파일입니다. Jinja 와 같은 인기 있는 템플릿 엔진이 있습니다 . 그러나 템플릿에서 멋진 작업을 수행할 계획이 없다면 Django에 내장된 템플릿 언어를 사용할 수 있습니다 .

render() 기능 보기은 앱 디렉터리 내부에 있는 디렉터리에서 HTML 템플릿을 찾습니다 . templates/서로 다른 앱의 템플릿은 동일한 이름을 가질 수 있으므로 디렉터리 내에 앱 이름이 포함된 하위 디렉터리를 추가하는 것도 모범 사례입니다 templates/.

template/디렉터리와 이름이 지정된 하위 디렉터리를 만든 blog/다음 그 안에 템플릿 파일을 만듭니다.

In [ ]:
!mkdir -p blog/templates/blog
!touch blog/templates/blog/index.html
!touch blog/templates/blog/category.html
!touch blog/templates/blog/detail.html

작업할 첫 번째 템플릿은 index.html입니다. for 루프를 사용하여 모든 게시물을 반복합니다. 각 게시물에 대해 제목과 본문 일부가 표시됩니다.

In [ ]:
<!-- blog/templates/blog/index.html -->

{% block page_title %}
    <h2>Blog Posts</h2>
{% endblock page_title %}

{% block page_content %}
    {% block posts %}
        {% for post in posts %}
            <h3><a href="{% url 'blog_detail' post.pk %}">{{ post.title }}</a></h3>
            <small>
                {{ post.created_on.date }} | Categories:
                {% for category in post.categories.all %}
                    <a href="{% url 'blog_category' category.name %}">
                        {{ category.name }}
                    </a>
                {% endfor %}
            </small>
            <p>{{ post.body | slice:":400" }}...</p>
        {% endfor %}
    {% endblock posts %}
{% endblock page_content %}

템플릿이 유효한 HTML 페이지로 렌더링되지 않음을 알 수 있습니다. 이를 위해 및 같은 필수 HTML 요소가 누락되었습니다. 이 문제는 나중에 처리하겠습니다. 지금은 Django 템플릿에서 context 사전을 사용하여 작업하는 방법에 집중하세요.

9행에서는 posts 값을 반복합니다. 이는 context 사전이 이미 압축 해제되어 있으므로 Django 템플릿의 키에 직접 액세스할 수 있음을 의미합니다.

for 루프 내에서, 10행의 .title 같이 post의 속성에 액세스할 수 있습니다. 게시물 제목을 인수로 정수를 사용하는 blog_detail 이라는 URL을 가리키는 하이퍼링크로 묶습니다. 이 정수는 게시물의 고유한 기본 키 값인 pk 입니다.

제목 아래에는 게시물의 .created_on 속성과 카테고리가 표시됩니다. 13행에서는 또 다른 for 루프를 사용하여 게시물에 할당된 모든 카테고리를 반복합니다.

19행에서는 블로그 색인을 더 쉽게 읽을 수 있도록 템플릿 필터인 slice를 사용하여 게시물 본문을 400자로 자릅니다. 템플릿 필터에 대해 자세히 알아보려면 내장된 태그 및 필터 에 대한 튜토리얼을 확인하세요 .

index.html 템플릿의 또 다른 흥미로운 부분은 {% block %} 템플릿 태그를 사용하는 것입니다. 이 템플릿 태그를 사용하면 상위 템플릿을 확장하는 하위 템플릿에서 사용하거나 재정의할 수 있는 콘텐츠 블록을 정의할 수 있습니다.

index.html의 하위 템플릿은 category.html입니다. 이 템플릿은 거의 동일해 보입니다. 템플릿이 보기에서 받는 모든 게시물이 나열되어야 합니다. 즉, category.html과 유일한 차이점은 헤드라인입니다.

In [ ]:
<!-- blog/templates/blog/category.html -->

{% extends "blog/index.html" %}

{% block page_title %}
<h2>{{ category }}</h2>
{% endblock page_title %}

상위 템플릿을 확장하려면 하위 템플릿 시작 부분에 {% extends %} 태그를 사용해야 합니다. 그런 다음 {% extends %} 태그 내에서 확장하려는 템플릿을 정의합니다.

상위 템플릿을 참조한 후에는 상속하거나 재정의할 블록을 결정할 수 있습니다. 하위 템플릿에서 블록을 참조하지 않으면 상위 블록을 그대로 상속합니다. 또는 새 콘텐츠로 상위 블록을 참조할 수 있습니다.

여기서는 title 블록만 재정의하고 index.html 의 posts 블록은 재정의 하지 않습니다. 상위 템플릿의 posts 블록을 참조하지 않으므로 이 블록의 index.html 콘텐츠를 상속합니다. 이것이 바로 blog_category() 뷰가 제공하는 모든 게시물을 표시하려는 것입니다.

마지막 템플릿은 detail.html템플릿입니다. 이 템플릿에서는 게시물의 제목과 전체 본문을 표시합니다.

게시물 제목과 본문 사이에 게시물이 작성된 날짜와 카테고리가 표시됩니다.

In [ ]:
<!--  blog/templates/blog/detail.html -->

{% block page_title %}
    <h2>{{ post.title }}</h2>
{% endblock page_title %}

{% block page_content %}
    <small>
        {{ post.created_on.date }} | Categories:
        {% for category in post.categories.all %}
            <a href="{% url 'blog_category' category.name %}">
                {{ category.name }}
            </a>
        {% endfor %}
    </small>
    <p>{{ post.body | linebreaks }}</p>
{% endblock page_content %}

게시물 제목, 날짜, 카테고리를 표시하는 템플릿의 처음 몇 줄은 이전 템플릿과 동일한 논리입니다. 이번에는 게시물 본문을 렌더링할 때 linebreaks템플릿 필터를 사용합니다. 이 태그는 두 개의 연속된 줄 바꿈을 새 단락으로 등록하므로 본문이 하나의 긴 텍스트 블록으로 표시되지 않습니다.

참고: 아직 이 템플릿의 댓글 작업을 하고 있지 않습니다. 나중에 템플릿에서 소개하겠습니다. 하지만 지금은 context 사전에 항목을 추가했다고 해서 템플릿의 항목을 사용할 필요가 없다는 점을 알아두면 좋습니다.

템플릿이 준비되면 처음부터 작성 중인 블로그를 실제로 볼 수 있는 퍼즐 조각 하나가 누락됩니다. 바로 경로입니다. 다음 섹션에서는 뷰가 브라우저에서 블로그를 방문할 수 있는 경로를 만듭니다.

URL에 대한 경로 포함

블로그가 실제로 실행되는 것을 보려면 Django 프로젝트에서 블로그에 대한 경로를 연결해야 합니다 . 일반적으로 경로는 브라우저의 주소 표시줄에 입력하는 URL입니다.

Django에서는 패턴을 사용하여 경로를 만듭니다 . 모든 블로그 게시물에 대한 URL을 수동으로 생성하는 대신 기존 블로그 게시물에 액세스하는 규칙을 생성할 수 있습니다.

이렇게 하려면 blog/ 내부에 urls.py 파일을 만들고 세 가지 보기에 대한 URL을 추가 해야 합니다 .

In [ ]:
# blog/urlspy

from django.urls import path
from . import views

urlpatterns = [
    path("", views.blog_index, name="blog_index"),
    path("post/<int:pk>/", views.blog_detail, name="blog_detail"),
    path("category/<category>/", views.blog_category, name="blog_category"),
]

블로그별 URL이 설정되면 include()을 사용하여 personal_blog/urls.py에 프로젝트의 URL 구성를 추가해야 합니다.

In [ ]:
# personal_blog/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("blog.urls")),
]

이 설정을 사용하면 다음과 같은 경로 논리를 갖게 됩니다.

경로 패턴예시 URL설명
"" http://localhost:8000/ 블로그 색인
"post/int:pk/" http://localhost:8000/post/1 게시물의 블로그 세부정보 보기pk=1
"category//" http://localhost:8000/category/python 카테고리가 포함된 모든 게시물의 블로그 색인 보기python

http://localhost:8000/에 가서 방문하세요. 그런 다음 주변을 클릭하여 주소 표시줄의 다양한 URL과 로드하는 템플릿을 확인하세요.

지금까지 블로그는 다소 기본적으로 보입니다. 다음 섹션에서는 블로그에 스타일을 추가하여 블로그의 모양과 느낌을 향상시켜 보겠습니다.

블로그를 멋지게 만드세요

프로젝트 스타일 지정을 시작하기 전에 이전에 하위 템플릿에서 확장할 기본 템플릿을 만듭니다 . 이렇게 하면 HTML 템플릿을 한 곳에서 구성하고 다른 템플릿이 콘텐츠를 상속하도록 할 수 있습니다.

django-blog/ 폴더에 이름이 지정된 templates/ 디렉터리와 새 디렉터리에 이름이 지정된 base.html 파일을 만드는 것부터 시작하세요.

In [ ]:
!mkdir templates/
!touch templates/base.html

이전에 본 것처럼 각 Django 프로젝트는 별도의 로직을 처리하는 여러 앱으로 구성될 수 있으며 각 앱에는 애플리케이션과 관련된 HTML 템플릿을 저장하는 자체 templates/ 디렉터리가 포함되어 있습니다. 전체 프로젝트가 공유하는 템플릿의 경우 루트 디렉터리에 templates/ 디렉터리를 만드는 것이 좋습니다 .

base.html 내부에 다음 코드 줄을 추가합니다.

In [ ]:
<!-- templates/base.html -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>My Personal Blog</title>
</head>
<body>
<h1>My Personal Blog</h1>
<a href="{% url "blog_index" %}">Home</a>
<hr>
{% block page_title %}{% endblock page_title %}
{% block page_content %}{% endblock page_content %}
</body>
</html>

위의 코드를 사용하여 유효한 HTML 문서의 뼈대를 만듭니다. 또한 모든 하위 템플릿이 상속할 내 개인 블로그 제목과 헤드라인을 정의합니다 .

이전에 배웠듯이 하위 템플릿 시작 부분에 {% extends %} 태그를 추가해야 합니다. 아래에 강조 표시된 줄을 index.html에 추가하세요.

In [ ]:
<!-- blog/templates/blog/index.html -->

{% extends "base.html" %}

{% block posts %}
    <!-- ... -->
{% endblock posts %}

index.html에 {% extends "base.html" %}를 추가하면 템플릿이 base.html의 구조를 상속하게 됩니다.

detail.html를 계속해서 base.html의 하위 템플릿으로 만듭니다.

In [ ]:
<!--  blog/templates/blog/detail.html -->

{% extends "base.html" %}

{% block page_title %}
    <h2>{{ post.title }}</h2>
{% endblock page_title %}

{% block page_content %}
    <!-- ... -->
{% endblock page_content %}

템플릿 상속을 사용하면 템플릿에서 마크업을 반복할 필요가 없습니다. 대신 하위 템플릿을 확장합니다. 그런 다음 Django는 뷰에 서비스를 제공할 때 자동으로 병합합니다.

categories.html는 index.html에 이미 확장되어 있음을 기억하십시오. 템플릿은 상속을 통과하므로 이 템플릿에 추가 태그를 추가할 필요가 없습니다.

기본 템플릿이 작동하는 모습을 보려면 먼저 base.html이 존재하는 Django 예제 프로젝트를 알려줘야 합니다. 기본 설정은 각 앱의 templates/ 디렉터리를 등록하지만 루트 디렉터리 자체에는 등록하지 않습니다. personal_blog/settings.py에서 TEMPLATES를 업데이트 하십시요.

In [ ]:
# personal_blog/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",
            ]
        },
    }
]

# ...

settings.py의 상수를 BASE_DIR에서 이미 정의했으며 이는 프로젝트의 루트 디렉터리를 가리킵니다. 다음으로 templates/ 디렉터리를 가리키도록 /pathlib에서 슬래시 연산자(/)를 사용하여 경로를 결합 하고 "DIRS" 목록에 추가합니다.

브라우저로 이동하여 http://localhost:8000/ 방문하세요.

 

블로그를 방문하면 모든 페이지에 기본 헤드라인이 표시됩니다. 이는 상속이 작동함을 의미합니다. 그러나 스타일을 지정하지 않으면 블로그는 여전히 매우 기본적으로 보입니다.

이 튜토리얼에서는 CSS 스타일을 지정하는 대신 프로젝트에 외부 CSS 프레임워크를 추가하게 됩니다. 외부 CSS 프레임워크를 사용하면 웹 개발 중에 많은 작업을 줄일 수 있습니다. 즉, CSS를 모른다면 웹 개발에 관심이 있다면 기본을 배우는 것이 좋습니다 .

base.html를 다시 열고 Simple.css 프레임워크에 대한 링크를 추가하세요.

In [ ]:
<!-- templates/base.html -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>My Personal Blog</title>
    <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
</head>
<body>
<!-- ... -->
</body>
</html>

Python 가져오기 기술 과 유사하게 외부 CSS 라이브러리를 웹사이트로 가져올 수 있습니다. 8번째 줄에서는 외부 CSS 파일을 로드합니다. 이 외부 CSS 파일은 HTML 요소에 클래스를 추가할 필요 없이 스타일을 제공합니다.

http://localhost:8000/ 카테고리 페이지를 열고 방문하여 하위 템플릿이 스타일을 상속하는지 확인하세요.

 

기본 템플릿에 스타일시트를 로드하는 것만으로도 프로젝트에 외부 CSS 스타일을 추가할 수 있습니다. 모든 템플릿은 base.html를 확장하고, 스타일을 자동으로 상속합니다.

귀하의 블로그가 거의 완성되었습니다. 구현할 마지막 기능은 블로그 게시물에 댓글을 추가하는 기능입니다.

사용자 의견 양식 작업

블로그에서 댓글이 작동하도록 하려면 게시물 페이지에 양식을 추가해야 합니다. 그렇게 하기 전에 Django 양식을 작성해야 합니다. Django 형식은 모델과 매우 유사합니다. 양식은 클래스 속성이 양식 필드인 클래스로 구성됩니다. Django에는 필요한 양식을 빠르게 생성하는 데 사용할 수 있는 몇 가지 기본 제공 양식 필드가 있습니다.

blog/ 디렉터리에 이름이 지정된 새 파일 forms.py을 만듭니다.

In [ ]:
!touch blog/forms.py

forms.py 내부에 두 개의 필드 author와 body이 포함된 CommentForm 클래스를 추가합니다.

In [ ]:
# blog/forms.py

from django import forms

class CommentForm(forms.Form):
    author = forms.CharField(
        max_length=60,
        widget=forms.TextInput(
            attrs={"class": "form-control", "placeholder": "Your Name"}
        ),
    )
    body = forms.CharField(
        widget=forms.Textarea(
            attrs={"class": "form-control", "placeholder": "Leave a comment!"}
        )
    )

author 및 body 대해 CharField 클래스를 사용합니다. 페이지에서 양식 요소를 렌더링하는 방법을 제어하려면 widget 인수를 전달합니다.

author 필드에 forms.TextInput 위젯이 있습니다. 이는 Django가 이 필드를 템플릿의 HTML 텍스트 입력 요소로 로드하도록 지시합니다. 대신 body 필드는 forms.TextArea 위젯을 사용하므로 필드는 HTML 텍스트 영역 요소로 렌더링됩니다.

이러한 위젯은 일부 CSS 클래스를 지정할 수 있는 사전인 attrs 인수도 사용합니다. 이는 나중에 이 보기에 대한 템플릿 형식을 지정하는 데 도움이 됩니다. 또한 일부 자리 표시자 텍스트를 추가할 수도 있습니다.

댓글을 위한 Django 양식을 만든 후에는 양식이 요청을 통해 어떻게 이동하는지 살펴보세요.

  1. 사용자가 양식이 포함된 페이지를 방문하면 서버에 GET 요청을 보냅니다. 이 경우 양식에 입력된 데이터가 없으므로 양식을 렌더링하고 표시하기만 하면 됩니다.
  2. 사용자가 정보를 입력하고 제출 버튼을 클릭하면 양식과 함께 제출된 데이터가 포함된 POST 요청을 서버로 보냅니다. 이 시점에서 데이터가 처리되며 다음 두 가지 일이 발생할 수 있습니다.
     - 양식이 유효하며 사용자는 다음 페이지로 리디렉션됩니다.
     - 양식이 유효하지 않으며 빈 양식이 다시 나타납니다. 사용자는 1단계로 돌아가서 프로세스가 반복됩니다.

이 동작과 유사해야 하는 보기 기능은 blog_detail()보기입니다. 아래 강조표시된 코드로 blog_detail()를 업데이트하세요.

In [ ]:
# blog/views.py

from django.http import HttpResponseRedirect
from django.shortcuts import render
from blog.models import Post, Comment
from blog.forms import CommentForm

# ...

def blog_detail(request, pk):
    post = Post.objects.get(pk=pk)
    form = CommentForm()
    if request.method == "POST":
        form = CommentForm(request.POST)
        if form.is_valid():
            comment = Comment(
                author=form.cleaned_data["author"],
                body=form.cleaned_data["body"],
                post=post,
            )
            comment.save()
            return HttpResponseRedirect(request.path_info)

    comments = Comment.objects.filter(post=post)
    context = {
        "post": post,
        "comments": comments,
        "form": CommentForm(),
    }
    return render(request, "blog/detail.html", context)

3행에서는 HttpResponseRedirect import를 수행하는데, 이는 22행에서 요청을 리디렉션하는 데 도움이 됩니다. 잠시 후 22행에서 자세히 살펴보겠습니다. 먼저 blog_detail()의 본문을 통해 요청을 따릅니다.

요청 유형에 관계없이 6행에서 가져온 CommentForm()을 가져와 12행에서 인스턴스를 만듭니다. 이렇게 하면 보기에 항상 빈 양식이 있는지 확인할 수 있습니다.

그런 다음 13번째 줄에서 POST 요청을 받았는지 확인합니다. 그렇다면 14행의 POST 요청 데이터로 form를 업데이트합니다. 이는 사용자가 양식에 입력한 데이터입니다.

그런 다음 15행의 .is_valid()를 사용, 양식을 검증하여 사용자가 모든 필드를 올바르게 입력했는지 확인합니다.

참고: 양식이 유효하지 않은 경우, 사용자에게 오류를 출력할 수 있습니다. 이는 이 튜토리얼의 범위를 벗어나지만 Django 문서에서 양식 오류 메시지 렌더링 에 대한 자세한 내용을 읽을 수 있습니다.

양식이 유효하면 16~20행에서 Comment의 새 인스턴스를 만듭니다. 사전인 form.cleaned_data를 사용하여 양식의 데이터에 액세스할 수 있습니다. 사용자가 제출한 데이터를 데이터베이스 쿼리에 전달하기 전에 양식 데이터를 정리하는 것이 좋습니다. 이렇게 하면 모든 입력이 일관되고 안전한지 확인할 수 있습니다.

form.cleaned_data의 키는 양식 필드에 해당하므로, 17행의 form.cleaned_data["author"]를 사용하여 작성자에 액세스 하고 18행의 form.cleaned_data["body"] 사용 하여 주석 본문에 액세스할 수 있습니다.

데이터베이스에 Comment 개체를 제대로 생성하려면, 19번 줄에서 해당 개체를 기존 Post에 연결해야 합니다. 11번 줄에서 뷰의 기본 키를 사용하여 관련 게시물을 가져옵니다.

식에서 댓글을 작성한 후에는 21행의 .save() 사용하여 댓글을 저장하고 22행에 포함된 request.path_info인 URL로 사용자를 리디렉션합니다. 귀하의 경우 해당 URL은 블로그 게시물의 URL이 됩니다.

즉, POST 요청과 함께 blog_detail()의 유효한 양식을 전송하면 Django는 댓글을 저장한 후 blog_detail()를 호출한다는 의미입니다. 이번에는 Django가 GET요청으로 보기 기능을 호출하고 블로그 게시물은 빈 양식과 댓글 목록에 있는 댓글과 함께 로드됩니다.

이러한 GET요청이나 양식이 유효하지 않은 경우, blog_detail()의 나머지는 다음을 수행합니다.

  • 24행은 데이터베이스에서 게시물에 대한 기존 댓글을 쿼리합니다.
  • 25~29행에서는 게시물 데이터, 필터링된 댓글 및 양식을 포함하는 context을 생성합니다 .
  • 30행은 context와 같이 detail.html 템플릿을 렌더링합니다.

이제 또한 context에 댓글과 form 데이터도 포함되어 있으므로 detail.html 템플릿을 업데이트할 수 있습니다.

In [ ]:
1<!-- blog/templates/blog/detail.html -->

{% block page_title %}
    <h2>{{ post.title }}</h2>
{% endblock page_title %}

{% block page_content %}
    <small>
        <!-- ... -->
    </small>
    <p>{{ post.body | linebreaks }}</p>

    <h3>Leave a comment:</h3>
    <form method="post">
        {% csrf_token %}
        <div>
            {{ form.author }}
        </div>
        <div>
            {{ form.body }}
        </div>
        <button type="submit" class="btn btn-primary">Submit</button>
    </form>

    <h3>Comments:</h3>
    {% for comment in comments %}
        <p>
            On {{ comment.created_on.date }} <b>{{ comment.author }}</b> wrote:
        </p>
        <p>
            {{ comment.body | linebreaks }}
        </p>
    {% endfor %}
{% endblock page_content %}

게시물 아래에 양식이 표시됩니다. 양식의 action 속성을 정의하지 않으면 양식 작업이 현재 있는 페이지를 가리킵니다. 그런 다음 보안을 제공하고 양식의 본문 및 작성자 필드를 렌더링하는 csrf_token 을 추가한 다음 제출 버튼을 추가합니다.

마지막으로 해당 게시물의 모든 댓글을 반복합니다. 전체 타임스탬프를 표시하는 대신, 댓글 .created_on 속성의 날짜만 표시합니다 .

다음 사이트로 이동하여 http://localhost:8000/, 업데이트가 실제로 작동하는지 확인하세요.

 

이러한 변경 사항을 적용하면 블로그를 진정한 대화형으로 만들 수 있습니다. 이제 독자들은 각 게시물 바로 아래에서 피드백을 공유할 수 있습니다.

블로그 앱에서 Django의 양식 기능을 사용하여 주석을 구현하는 것이 이 튜토리얼의 마지막 단계입니다.

In [ ]:
출처 : https://realpython.com/build-a-blog-from-scratch-django