Django、ウェブサイトの設定をモデルで管理

Python Django

概要

Djangoで、ウェブサイトの設定をモデルで管理していきます。

例えばウェブサイトの<title>やトップページの<meta name="description>"、ウェブサイトの管理者名やメールアドレスをエンドユーザーが簡単に設定できるようにしたいとしましょう。

Djangoプロジェクト全体の設定やウェブサイトのグローバルな設定は、settings.pyで管理するのが一般的なので、次のように書けます。

BASE_TITLE = 'naritoブログ'
BASE_DESCRIPTION = 'PythonやDjango等についてのメモブログです。'

SITE_EMAIL = 'toritoritorina@gmail.com'
SITE_AUTHOR = 'narito'

テンプレートに直接書くよりも、このように書いておくほうが上書きがしやすいでしょう。しかし、途中からこれらの内容を変えようと思うとDjangoプロジェクトの再起動が必要で、もっと手軽な方法が好まれることもあります。

理想としては、admin管理サイト等で簡単に設定できれば良さそうです。そのためには、ウェブサイト全体の設定を表すモデルをどうやって作るかを考えることになります。

sitesフレームワークを使う

Djangoに付属しているsitesフレームワークを導入すると、1つのウェブサイトに1つのSiteデータが割り当てられます。これとOneToOneで紐づくSiteDetailのようなモデルを作ることで、そのウェブサイト全体の設定を表すモデルが作れます。

sitesフレームワークを使うための準備が少し必要です。

settings.pyに追記します。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites',  # これ
    'app.apps.AppConfig',  # マイアプリケーション
]

# サイトマップフレームワークで使う変数です。
SITE_ID = 1

そして、モデルを定義します。フィールドは好きに追加したり減らしたりしてください。

class SiteDetail(models.Model):
    """Siteと1対1で紐づくサイト詳細情報"""
    site = models.OneToOneField(Site, verbose_name='Site', on_delete=models.PROTECT)
    title = models.CharField('タイトル', max_length=255, default='Webサイトのタイトル')
    description = models.TextField('サイトの説明', max_length=255, default='Webサイトの説明')
    keywords = models.CharField('サイトのキーワード', max_length=255, default='Webサイトのキーワード')
    author = models.CharField('管理者', max_length=255, default='サンプルの管理者')
    email = models.EmailField('管理者アドレス', max_length=255, default='your_mail@gmail.com')
    github = models.CharField('Githubアカウント', max_length=255, blank=True)
    twitter = models.CharField('Twitterアカウント', max_length=255, blank=True)
    facebook = models.CharField('FaceBookアカウント', max_length=255, blank=True)
    google_ad_html = models.TextField('アドセンスHTML', blank=True)
    google_analytics_html = models.TextField('アナリティクスHTML', blank=True)

    def __str__(self):
        return self.author

これでSiteDetailを作ればそれで話は済むのですが、幾つか手間をかければもう少し扱いやすくなります。

初期データとして投入する

sitesフレームワークで作成されるSiteデータは、migrate後に自動で作られます。なので、SiteDetailもmigrate後に一緒に作ると便利です。これをしておくと必ずSiteDetailデータも作られるので、SiteDetailがまだ作られていない場合の処理を考える必要がなくなります。

Djangoで、初期データの投入でも紹介した方法です。

apps.py

from django.apps import AppConfig
from django.db.models.signals import post_migrate


class AppConfig(AppConfig):
    name = 'app'

    def ready(self):
        from .models import create_default_site_detail
        post_migrate.connect(create_default_site_detail, sender=self)

models.py

def create_default_site_detail(sender, **kwargs):
    site = Site.objects.get(pk=settings.SITE_ID)
    SiteDetail.objects.get_or_create(site=site)

これで、migrate後にSiteDetailも作られることが保証されました。

models.pyに書いていますが、他の場所でも良いです。例えばサイトフレームワークでは、management.pyに初期データ作成用の関数を定義しています。他には、signals.pyのようなファイルも見かけます。

request.siteでアクセスする

settings.pyを編集します。

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',
    'django.contrib.sites.middleware.CurrentSiteMiddleware',  # これを足した
]

django.contrib.sites.middleware.CurrentSiteMiddlewareは、requestオブジェクトのsite属性にSiteモデルインスタンスを設定してくれます。

request.siteでSiteモデルインスタンスが、request.site.sitedetailでSiteDetailモデルインスタンスが取得できます。テンプレートはrequestオブジェクトが暗黙のうちに渡されるので、そちらでも{{ request.site }}のように書けます。便利ですね。

管理画面でインライン表示

SiteとSiteDetailは1対1で紐づいているので、管理画面では同時に編集できたほうがよさそうです。

admin.pyを次のようにします。

from django.contrib import admin
from django.contrib.sites.models import Site
from .models import SiteDetail


class SiteDetailInline(admin.StackedInline):
    """サイト詳細情報のインライン"""
    model = SiteDetail


class SiteAdmin(admin.ModelAdmin):
    """Siteモデルを、管理画面でSiteDetailもインラインで表示できるように"""
    inlines = [SiteDetailInline]


admin.site.unregister(Site)
admin.site.register(Site, SiteAdmin) 

こんな感じで、SiteとSiteDetailが編集できるようになります。

Relation Posts

Djangoで、初期データの投入

データベースに予めデータを用意しておきたい、という場合があります。ロジック上必要な初期データや、テスト用のデータ等がそれです。今回は、Djangoで初期データを投入する方法を紹介していきます。

Python Django

Comment

記事にコメントする

Djangoスターター

いつも大変参考にさせていただいております。

settings.pyに`'django.contrib.sites',`と`SITE_ID = 1`を追記しました。
その後
class SiteDetail(models.Model):
を定義するためのmodels.pyを、プロジェクトフォルダ/アプリケーションフォルダに新たに"sites"というフォルダを作り、そこに作成しました。

ですがmakemigrationsをしても、「No changes detected」となってしまいます。

このmodels.pyはどのフォルダに作ればよいでしょうか?

返信する

なりと

`makemigrations`は、`settings.py`の`INSTALLED_APPS`にあるアプリケーションの、`models.py`の内容をもとに行われます。

なので、Djangoアプリケーションを作り、そのアプリケーションを`INSTALLED_APPS`に追加するという作業が必要です。

startappで適当なアプリケーションをつくり、そこで作られたmodels.pyにSiteDetailを定義し、そのアプリケーションををINSTALLED_APPSに追加してください。

Djangoスターター

ありがとうございます。
解決しました!