Django自分用メモになります
今回はクラスベースビュー(ログイン)についてまとめます
開発環境
OS:mac
エディタ:vscode
python:3.10.9
django:4.1.0
LoginRequiredMixin
ログインしていないと動かしたくないviewに多重継承させる
またはログインが必要なViewにデコレータをつける
@method_decorator(login_required)
下準備(AbstractBaseUserなどの復習)
from django.db import models
from django.contrib.auth.models import (
BaseUserManager,AbstractBaseUser,PermissionsMixin
)
from django.urls import reverse_lazy
class UserManager(BaseUserManager):
def create_user(self,username,email,password=None):
if not email:
raise ValueError('メールアドレスをいれてください')
user =self.model(
username = username,
email = email
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self,username,email,password=None):
user = self.model(
username=username,
email = email,
)
user.set_password(password)
user.is_staff=True
user.is_active=True
user.is_superuser=True
user.save(using=self._db)
return user
class Users(AbstractBaseUser,PermissionsMixin):
username = models.CharField(max_length=150)
email = models.EmailField(max_length=255,unique=True)
is_active=models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS=['username']
objects = UserManager()
def __str__(self):
return self.email+''+self.username
def get_absolute_url(self):
return reverse_lazy('accounts:home')
settings.pyに設定も忘れず
AUTH_USER_MODEL = 'accounts.Users'
# Application definition
INSTALLED_APPS = [
#(略)
'accounts',
]
viewの作成 登録フォームの作成 urlの作成 遷移ができるhtmlの作成
from django.shortcuts import render
from django.views.generic.base import TemplateView,View
from django.views.generic.edit import CreateView,FormView
from .forms import RegistForm
class HomeView(TemplateView):
template_name = 'home.html'
class RegistUserView(CreateView):
template_name = 'regist.html'
form_class = RegistForm
class UserLoginView(FormView):
pass #のちに記述
class UserLogoutView(View):
pass #のちに記述
from django import forms
from django.contrib.auth import get_user_model
from .models import Users
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
User=get_user_model
class RegistForm(forms.ModelForm):
username=forms.CharField(label='名前')
age = forms.IntegerField(label='年齢',min_value=0)
email =forms.EmailField(label='メールアドレス')
password = forms.CharField(label='パスワード',widget=forms.PasswordInput)
confirm_password = forms.CharField(label='パスワード再入力',widget=forms.PasswordInput)
class Meta:
model=Users
fields = ('username','age','email','password')
def clean(self):
cleaned_data=super().clean()
password = cleaned_data.get('password')
confirm_password = cleaned_data.get('confirm_password')
if password !=confirm_password:
raise ValidationError('パスワードが一致しません')
def save(self,commit=False):
user =super().save(commit=False)
validate_password(self.cleaned_data.get('password'),user)
user.set_password(self.cleaned_data.get('password'))
user.save()
return user
from django.urls import path
from .views import(
HomeView,RegistUserView,UserLoginView,UserLogoutView
)
app_name='accounts'
urlpatterns = [
path('home/',HomeView.as_view(),name='home'),
path('regist/',RegistUserView.as_view(),name='regist'),
path('user_login/',UserLoginView.as_view(),name='user_login'),
path('user_logout/',UserLogoutView.as_view(),name='user_logout'),
]
<!DOCTYPE html>
<html lang='ja'>
<head>
<meta charset='utf-8'>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
<body>
<nav class ='navbar navbar-expand-lg navbar-light bg-light'>
<a class ='navbar-brand' href="{%url 'accounts:home'%}">ホーム</a>
{% if user.is_authenticated%} {#ログインしているかどうかで表示を切り替える#}
<a class ='navbar-brand' href="{%url 'accounts:user_logout'%}">ログアウト</a>
{% else %}
<a class ='navbar-brand' href="{%url 'accounts:user_login'%}">ログイン</a>
<a class ='navbar-brand' href="{%url 'accounts:regist'%}">ユーザ登録</a>
{% endif %}
</nav>
{% block content%}
{% endblock %}
</body>
</html>
ログインとログアウトの実装
views.pyで先ほど定義したUserLoginViewとUserLogoutViewの処理を書いていく。
ログインに関してはforms.pyでログインに必要な項目を記載したフォームをつくったら
FormViewを継承し、form_classで使うフォームに指定。
def postでPOSTが行われたそのemailとpasswordを取得しauthenticateで
存在するか確かめてTrueであればloginメゾットでログインし、redirectさせる
ログアウトもgetで受け取ってlogoutメゾットを使って、redirectさせるだけ
class UserLoginForm(forms.Form):
email=forms.EmailField(label='メールアドレス')
password =forms.CharField(label='パスワード',widget=forms.PasswordInput)
class UserLoginView(FormView):
template_name='user_login.html'
form_class=UserLoginForm
# success_url=reverse_lazy('accounts:home') #redirectさせるのでいらない
def post(self,request,*arg,**kwargs):
#POSTが行われたらemailとpasswordを取得して
email = request.POST['email']
password = request.POST['password']
#そのユーザが存在するかauthenticateで確かめる
user = authenticate(email=email,password=password)
#userが存在し、かつ、is_activeがTrueの場合にログインを行う
if user is not None and user.is_active:
login(request,user)
return redirect('accounts:home')
class UserLogoutView(View):
def get(self,request,*args,**kwargs):
logout(request)
return redirect('accounts:user_login')
ログインしていないと実行させたくないを設定する方法(LoginRequiredMixin,login_required)
やりかたは3つ
・dispatchメゾットにmethod_decorator(login_required)を定義する方法
・クラス全体にmethodo_decorator(login_required,name='dispatch')を定義する方法
・LoginRequiredMixinをクラスに多重継承させる
importするもの
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.contrib.auth.mixins import LoginRequiredMixin
# 2.@method_decorator(login_required,name='dispatch')
class UserView(LoginRequiredMixin,TemplateView): #3
template_name = 'user.html'
#1. @method_decorator(login_required) #ログインしていないと実行されない
#dispatchをオーバーライド
#dispatchとはgetリクエストならばgetをpostリクエストならpost処理を行うメゾット、だからリクエストの際必ず行われる
def dispatch(self,*args,**kwargs):
return super().dispatch(*args,**kwargs)
dispatchとはgetリクエストならばgetをpostリクエストならpost処理を行うメゾット、だからリクエストの際必ず行われる
ここでLoginRequiredMixinやlogin_requiredで弾かれた場合内部的にどのurlにアクセスしていて
page not found がでているのかというとデフォルトでhttp://127.0.0.1:8000/accounts/login/
にアクセスしていて、urls.pyにそんなページは存在しないからpage not foundとなる。
もし弾かれた場合にログイン画面に移動させたい場合はsettings.pyでLOGIN_URLを設定してあげる
LOGIN_URL='/accounts/user_login' #デフォルトのaccounts/login/をにaccounts/user_loginにリダイレクト先を変更する
?next= に対応させる(よりスマートなページ移行)
login_requiredまたはLoginRequiredMixinによって弾かれてかつ
settings.pyでLOGIN_URLでログインページにリダイレクトした場合、
ログインページに普通にアクセスするときと違い、urlパラメータに?next=/accounts/user/と
表示されていることがわかる。
?next=とはこのページに行こうとして弾かれてしまったので、ログインができたらこのページに行こうとしていたよという情報を残してくれている。
もしnext_urlがあればログインした時にホーム画面などに飛ぶのではなく、その行こうとしたページに飛ぶといった処理を
1.ログインするviewに追加することと、
2.ログインのhtmlにinputからvalueで?next=のあとの情報をPOSTするように設定すると、そのように実行されるのでスマートになる
class UserLoginView(FormView):
template_name='user_login.html'
form_class=UserLoginForm
# success_url=reverse_lazy('accounts:home')
def post(self,request,*arg,**kwargs):
#POSTが行われたらemailとpasswordを取得して
email = request.POST['email']
password = request.POST['password']
#そのユーザが存在するかauthenticateで確かめる
user = authenticate(email=email,password=password)
#userが存在し、かつ、is_activeがTrueの場合にログインを行う
if user is not None and user.is_active:
login(request,user)
# ?nextがurlパラメータにあればそちらに移行する #追加
next_url =request.POST['next']
if next_url:
return redirect(next_url)
return redirect('accounts:home')
<input type="hidden" name="next" value="{{request.GET.next}}">
#request.GET.nextで?next=の後の情報をviewに投げることができる
LoginView,LogoutView
from django.contrib.auth.views import LoginView #ログインするためのView
template_name= '' #表示するテンプレートの指定
authentication_form #ログイン認証に用いられるFormを指定(デフォルトはAuthenticationForm)
from django.contrib.auth.forms import AuthenticationForm #ログイン認証に用いられるForm(forms.pyで使うフォームに継承させる)
from django.contrib.auth.views import LogoutView #ログアウトするためのView
#settings.pyに設定する内容
LOGIN_REDIRECT_URL #LoginViewで次に遷移する先が指定されていなかった場合の遷移先のURL
LOGIN_URL #ログインしていない状態でlogin_requiredが指定されているViewを表示しようとした場合にリダイレクトされるView
LOGOUT_REDIRECT_URL #LogoutViewで次に遷移する先が指定されていなかった場合の遷移先のURL
LoginViewを継承してUserLoginViewを作り直す。
class UserLoginView(LoginView):
template_name='user_login.html'
authentication_form=UserLoginForm
class UserLogoutView(LogoutView):
pass
ここでのusernameとはmodels.pyでのUSERNAME_FIELD = 'email'に設定したものを扱うのでEmailFieldをつかう
class UserLoginForm(AuthenticationForm):
#ここでのusernameとはmodels.pyでのUSERNAME_FIELD = 'email'に設定したものを扱うのでEmailFieldをつかう
username= forms.EmailField(label='メールアドレス')
password= forms.CharField(label='パスワード',widget=forms.PasswordInput())
ログインの遷移先を指定しないとpage not foundになる
LOGIN_REDIRECT_URL='/accounts/home'
LOGOUT_REDIRECT_URL='/accounts/user_login'
次回のログインを記憶させる(セッションの保存時間を変更する)
SESSION_COOKIE_AGE #セッションの保存時間(秒) デフォルトは1209600(2週間)
公式
さらにrequest.session.set_expiry(value):セッションの保存時間を引数の秒に変更する。
引数が0の場合、ブラウザを閉じるとセッションがなくなる。もしvalueがdatetimeまたはtimedeltaオブジェクトならば指定された日時に破棄される
公式
SESSION_COOKIE_AGE=5 #5秒でセッションが解除される
ログイン状態を保持するボタンをforms.pyに追加
class UserLoginForm(AuthenticationForm):
#ここでのusernameとはmodels.pyでのUSERNAME_FIELD = 'email'に設定したものを扱うのでEmailFieldをつかう
username= forms.EmailField(label='メールアドレス')
password= forms.CharField(label='パスワード',widget=forms.PasswordInput())
remember= forms.BooleanField(label='ログイン状態を保持する',required=False)
class UserLoginView(LoginView):
template_name='user_login.html'
authentication_form=UserLoginForm
#ログイン状態を保持する(session時間を変更する)
def form_valid(self,form):
#rememberを取り出してTrueであれば秒数を更新
remember=form.cleaned_data['remember']
if remember:
self.request.session.set_expiry(1200000)
return super().form_valid(form)