【Django】Blog(ブログ)サイトの「下書き保存」を実現させる2種類の方法


投稿日 2019年11月13日 >> 更新日 2023年3月2日

今回はBlogサイトなどで必須とも言える、「下書き保存」機能について説明していきたいと思います。

「下書き保存」というと、少し語弊があるかもしれませんが実際は保存する記事を公開/非公開とすることで、作成途中の記事を自由に保存したり再開したりすることができます。

Djangoフレームワークで1からアプリを作り込むとついつい忘れがちになってしまう機能と言えるかもしれません。

数時間かけて書いている最中の記事を不慮の事故により紛失しないためにも、公開/非公開機能を取り入れて未然に防いでいきましょう。

実装方法について色々なやり方があると思いますが、ここでは2種類の方法を使って実装したいと思います。

実行環境&使用ライブラリ

実行環境
Windows Subsystem for Linux
Python 3.6.8
pip 9.0.1
使用ライブラリ ライセンス
Django==2.2.6 BSD

Blogアプリを作成しモデルを作る

アプリ作成については簡単な説明になりますが、「プロジェクト」→「アプリ」→「設定ファイル」まで終わったらモデルを定義します。


$ django-admin startproject project

$ cd project

/project$ python3 manage.py startapp blog

# 設定ファイル内編集
/project$ vi project/settings.py

2種類の「公開/非公開」機能を試すので、アプリディレクトリ内の「models.py」に「Blog_1モデル」と「Blog_2モデル」を以下のようにそれぞれ定義します。

# project/blog/models.py

from django.db import models


""" サンプル1 """
class Blog_1(models.Model):
    title = models.CharField('タイトル', max_length=10)
    text = models.TextField('テキスト')
    created_at = models.DateField('作成日', auto_now_add=True)
    updated_at = models.DateField('更新日', auto_now=True)
    is_publick = models.BooleanField('公開する', default=False, help_text='公開する場合はチェックを入れてください')

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = 'ブログ_1'
        verbose_name_plural = 'ブログ_1'


""" サンプル2 """
class Blog_2(models.Model):
    title = models.CharField('タイトル', max_length=10)
    text = models.TextField('テキスト')
    created_at = models.DateField('作成日', auto_now_add=True)
    updated_at = models.DateField('更新日', auto_now=True)
    is_publick = models.IntegerField('選択', choices=((1, '公開'), (2, '非公開')))

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = 'ブログ_2'
        verbose_name_plural = 'ブログ_2'

双方の違いは、「is_publick」フィールドだけです。

「models.BooleanField」ではTrue or Falseの真偽値による公開/非公開で、「models.IntegerField」では引数に「choices」とすることで指定させることができます。

それでは「admin.py」にデフォルト設定してから、ターミナルでモデルを反映させadminサイトインターフェイスにアクセスしましょう。

# project/blog/admin.py

from django.contrib import admin
from .models import Blog_1, Blog_2


admin.site.register(Blog_1)
admin.site.register(Blog_2)

マイグレーションしてから管理者として登録


$ python3 manage.py makemigrations

$ python3 manage.py migrate

$ python3 manage.py createsuperuser

$ python3 manage.py runserver
127.0.0.1:8000

「Blog_1モデル」の追加・編集ページ内です。

チェックを入れるか入れないかによってオブジェクトを作成できます。

「Blog_2モデル」の追加・編集ページ内です。

セレクトボックスによって選択することができます。(後ほどラジオボタンに変更します。)

ではサンプル1の「Blog_1モデル」からテンプレートに表示するまでを実装していきたいと思います。

Blog_1での実装

それでは公開用と非公開用の記事をそれぞれ保存しておきましょう。

そして「admin.py」を開き、チェンジリストページ内で「title」と「is_publick」フィールドを表示させるためにModelAdminクラスを定義します。

# project/blog/admin.py

from django.contrib import admin
from .models import Blog_1, Blog_2


""" 追加 """
class Blog_1Admin(admin.ModelAdmin):
    list_display = ['title', 'is_publick']


admin.site.register(Blog_1, Blog_1Admin)    # 追加
admin.site.register(Blog_2)

ModelAdminクラスはチェンジリストや追加・編集ページに機能を追加できるクラスで、さまざまなカスタムをすることができます。

チェンジリストのカスタムについては別記事の方で説明しているので、興味がありましたらこちら「【Django】ModelAdminを使ってadminサイトのチェンジリストをカスタマイズする」をご参照ください。

リロードするとこのようになります。

次にビューへ渡すために「project/urls.py」→「blog/urls.py」→「blog/views.py」→「templates/blog/index.html」→「templates/blog/detail.html」という順番で各ファイルを編集・作成していきます。

# project/project/urls.py

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


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

from django.urls import path
from . import views


app_name = 'blog'
urlpatterns = [
        path('', views.index, name='index'),
        path('detail/<int:blog_id>/', views.detail, name='detail'),
]
# project/blog/views.py

from django.shortcuts import render, get_object_or_404
from .models import Blog_1


""" 一覧表示 """
def index(request):
    blog = Blog_1.objects.filter(is_publick=True).order_by('-id')
    return render(request, 'blog/index.html', {'blog': blog })


""" 詳細表示 """
def detail(request, blog_id):
    blog = Blog_1.objects.filter(is_publick=True)
    blog = get_object_or_404(blog, id=blog_id)
    return render(request, 'blog/detail.html', {'blog': blog })

