Django 1.10アップデート時にはまったこと

2016年8月1日にDjango 1.10がリリースされました。このバージョンにアップデートしたところ、3点ほどはまったことがあったので忘備録として残しておきます。

authenticateでis_activeをチェック

django.contrib.auth.authenticateメソッドで、is_activeがTrueかをチェックするようになりました。今までis_activeをチェックし、エラーコードを返していた場合、エラーコードが一致していない場合はテストケースなどの修正が必要になってきます。

from django.contrib.auth import authenticate
from django.contrib.auth.models import User

# ユーザを作成します。
User.objects.all().delete()
username = 'foo'
password = 'password'
user = User.objects.create_user(username, 'foo@example.com', password)

# 認証されることを確認します。
user = authenticate(username=username, password=password)
type(user)  # print <class 'django.contrib.auth.models.User'>

# is_activeをFalseにします。
user.is_active = False
user.save()

# 認証されない(Noneが返る)ことを確認します。
user = authenticate(username=username, password=password)
type(user)  # print <class 'NoneType'>

ちなみにDjango 1.9ではis_activeに関わらず認証されていました。

...
# is_activeをFalseにします。
user.is_active = False
user.save()

# 認証されない(Noneが返る)ことを確認します。
user = authenticate(username=username, password=password)
type(user)  # print <class 'django.contrib.auth.models.User'>

セッションからユーザ取得時のハッシュチェック

セッションからユーザを取得する際に、ハッシュ値のチェックが追加されました。具体的には、セッションに保持したハッシュ値と、ユーザモデルから計算したハッシュ値を比較しています。ハッシュ値の比較周り

デフォルトのユーザモデルを使っている場合、このハッシュ値の計算にはパスワードを使っています。そのためパスワードを更新するとハッシュ値が一致せず、AnonymousUserになってしまいます。それにより、パスワード変更後にログイン後のページにリダイレクトさせていた場合、ログイン前のページ(ログイン画面など)にリダイレクトしてしまいます。

from importlib import import_module
from django.conf import settings
from django.contrib.auth import update_session_auth_hash, get_user, SESSION_KEY, BACKEND_SESSION_KEY
from django.contrib.auth.models import User
from django.http import HttpRequest

# ユーザを作成します。
User.objects.all().delete()
username = 'myuser'
password = 'password'
user = User.objects.create_user(username, 'foo@example.com', password)

# セッションを作成します。
engine = import_module(settings.SESSION_ENGINE)
session = engine.SessionStore('123')
session[BACKEND_SESSION_KEY] = 'django.contrib.auth.backends.ModelBackend'
session[SESSION_KEY] = user.pk

# リクエストを作成します。
request = HttpRequest()
request.user = user
request.session = session

# ハッシュ値を計算してセッションに登録します。
update_session_auth_hash(request, user)

# セッションを使ってユーザを取得します。
get_user(request)  # <User: myuser>

# パスワードを更新します。
user.password = 'BAR'
user.save()

# パスワード更新後、セッションからユーザを取得するとAnonymousUserになっています。
get_user(request)  # <django.contrib.auth.models.AnonymousUser object at ...>

パスワード変更後、ユーザのセッションを継続するにはfrom django.contrib.auth.update_session_auth_hashが使えます。

# Viewの中

user = form.save()  # パスワード更新
update_session_auth_hash(request, user)

ちなみに、Django 1.9ではハッシュ値のチェックをしないため、パスワード変更してもセッションからユーザを取得できました。

...
user.password = 'BAR'
user.save()
get_user(request)  # <User: myuser>

staticタグのエスケープ

これは元々の使い方が悪かったのが原因です。以下のようにstaticタグの中に「@」やスペースがあると、%xxを使ってエスケープされます。

{% load static %}

<img src="{% static 'foo.png' %}" srcset="{% static 'foo@2x.png 2x' %}" />
↓
<img src="/static/foo.png" srcset="/static/foo%402x.png%202x" />

拡張子(png)の後の「%202x」により、/static/foo%402x.png%202xという画像がないため404を返すようになりました。

修正は、404が発生した原因の「 2x」をstaticタグの外に出してエスケープされないようにしました。

{% load static %}

<img src="{% static 'foo.png' %}" srcset="{% static 'foo@2x.png' %}  2x" />
↓
<img src="/static/foo.png" srcset="/static/foo%402x.png 2x" />

ちなみに、staticfilesINSTALLED_APPSから除外した場合はエスケープされないみたいです。

INSTALLED_APPS = [
  ...
  # 'django.contrib.staticfiles',
  ...
]
<img src="{% static 'foo.png' %}" srcset="{% static 'foo@2x.png 2x' %}" />
↓
<img src="/static/foo.png" srcset="/static/foo@2x.png 2x" />

この記事が役に立った場合、シェアしていただけると励みになります!!

この記事に関する質問は@ysk_murayamaでご連絡ください。可能な内容であれば回答します!