11  Build A Blog From Scratch Django

12 Django로 나만의 블로그 만들기

Real Python의 단계별 지침에 따라 Django로 나만의 블로그를 만들어 보세요.

12.1 설정

아래 설명된 단계에 따라 제공된 예제 프로젝트를 로컬 컴퓨터에서 실행할 수 있습니다.

새 가상 환경을 만듭니다.

$ python3 -m venv venv

가상 환경을 활성화합니다.

$ source venv/bin/activate

아직 설치하지 않은 경우 이 프로젝트의 종속성을 설치합니다.

(venv) $ python -m pip install -r requirements.txt

프로젝트에 대한 마이그레이션을 만들고 적용하여 로컬 데이터베이스를 빌드합니다.

(venv) $ python manage.py makemigrations
(venv) $ python manage.py migrate

Django 개발 서버를 실행합니다.

(venv) $ python manage.py runserver

블로그가 작동하는 것을 보려면 http://localhost:8000/으로 이동하십시오.

12.2 Django 관리자 사이트 사용

새 게시물을 만들려면 슈퍼유저를 만들어야 합니다.

(venv) $ python manage.py createsuperuser
사용자 이름 (비워두면 'root' 사용): admin
이메일 주소: admin@example.com
비밀번호: RealPyth0n
비밀번호 (다시): RealPyth0n
슈퍼유저가 성공적으로 생성되었습니다.

createsuperuser 관리 명령을 실행하면 사용자 이름을 선택하고 이메일 주소를 제공하고 암호를 설정하라는 메시지가 표시됩니다. 이 필드에 자신의 데이터를 사용하고 기억해 두십시오.

http://localhost:8000/admin으로 이동하여 방금 슈퍼유저를 만드는 데 사용한 자격 증명으로 로그인합니다.

12.3 파일: django-blog/blog/__init__.py

12.4 파일: django-blog/blog/admin.py

from blog.models import Category, Comment, Post
from django.contrib import admin


class CategoryAdmin(admin.ModelAdmin):
    pass


class PostAdmin(admin.ModelAdmin):
    pass


class CommentAdmin(admin.ModelAdmin):
    pass


admin.site.register(Category, CategoryAdmin)
admin.site.register(Post, PostAdmin)
admin.site.register(Comment, CommentAdmin)

12.5 파일: django-blog/blog/apps.py

from django.apps import AppConfig


class BlogConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "blog"

12.6 파일: django-blog/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!"}
        )
    )

12.7 파일: django-blog/blog/migrations/0001_initial.py

# Generated by Django 4.2.4 on 2023-08-29 12:43

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Category',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=30)),
            ],
        ),
        migrations.CreateModel(
            name='Post',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=255)),
                ('body', models.TextField()),
                ('created_on', models.DateTimeField(auto_now_add=True)),
                ('last_modified', models.DateTimeField(auto_now=True)),
                ('categories', models.ManyToManyField(related_name='posts', to='blog.category')),
            ],
        ),
        migrations.CreateModel(
            name='Comment',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('author', models.CharField(max_length=60)),
                ('body', models.TextField()),
                ('created_on', models.DateTimeField(auto_now_add=True)),
                ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.post')),
            ],
        ),
    ]

12.8 파일: django-blog/blog/migrations/__init__.py

12.9 파일: django-blog/blog/models.py

from django.db import models


class Category(models.Model):
    name = models.CharField(max_length=30)

    class Meta:
        verbose_name_plural = "categories"

    def __str__(self):
        return self.name


class Post(models.Model):
    title = models.CharField(max_length=255)
    body = models.TextField()
    created_on = models.DateTimeField(auto_now_add=True)
    last_modified = models.DateTimeField(auto_now=True)
    categories = models.ManyToManyField("Category", related_name="posts")

    def __str__(self):
        return self.title


class Comment(models.Model):
    author = models.CharField(max_length=60)
    body = models.TextField()
    created_on = models.DateTimeField(auto_now_add=True)
    post = models.ForeignKey("Post", on_delete=models.CASCADE)

    def __str__(self):
        return f"{self.author} on '{self.post}'"

12.10 파일: django-blog/blog/urls.py

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"),
]

12.11 파일: django-blog/blog/views.py

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


def blog_index(request):
    posts = Post.objects.all().order_by("-created_on")
    context = {"posts": posts}
    return render(request, "blog/index.html", context)


def blog_category(request, category):
    posts = Post.objects.filter(categories__name__contains=category).order_by(
        "-created_on"
    )
    context = {"category": category, "posts": posts}
    return render(request, "blog/category.html", context)


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": form}

    return render(request, "blog/detail.html", context)

12.12 파일: django-blog/manage.py

관리 작업을 위한 Django의 명령줄 유틸리티입니다.

#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""

import os
import sys


def main():
    """Run administrative tasks."""
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "personal_blog.settings")
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == "__main__":
    main()

12.13 파일: django-blog/personal_blog/__init__.py

12.14 파일: django-blog/personal_blog/asgi.py

personal_blog 프로젝트에 대한 ASGI 구성입니다.

이는 application이라는 모듈 수준 변수로 호출 가능한 ASGI를 노출합니다.

이 파일에 대한 자세한 내용은 다음을 참조하세요. https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/

"""
ASGI config for personal_blog project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "personal_blog.settings")

application = get_asgi_application()

12.15 파일: django-blog/personal_blog/settings.py

personal_blog 프로젝트에 대한 Django 설정입니다.

Django 4.2.4를 사용하여 ’django-admin startproject’로 생성되었습니다.

이 파일에 대한 자세한 내용은 다음을 참조하세요. https://docs.djangoproject.com/en/4.2/topics/settings/

설정 및 해당 값의 전체 목록은 다음을 참조하세요. https://docs.djangoproject.com/en/4.2/ref/settings/

"""
Django settings for personal_blog project.

Generated by 'django-admin startproject' using Django 4.2.4.

For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = (
    "django-insecure-jl==lja$kjxwf+!=yt@(2oja*6ob8jg#!n*_7#j_a+zrrk2-kr"
)

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    "blog.apps.BlogConfig",
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

ROOT_URLCONF = "personal_blog.urls"

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",
            ],
        },
    },
]

WSGI_APPLICATION = "personal_blog.wsgi.application"


# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
    }
}


# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
    },
]


# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/

LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/

STATIC_URL = "static/"

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

12.16 파일: django-blog/personal_blog/urls.py

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

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

12.17 파일: django-blog/personal_blog/wsgi.py

personal_blog 프로젝트에 대한 WSGI 구성입니다.

application이라는 모듈 수준 변수로 호출 가능한 WSGI를 노출합니다.

이 파일에 대한 자세한 내용은 다음을 참조하세요. https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/

"""
WSGI config for personal_blog project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "personal_blog.settings")

application = get_wsgi_application()