Djangoでのブログの始め方6

Python

今回は前回に引き続きDjangoでのブログの始め方について記事を書きたいと思います。今回はDjangoでブログの記事を書く準備について書いていきたいと思います。

全体の流れ

以下項目についてこれから説明していきます。

  • 記事用のアプリ作成
  • テンプレートディレクトリの作成
  • base.htmlの作成
  • 記事の基盤になるHTMLファイルの作成
  • ckeditorの設定
  • models.pyの編集
  • views.pyの編集
  • urls.pyの編集
  • admin.pyの編集
  • 記事の作成・表示

記事用のアプリ作成

まずは記事に関するアプリ(記事の動作を設定するディレクトリ)を作成します。以下のコマンドを実行してアプリを作成しましょう。※作成する時は(/venv/プロジェクト名/)にいる状態で作成しましょう。

Plaintext
python manage.py startapp [アプリ名]

また、各アプリを作成したらsetting.pyのINSTALLED_APPSにそのアプリを追加しましょう。以下のような感じで、一番下に作成したアプリ名を追加します。

Python
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    '[アプリ名]'
]

テンプレートディレクトリの作成

次にHTMLファイルを格納するテンプレートディレクトリを作成します。先ほど作成したブログアプリのディレクトリに移動した後にテンプレートディレクトリ(templates)を作成します。以下コマンドを実行するかエディタツール(VisualStudioCodeなど)で作成しましょう

Plaintext
cd [アプリ名]
mkdir templates

作成したらそのテンプレートディレクトリの中にそのアプリ名のディレクトリを作成します。何故そうなってるのかはよくわかりませんが、以下のような構造にします。

なので、先ほどテンプレートディレクトリを作成したみたいにアプリ名ディレクトリも作成します。以下コマンドを実行して作成していきましょう。(エディタツール使ってもOK)

Plaintext
cd templates
mkdir [アプリ名]

base.htmlの作成

次はbase.htmlの作成です。base.htmlとはそのサイトのどのページにも共通してあるものを記載するHTMLファイルです。例えば、ヘッダーやフッターはどのページでも共通して存在しますよね。そのような描写はbase.htmlで行います。そうすることで、使いまわしをすることができ、開発精度、速度を上げることができます。共通するものは使いまわす、プログラミングの基本ですね。base.htmlはプロジェクトの下にtemplatesフォルダを作成してその中に作成します。下がイメージになります。

実際のbase.htmlは以下です。

HTML
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}{% endblock %}</title>

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body {
            background-color: #f3f4f6;
        }

    </style>
    {% block extra_head %}{% endblock %}
</head>
<body>

<!-- ナビゲーションバー -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
    <div class="container">
        <a class="navbar-brand" href="{% url 'index' %}">ChikeTechBlog</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav ms-auto">
                <li class="nav-item"><a class="nav-link" href="{% url 'index' %}">ホーム</a></li>
                <li class="nav-item"><a class="nav-link" href="#">技術</a></li>
                <li class="nav-item"><a class="nav-link" href="#">趣味</a></li>
                <li class="nav-item"><a class="nav-link" href="{% url 'profile' %}">プロフィール</a></li>
            </ul>
        </div>
    </div>
</nav>

<!-- メインコンテンツ -->
<main class="container">
    {% block content %}{% endblock %}
</main>

<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="{% static 'blog/js/script.js' %}"></script>
{% block extra_js %}{% endblock %}

</body>
</html>

記事の基盤になるHTMLファイルの作成

次は記事の基盤になるHTMLファイルの作成です。ブログの記事を表示するにしても内容以外の部分は共通していると思うので、その共通部分だけのHTMLファイルを作成します。イメージ的には以下です。基盤のHTMLに各記事のHTMLを追加していく感じ。base.htmlの関係に似てますね。

実際の基盤になるHTMLは以下です。

