DjangoとNuxt3で作るブログ。ソースコード公開。

さいきんNuxt3にはまっていて、今度はDjangoとNuxt3でブログを作ってみました。
フレームワークは試験的にVuetifyを使っています。
Django REST FrameworkでブログのAPIを配信して、Nuxt3で表示させる、という構成です。
ソースコードは全文githubに公開中です。

ソースコード

バックエンド
https://github.com/qlitre/django-nuxt3-blog-backend
フロントエンド
https://github.com/qlitre/django-nuxt3-blog-frontend

動作環境

バックエンドはpythonanywhere、フロントエンドはNetlifyにデプロイしています。
動いているサイト

Nuxt3のバージョンはv3.0.0-rc.3 です。

主な機能

  • ページネーション
  • タグもしくはカテゴリでの絞り込み
  • キーワード検索
  • マークダウン記事投稿


ブログの始め方

適当なディレクトリを作成し、gitcloneしてください

git clone https://github.com/qlitre/django-nuxt3-blog-backend
git clone https://github.com/qlitre/django-nuxt3-blog-frontend


バックエンドの編集

backendディレクトリに仮想環境を作成しライブラリをインストールします

cd django-nuxt3-blog-backend
python -m venv myvenv
myvenv\scripts\activate
pip install -r requirements.txt


local_settings.pyの作成

まずprojectディレクトリにlocal_settings.pyを作成し以下のようにします。

DEBUG = True
SECRET_KEY = 'your django secret key'
ALLOWED_HOSTS = ['127.0.0.1']
HOST_URL = 'http://127.0.0.1:8000'


secret keyはコマンドラインからpython manage.py shellを実行し、対話モードで取得できます。

python manage.py shell
>>> from django.core.management.utils import get_random_secret_key
>>> get_random_secret_key()
'5bhqfdc^v#v^#lrqb(l#2lrxslmzba8%=ya2ljlvt*a4w1^zel'


作成したら抜けましょう

>>> exit()


スーパーユーザーの作成

次にモデルをmigrateしてsuperuserを作成します。

python manage.py migrate
python manage.py createsuperuser


開発環境用のAPIキーの作成

このブログではAPIの取得に関してキー認証を行っています。
詳細はDjango REST FrameworkでAPIキー認証する方法に書きました。
再び対話モードでAPIキーを作成します。

python manage.py shell
>>> from rest_framework_api_key.models import APIKey
>>> api_key, key = APIKey.objects.create_key(name="api-key-dev")
>>> key
'KjBF0Ep9.Yuab4bJQQjK6999jwKoAlqoMirX8zMPH'
>>> exit()


発行されたキーはどこかにメモしておいてください

初期データの作成

ここまで来たらrun serverして、初期データを投入してみましょう。
http://127.0.0.1:8000/adminから管理サイトに入ります。

こんな感じの構成になっています。


適当に初期データを追加します。

こちらはAbout



カテゴリ


タグ



カテゴリとタグにもスラッグフィールドをあてています。
フロントで表示させた際にtag/1/page/1みたいなURLじゃなくて、tag/python/page/1みたいなURLの方がかっこいいんじゃないかという、それだけの理由です。

ポスト



サムネイル画像は1600 x 900のサイズにリサイズすると良い感じに表示されると思います。

記事本文はマークダウンで書きます。




バックエンドはとりあえずここまでです。

フロントエンドの編集

次にフロントエンドの編集です。
フロントエンドディレクトリに移り、npm installします。

cd ..
cd django-nuxt3-blog-frontend
npm install


次にフロントエンド直下に.envファイルを作り、先ほど作成したAPIキーをAPI_KEYに記載します。

BASE_URL=http://127.0.0.1:8000
API_KEY=KjBF0Ep9.Yuab4bJQQjK6999jwKoAlqoMirX8zMPH


BASE_URLはDjangoサイトのデプロイ先のURLを記載します。
https://www.your-django-site.comのように最後に/をつけないのが注意です。
API_KEYは本番用のキーです。これもDjangoサイトを本番環境にアップさせ、キーを発行したら記載します。

