さいきん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
まず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の取得に関してキー認証を行っています。
詳細は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すると、ブログが立ち上がります。
バックエンドについて、すこしセキュリティ対策をしています。
なので、本番環境では、以下の環境変数を設定しています。
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
}