HTML
{% extends "base.html" %}
{% load static %}
{% block title %}{{ post.title }} | ChikeTechBlog{% endblock %}
{% block extra_head %}
<head>
    <meta name="description" content="{{ post.content|striptags|truncatechars:120 }}">
    <meta name="keywords" content="Python, AWS, アニメ, ゲーム, 技術ブログ">
    <style>
        @media (min-width: 768px) {
            .toc-sticky {
                position: sticky;
                top: 1rem;
            }
            }
            .card-subtitle::before{
                content: "";
                display: inline-block;
                width: 20px;
                height: 5px;
                background-color: #800080;
                margin-right: 8px;
                vertical-align: middle;
            }
            .subtitle{
                border-bottom: 5px solid purple;
                padding-bottom: 5px;
                padding-top: 40px;
            }
            p {
                line-height: 2;
            }
            #toc-header{
                color: purple;
            }
            .list-group a{
                text-decoration: none;
                color: #6b46c1;
                font-weight: 500;
            }
            .list-group a:hover{
                color: purple;
                padding-left: 4px;
            }

            .code-container {
                position: relative;
                background-color:#24292e;
                padding: 1rem;
                border-radius: 10px;
                overflow-x: auto;
            }
            /* ① ヘッダーはコンテナ内で上に浮かせる */
            .code-header {
            position: absolute;
            top: 10px;
            left: 1rem;   /* container の padding と合うように */
            right: 1rem;
            display: flex;
            justify-content: space-between;
            align-items: center;
            }
            .code-lang{
                color: white;
                background-color: #6b46c1;
                {% comment %} font-size: 0.8rem; {% endcomment %}
                border-radius: 5px;
                padding: 1px 3px;
            }
            .copy-button {
                background-color: #3b82f6;
                color: white;
                border: none;
                padding: 1px 3px;
                border-radius: 5px;
                cursor: pointer;
            }
            .copy-button:hover {
                background-color: #2563eb;
            }
            .shiki{
                margin-top: 2.5rem; 
            }

    </style>
</head>
{% endblock %}
{% block content %}
            <h1 class="title">{{ post.title }}</h1>
            <p class="meta">投稿日:{{ post.created_at|date:"Y年m月d日" }}</p>
            <div>{{ post.content|safe }}</div>
            {% if post.previous_in_series or post.next_in_series %}
            <div class="row">
            {% if post.previous_in_series %}
            <div class="col-md-6 mb-3">
                <div class="card h-100">
                <div class="card-body">
                    <h5 class="card-title">← 前の記事</h5>
                    <p class="card-text">{{ post.previous_in_series.title }}</p>
                    <a href="{% url 'post:post_detail' post.previous_in_series.slug %}" class="stretched-link"></a>
                </div>
                </div>
            </div>
            {% endif %}
            
            {% if post.next_in_series %}
            <div class="col-md-6 mb-3">
                <div class="card h-100">
                <div class="card-body text-end">
                    <h5 class="card-title">次の記事 →</h5>
                    <p class="card-text">{{ post.next_in_series.title }}</p>
                    <a href="{% url 'post:post_detail' post.next_in_series.slug %}" class="stretched-link"></a>
                </div>
                </div>
            </div>
            {% endif %}
            </div>
            {% endif %}
{% endblock %}
{% block extra_js %}
<script>
    function copyCode(button) {
      const code = button.nextElementSibling.innerText;
      navigator.clipboard.writeText(code).then(() => {
        button.innerText = "Copied!";
        setTimeout(() => button.innerText = "Copy", 1500);
      });
    }
  </script>
{% endblock %}

ckeditorの設定

次はckeditorの設定です。ライブラリをインストールしていない人はインストールしましょう。

Plaintext
pip install django-ckeditor

次にsetting.pyの編集をします。以下のようなコードを追加しましょう。

Python
CKEDITOR_UPLOAD_PATH = "uploads/"
CKEDITOR_IMAGE_BACKEND = "pillow"
CKEDITOR_CONFIGS = {
    'default': {
        'toolbar': 'full',
        'height': 300,
        'width': '100%',
        'allowedContent': True
    },
}

また、setting.pyのINSTALLED_APPSにckeditor,ckeditor_uploaderを追加しましょう。以下のような感じで、一番下に作成したアプリ名を追加します。

Python
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    '[アプリ名]',
    '[アプリ名]',
    'ckeditor',
    'ckeditor_uploader',
]

models.pyの編集

次はmodels.pyの編集です。models.pyでは記事に関するテーブルを作成します。以下コードで作成されているカラムの説明は以下です。

  • title:記事のタイトル
  • slug:記事のURL
  • content:記事の本文(記事のHTML)
  • created_at:いつその記事が作成されたか
  • views:その記事が何回閲覧されたか
  • serise_name:その記事をシリーズとして作成する場合に使用
  • order_in_series:シリーズの何番目の記事か

また、ほかにもいくつか関数を記載しておりそれぞれの説明は以下です。

  • get_absolute_url(self):post_detailがリクエストされたときに記事それぞれのURL(slug)を返す
  • next_in_series(self):その記事がシリーズだった場合、その記事の後の記事に飛ぶ
  • previous_in_series(self):その記事がシリーズだった場合、その記事の前の記事に飛ぶ
Python
from django.db import models
from django.urls import reverse
from ckeditor_uploader.fields import RichTextUploadingField