ブログの起動

バックエンドをpython manage.py runserverしたのちに、フロントエンドをnpm run devすると、ブログが立ち上がります。


本番環境で使用する場合


バックエンドについて、すこしセキュリティ対策をしています。

  • 管理画面ログインURLのパスを変える
  • ADMINサイトにアクセスできるIPを制限する


なので、本番環境では、以下の環境変数を設定しています。

HOST_URL=https://your-django-site.com
ADMIN_PATH=本番環境用に文字列を設定
ALLOWED_ADMIN=管理サイトにアクセスを許可するグローバルIPを入力。複数ある場合はカンマ区切りで入れる
SECRET_KEY=YourDjangoSecretKey


私の場合はADMINのパスはランダム文字列をuuidで生成しました。
uuidは以下のプログラムを実行すると、入手できます。

import uuid

print(str(uuid.uuid4()))
>>>
cc3de76e-df7c-46d6-a62a-861073cfc6e7


グローバルIPはこの辺のサイトにアクセスすると教えてくれます。
フロントエンド側については本番では環境変数のBASE_URLにデプロイされたpythonサイトのURLを記載、
API_KEYもDjangoの本番環境で発行されたものを環境変数にします。

つくってみた感想

このブログはmicroCMS + Nuxt2での開発なのですが、バックエンドから自作するとやはり楽しいです。
特に検索とかページネーション周りの処理なんかは自分でAPIが取得しやすいようにバックエンドを加工できるから良いですね。
このあたりをmicroCMS + Nuxtでやる場合、ちょっと手間がかかります。

それとNuxt3は優れた開発体験、というのでしょうか、コードを書いていて楽しいという感覚がします。
Typescript上のエディタ上のバグをちまちまつぶしていくのが楽しいというか…
例えばバックエンドのモデルに合わせて、TypescriptでTypeを記述していくのとか、地味な作業なんですけど、妙に楽しかったです。

# backend/blog/models.py
class Category(models.Model):
    """カテゴリ"""
    name = models.CharField('カテゴリ名', max_length=32)
    slug = models.SlugField('スラッグ', max_length=32)

    def __str__(self):
        return self.name


class Tag(models.Model):
    """タグ"""
    name = models.CharField('タグ名', max_length=32)
    slug = models.SlugField('スラッグ', max_length=32)

    def __str__(self):
        return self.name


class Post(models.Model):
    """記事"""
    title = models.CharField('タイトル', max_length=40)
    slug = models.SlugField('スラッグ', max_length=40)
    category = models.ForeignKey(Category, on_delete=models.PROTECT, verbose_name='カテゴリ')
    tag = models.ManyToManyField(Tag, verbose_name='タグ', blank=True)
    thumbnail = models.ImageField('サムネイル', blank=True, null=True)
    description = models.TextField('紹介文')
    main_text = MDTextField()
    created_at = models.DateTimeField('作成日', default=timezone.now)
    is_public = models.BooleanField('公開可能フラグ', default=True)

    class Meta:
        ordering = ('-created_at',)

    def __str__(self):
        return self.title


class About(models.Model):
    profile_image = models.ImageField('プロフィール画像')
    body = MDTextField()


/* frontend/client/types/blog.ts */
export type Category = {
    id: string
    name: string
    slug: string
    post_count: number
}

export type Tag = {
    id: string
    name: string
    slug: string
    post_count: number
}

export type CategoryList = Array<Category>

export type TagList = Array<Tag>

export type Post = {
    id: string
    slug: string
    category: Category
    tag: Array<Tag>
    thumbnail: string
    title: string
    description: string
    created_at: string
    main_text: string
    is_public: boolean
}

export type PostList = Array<Post>

export type PostResponce = {
    next: string
    previous: string
    total_pages: number
    current_page: number
    results: PostList
    page_size: number
}

export type About = {
    profile_image: string
    body: string
}
TOPページ