Djangoで、キャッシュを使う

Twitterでシェア FaceBookでシェア はてなブックマークでシェア

Python - Django
2018年11月22日21:15に更新(約21日前)
2018年11月10日17:59に作成(約33日前)

旧ブログ移行記事です。

概要

Djangoでは、というより動的にページを作成する場合は殆どそうなのですが、

  1. ブラウザからあるURLにアクセスする(HTTPリクエストを送る)
  2. サーバー側のDjangoフレームワークに情報が渡され、ページを作成する(HTTPレスポンスとなるものを作る)
  3. サーバーからブラウザにページ(HTTPレスポンス)を返し、ブラウザでそれを描画する

といった処理を行います。アクセスした段階で、やっとページを作成し始めるわけです。

Djangoのキャッシュ機能では、一度作成したページを何処かに保存しておき、次にそのページにアクセスされたらそれを返すというのが基本です。非常に豊富な機能があるのですが、今回は簡単に紹介します。

準備

キャッシュをどこに保存するか、ファイルに保存することもできるし、Memcachedやデータベースを利用したり、メモリ上に保存することもできます。

今回はデータベースに保存することにします。そのための設定として、settings.pyに以下のように書いておきます。

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table',
    }
}

その後、コマンドを実行します。これで準備は完了です。

python manage.py createcachetable

Djangoプロジェクト全体をキャッシュする

まずは最も単純な、Djangoプロジェクト全体をキャッシュさせる方法です。 CommonMiddlewareの上下に追加します。

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.cache.UpdateCacheMiddleware',  # ここ
    'django.middleware.common.CommonMiddleware',  # ここ
    'django.middleware.cache.FetchFromCacheMiddleware',  # ここ
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

特定のビューだけキャッシュする

次に、特定のビューだけキャッシュさせる方法です。上のMIDDLEWAREを試した方は、元に戻しておきましょう。

views.pyで各ビューに設定することもできますが、urls.pyにキャッシュの定義することもできます。urls.pyに定義する方法のほうが、ビューの再利用がしやすくなるでしょう。

以下は60*15で、900秒、即ち15分ですね。

from django.views.decorators.cache import cache_page
...
...
urlpatterns = [
    path('', cache_page(60 * 15)(views.Top.as_view()), name='top'),
]

テンプレートでの部分的キャッシュ

テンプレートでのある部分だけキャッシュする、といったことも可能です。

以下は、ページコンテンツの一部だけをキャッシュしています。600秒...つまり10分の間は、キャッシュが使われるようになります。

{% load cache %}
  {% cache 600 content %}
    <h1>ハローワールド</h1>
     キャッシュしたいページコンテンツ
  {% endcache %}

その他のコンテンツ...

以下は記事の一覧をキャッシュしています。この場合、当然ですが10分の間は記事を更新してもキャッシュされたものが表示されます。ページの表示は早くなりますが、更新内容をすぐにお届けできないという訳ですね。

{% load cache %}
  {% cache 600 content %}
    {% for post in post_list %}
      <p>{{ post.title }}</p>
    {% endfor %}
  {% endcache %}

ビューのキャッシュや全体のキャッシュにも言えることですが、動的なコンテンツ(データの一覧など)をキャッシュする場合はよく考えて利用する必要があります。

キャッシュの削除

キャッシュを一度削除したければ、cache.clear()としましょう。

from django.core.cache import cache
cache.clear()

# 1行でコピペするならばこちら
from django.core.cache import cache;cache.clear()

manage.py shellでのインタラクティブモードでも、当然キャッシュの削除はできます。

キャッシュの注意点

Djangoプロジェクト全体のキャッシュやビュー単位でのキャッシュを利用している場合は、注意点があります。

テンプレートに以下のように書いている場合です。

{% if user.is_superuser %}
管理者用メニュー
{% else %}
通常メニュー
{% endif %}

他の表示...

最初に表示された内容でキャッシュされてしまうので、管理者が管理者メニューを見れなくなるか、又は通常ユーザーが管理者メニューを見れるようになります。

また、以下のようなビューも注意です。LoginRequiredMixinで、ログインユーザーしか見せないようなビューです。

class Top(LoginRequiredMixin, generic.TemplateView):
    template_name = 'app/top.html'

ログインユーザーでTopビューにアクセスしてしまうと、そのビューがキャッシュされます。URL単位でキャッシュを管理しており、今後そのURLに対してのアクセスは期限内であれば全てキャッシュが使われます。これが何を意味するかというと、ログインしていないユーザーであっても、キャッシュされたページが使われるということです。

キャッシュさせないデコレータ

上の問題は、never_cacheデコレータを使うことで解決できます。

from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache
from django.views import generic
from .models import Post


class Index(LoginRequiredMixin, generic.ListView):
    model = Post

    @method_decorator(never_cache)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

dispatchは、クラスベースビューにおいて本来の意味で最初に呼ばれるメソッドです。@method_decoratorは、今回のようにクラスベースビューに関数デコレータ(never_cache)を適用したいときに使うものです。

never_cacheはあくまでビューのキャッシュを無効するので、テンプレートでの部分的キャッシュや低レベルなキャッシュについては問題なく利用することができます。

Twitterでシェア FaceBookでシェア はてなブックマークでシェア

記事にコメントする