class Post(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True, blank=True)
    content = RichTextUploadingField(config_name='default')
    created_at = models.DateTimeField(auto_now_add=True)
    views = models.PositiveIntegerField(default=0)
    
    # 連載としての順番を定義(任意)
    series_name = models.CharField(max_length=100, blank=True, null=True)
    order_in_series = models.PositiveIntegerField(blank=True, null=True)


    def get_absolute_url(self):
        return reverse('post:post_detail', kwargs={'slug': self.slug})

    def next_in_series(self):
        if self.series_name and self.order_in_series is not None:
            return Post.objects.filter(
                series_name=self.series_name,
                order_in_series=self.order_in_series + 1
            ).first()

    def previous_in_series(self):
        if self.series_name and self.order_in_series is not None:
            return Post.objects.filter(
                series_name=self.series_name,
                order_in_series=self.order_in_series - 1
            ).first()
    
    def __str__(self):
        return self.title

views.pyの編集

次はviews.pyの編集です。今回のviews.pyはシンプルです。この関数の機能としては、リクエストした記事がない場合は404エラーを表示する。閲覧されたら閲覧数を数える。記事用のHTMLを返す。だけですね。特に説明不要かと思います。

Python
from django.shortcuts import render
from django.shortcuts import get_object_or_404, render
from .models import Post

def post_detail(request, slug):
    post = get_object_or_404(Post, slug=slug)

    # 閲覧数を増やす 
    post.views += 1
    post.save()

    return render(request, 'post/post_detail.html', {'post': post})

urls.pyの編集

次はurls.pyの編集です。urls.pyは複数のファイルを編集する必要があるので注意が必要です。まずは、今回作成したアプリの編集です。以下がコードになります。

今回のアプリのurls.pyを編集

Python
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from . import views

#今回作成したアプリ名をapp_nameに代入。
app_name = 'post'

#urlpatternsは、このURLが来たらこの処理をするという紐づけをする部分。
urlpatterns = [
    #記事のURLがリクエストされたら、views.pyのpost_detail関数を実行する
    path('<slug:slug>/', views.post_detail, name='post_detail'),
    #ckeditorを使用する際に必要なコード(意味はよくわかりません。。)
    path('ckeditor/', include('ckeditor_uploader.urls')),
    #media(サイト内でuploadするときに使う)を使う際に必要なコード
]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

以下は今回作成したアプリ以外(今回はblogアプリ)のurls.py。全アプリのurls.pyにckeditorに関するコードを記載する必要があります。

Python
from django.urls import path, include
from .views import top

app_name = 'blog'

urlpatterns = [
    path('', top, name='top'),
    #ckeditorを使用する際に必要なコード(意味はよくわかりません。。)
    path('ckeditor/', include('ckeditor_uploader.urls'))
]

以下はプロジェクトのurls.py。今回作成したアプリを追記。

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

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

また、mediaを使う場合はsetting.pyにMEDIA_URLを追加。

Python
MEDIA_URL = '/media/'

また、base.htmlを使用する場合(templatesフォルダをプロジェクトディレクトリ直下に作る場合)はsetting.pyのtemplatesを以下のように変更。

Python
import os

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        #↓(DIRS)を変更
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

admin.pyの編集

次はadmin.pyの編集です。記事を作成するのは管理者画面で行うため、admin.pyを以下のように編集する必要があります。admin.pyに管理者画面で編集したいDBのテーブル(models.pyのクラス)は以下のように記載する必要があります。

Python
from django.contrib import admin
from .models import Post

admin.site.register(Post)

記事の作成・表示

最後に記事の作成と表示を行っていきましょう。まずは、管理者画面にアクセスして記事を作成します。そしてPostsテーブルにある追加ボタンをクリック。

すると以下のような画面が表示されるので、記事に関する情報を記載していきましょう。記載し終わったら保存をクリック!

これで記事が作成されているはずなので、先ほどの画面で設定したslugの値をURLの末尾に追加して検索しましょう。(例:http://127.0.0.1:8000/first-post/)すると先ほど作成された記事が表示されるはずです!自分のは味気ないですが。。w

おわりに

皆さんここまで見ていただきありがとうございます。Djangoでのブログの始め方は今回を持ちましていったん終了とさせていただきます。本シリーズをここまで見ていただありがとうございました。これからもDjangoに関することだったり、ほかのプログラミングに関する知識、インフラに関する知識も記事にできたらと思います。(自分の趣味に関しても書いてみたり。。?)これからも本ブログをよろしくお願いします!

コメント

タイトルとURLをコピーしました