こんにちは、MSKです。
出退勤を記録するアプリの続きです。
今回はユーザー登録と認証を作っていきます。
関連する記事は以下になります。
出退勤の時間を記録するWebアプリの作り始め!
出退勤時間を記録するアプリのAPIを作る!
出退勤時間を記録するアプリのユーザー登録と認証を実装する! <- この記事はここ!
ユーザー登録
serializers.pyに次を追加しました。
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id','username','email','password')
extra_kwargs = {'password':{'write_only':True,'required': True}}
def create(self,validated_data):
user = User.objects.create_user(**validated_data)
TokenModel.objects.create(user=user)
return user
パスワードを必須にして、writeオンリーにしています。
また、認証を行うので、TokenModelのオブジェクトを作成しています。
次にviews.pyを編集して、次を追加します。
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
最後に、アプリのurls.pyに次を追加します。
router = routers.DefaultRouter()
router.register('',ApiViewSet)
router.register('user',UserViewSet)
この状態でrunserverして、Postmanからユーザー登録してみます。

Sendボタンを押すと、403 Forbiddenと表示されて、次のメッセージが出ていました。
{
"detail": "CSRF Failed: CSRF token missing or incorrect."
}
かなり悩んだのですが、PostmanのCookiesから残っていたCookieたちを消去してあげたら、うまくいきました。
{
"id": 3,
"username": "piyo",
"email": "piyo@hoge.com"
}
うまくいったと思って、管理者のページを見てみると次のような表示が・・・

パスワードが正常に登録されなかったみたいです。
いろいろ調べても分からず、途方にくれてserializers.pyを眺めていると、ふと「def create()はユーザーが作成されるときに通るはずだから、中身どうなっているか見てみるか」と思いログをprint文で仕込みました。
※組み込み系では、デバッグのやり辛さからブレイクで止めて解析するよりログを流して解析することをよくやります。(他のソフトウェア業界も同じかもしれませんが・・・)
Postmanから投げてみると、そのログが出てきませんでした・・・
「ここ通ってなさそうだな、なんで?」と悩んで見ていると気づいてしまいました・・・
def create()がclass Meta:の中に入ってしまっていることに・・・
def create()のインデントを1つ左にずらしました。
なので、上のserializers.pyは正しくは次のようになります。
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id','username','email','password')
extra_kwargs = {'password':{'write_only':True,'required': True}}
def create(self,validated_data):
user = User.objects.create_user(**validated_data)
TokenModel.objects.create(user=user)
return user
もう一度Postmanからユーザー登録を行ったところ・・・

今度はうまくいきました!
しょぼいミスというのはプログラマあるあるですね・・・
ユーザー認証
次にユーザー認証を作っていきたいと思います。
検索をしてみて、よさそうだと思ったのが「dj_rest_auth」です。
dj_rest_authについてはこのページに詳しく載っています。
インストールをしてみます。
pip install dj-rest-auth
インストールが終わったら、settings.pyを編集します。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'api.apps.ApiConfig',
'rest_framework.authtoken',
'dj_rest_auth',
]
プロジェクトのurls.pyに認証のためのパスを追加します。
path('dj-rest-auth/',include('dj_rest_auth.urls')),
ログインしたユーザーのデータしかとれないようにしたいので、ApiModelをUserModelに紐づけます。
class ApiModel(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE,null=True)
type = models.IntegerField(choices=ATTENDANCE_CHOICE)
created_at = models.DateTimeField(default=timezone.now)
次にview.pyを変更します。
認証に必要な処理と、get_querysetをオーバーライトすることでログインしているユーザーを取得し、それによりフィルターをかけています。
class ApiViewSet(viewsets.ModelViewSet):
serializer_class = ApiSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def get_queryset(self):
user = self.request.user
return ApiModel.objects.filter(user=user)
動作をみてみようと思ってrunserverすると、エラーが起こります・・・
AssertionError: `basename` argument not specified, and could not automatically determine the name from the viewset, as it does not have a `.queryset` attribute.
querysetを使う場合にはbasenameを入れてあげないといけないみたいですね。
router = routers.DefaultRouter()
router.register('',ApiViewSet,basename='api')
router.register('user',UserViewSet,basename='user_regi')
runserverするとエラーが解消されているようです。
dj-rest-auth/login/に登録しているユーザー名、パスワードのパラメータを記述してアクセスします。


正常に動作しているみたいです。
keyという名前でトークンが与えられています。
ログインしているユーザーでGETをしてみたいと思います。
HeadersにAuthorizationを追加して、先ほど取得したkeyをtoken (取得したkey)でvalueに入力し、/api/にアクセスを行います。

GETできたデータはログインしたuserのものになっています。

最後に
今回はユーザー登録と認証を作りました。
「dj-rest-auth」を使うと簡単に認証機能を作ることができました。
次は今回までで作ったアプリをサーバーにあげてみたいと思います。
以上、「出退勤時間を記録するアプリのユーザー登録と認証を実装する!」でした。
最後までご覧いただき、ありがとうございます。