クエリメソッドの「filter(is_publick=True)」とすることで、Trueに設定されている記事を表示するように指定しています。

ではアプリディレクトリに「templates」ディレクトリ→「blog」ディレクトリを作りHTMLファイルを作成します。

<!-- project/blog/templates/blog/index.html -->

<h1>一覧</h1>

{% for blog in blog %}
    <ul>
        <li>
            {{ blog.created_at }}
            {{ blog.title }}
            <a href="{% url 'blog:detail' blog_id=blog.id %}">詳細</a>
        </li>
    </ul>
{% endfor %}
<!-- project/blog/templates/blog/detail.html -->

<p>{{ blog.updated_at }}</p>
{{ blog.text }}<hr />
<br>

<a href="{% url 'blog:index' %}">トップページに戻る</a>

システムを一旦停止し、再起動してから表示してみましょう。

「公開用」の記事だけが表示されていれば成功です。

詳細表示を開いてみて、URLから非公開の記事にアクセスされないかテストしてみます。

404エラーが表示されれば成功です。

チェンジリストのアクションバーで簡易編集

このままadminサイトへアクセスし編集ページで非公開の記事を公開したいところですが、チェンジリストから簡易的に編集できるようアクションバーに機能を追加したいと思います。

アクションバーとは下図のセレクトボックスのことで、このアクションバーの機能を拡張していきます。

そのためには、「admin.py」にてModelAdmin内にカスタムメソッドを定義します。

# project/blog/admin.py

from django.contrib import admin
from .models import Blog_1, Blog_2


class Blog_1Admin(admin.ModelAdmin):
    list_display = ['title', 'is_publick']
    actions = ['make_published']     # カスタムメソッドを渡す

    """ カスタムメソッド """
    def make_published(self, request, queryset):
        queryset.update(is_publick=True)
    make_published.short_description = '公開する'


admin.site.register(Blog_1, Blog_1Admin)
admin.site.register(Blog_2)

アクションバーの詳しい説明についてはDjango公式ドキュメントをご参照ください。

アクションバー内に「公開する」が追加されていると思うので、非公開記事のチェックボックスにチェックを入れ実行します。

これでサンプル1の「Blog_1モデル」の実装は一通り行えました。

Blog_2での実装

サンプル2の「Blog_2モデル」の実装ですが、これまで行ってきた「Blog_1モデル」の実装とほとんど変わりはありません。

異なる部分は、追加・編集ページに表示されているセレクトボックスからラジオボタンに変更することと、真偽値として渡されていた値を整数の「1」に変えるだけです。

「Blog_2モデル」の「is_publick」フィールドでは「models.IntegerField」としていて、引数に「choices=((1, '公開'), (2, '非公開'))」という1か2の整数を指定するようにしているので、公開であれば1の値にする必要があります。

実際にコードを見た方が早いので順番に追加・変更していきます。

# project/blog/admin.py

from django.contrib import admin
from .models import Blog_1, Blog_2


class Blog_1Admin(admin.ModelAdmin):
    list_display = ['title', 'is_publick']
    actions = ['make_published', 'make_non_published']

    def make_published(self, request, queryset):
        queryset.update(is_publick=True)
    make_published.short_description = '公開する'


""" サンプル2 """
class Blog_2Admin(admin.ModelAdmin):
    list_display = ['title', 'is_publick']
    """ ラジオボタン機能 """
    # radio_fields = {'is_publick': admin.VERTICAL} # 縦に並ぶ
    radio_fields = {"is_publick": admin.HORIZONTAL} # 横に並ぶ
    actions = ['make_published']

    """ カスタムメソッド """
    def make_published(self, request, queryset):
        queryset.update(is_publick=1)    # Trueから1に変更
    make_published.short_description = '公開する'


admin.site.register(Blog_1, Blog_1Admin)
admin.site.register(Blog_2, Blog_2Admin)  # 追記

「Blog_2モデル」の追加・編集ページを確認してみます。

記事をそれぞれ保存し、チェンジリストページも確認してみましょう。

「views.py」でもモデルと値を変更するだけで、テンプレートへ渡すことができます。

# project/blog/views.py

from django.shortcuts import render, get_object_or_404
from .models import Blog_1, Blog_2


def index(request):
    blog = Blog_2.objects.filter(is_publick=1).order_by('-id')
    return render(request, 'blog/index.html', {'blog': blog })


def detail(request, blog_id):
    blog = Blog_2.objects.filter(is_publick=1)
    blog = get_object_or_404(blog, id=blog_id)
    return render(request, 'blog/detail.html', {'blog': blog })

番外編

Categoryモデルと紐づいている場合

Blogサイトなので、カテゴリーやタグといったモデルを作成していても不思議ではありません。

カテゴリーについてもこれまでと同じようにDjangoのクエリメソッドを使って制御します。

# project/blog/views.py

from django.shortcuts import render, get_object_or_404
from .models import Category


""" カテゴリーがあった場合 """
def category(request, category):
    category = Category.objects.get(title=category)
    """ 公開記事のカテゴリで絞り込む"""
    blog = Blog.objects.filter(category=category, is_publick=True).order_by('title')
    return render(request, 'blog/index.html', {'blog': blog, 'category': category})

今回はブロガーによるブロガーのための機能である公開/非公開にする実装を行ってきました。

Djangoアプリをより便利にするために色々な機能を発見して育てていきましょう。

それでは以上となります。

最後までご覧いただきありがとうございました。