はじめに
Djnagoとdjango-allauthを使ってGoogleの2段階認証を実装します。
通常、Googleのアカウントを持っていると誰でもログイン(2段階認証)できますが、今回の仕様上、予めDjangoの管理画面でCustomUserに登録したメールアドレスを持つユーザにログインを許可します。
また、管理側でログインを許可するユーザを制御する関係上、ログイン、ログアウトのみテンプレートを用意し、それ以外は404エラーとして処理します。
Googleの2段階認証とDjangoが作るCookieの有効期限は異なるので、Cookieの期限はsettings.pyで制御します。
ログ機能も付けているので、不要なら削除します。
作業手順
Djangoのプロジェクトを作って、
mkdir ProjectName cd ProjectName django-admin startproject config .
ログを記録するなら、下記のsettings.pyに設定したようにディレクトリを作成して、
mkdir log
ライブラリをインストールして、
pipenv install django-allauth
CustomUserを作成して、
python manage.py startapp users
今回、掲載した複数のファイルを修正して、makemigrations、migrateを実行して、
python manage.py makemigrations python manage.py migrate
管理ユーザを作成して、
python manage.py createsuperuser
Djangoの管理画面で、Usersテーブルに新規ユーザ(メールアドレスを登録)を作成してからテストします。
python manage.py runserver 192.168.10.100
構成
実際はpipenvで作った仮想環境で行っています。
Debian GNU Linux(Bookworm) Python 3.10.13 Django 5.1.2 django-allauth 65.0.2 pipenv 2023.10.3 google-api-core 2.21.0 google-api-python-client 2.149.0 google-auth 2.35.0 google-auth-httplib2 0.2.0 google-auth-oauthlib 1.2.1 googleapis-common-protos 1.65.0
ディレクトリ構成
django-allauthのルーティングをCustomUserのルーティング(urls.py)で上書きして使いますが、ひとつだけ制御できなかったので、ローカルにdjango-allauthのテンプレート構成を再現してファイルを置いています。
ただ、urls.pyで制御できなかったファイルが
.venv/lib/python3.10/site-packages/allauth/templates/socialaccount/login_cancelled.html
と分かったので、以下のように対処しました。
・ローカルにdjango-allauthのテンプレート用ディレクトリを作成。
・404.htmlをコピーしてlogin_cancelled.htmlに名前変更。
・settings.pyのTEMPLATESにローカルのallauthテンプレートを参照するようにパスを追加。
今回、関係ありそうなファイルは以下の通りです。
Bootstrapはダウンロード・解凍して、設置しています。
project/
- config/
settings.py
- templates/
- 404.html
- home.html
- base.html
- allauth/
- socialaccount/
login_cancelled.html
- users
login.html
logout.html
- users/
admin.py
forms.py
models.py
urls.py
views.py
- static
- css
bootstrap.min.css
bootstrap.min.css.map
- js
bootstrap.min.js
bootstrap.min.js.map
- favicon
favicon.ico
django-allauthのインストール
pipの場合は、
pip install django-allauth
pipenvを使っている場合は、
cd project pipenv shell pipenv install django-allauth
settings.py
django-allauthに関連する部分の抜粋です。
デバッグ中はデータベースを削除、作成を繰り返すことが多く、Webで毎回設定するのが面倒なのでGoogleのclient_id、secretは直書きします。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites', # django-allauth
'allauth', # django-allauth
'allauth.account', # django-allauth
'allauth.socialaccount', # django-allauth
'allauth.socialaccount.providers.google', # django-allauth
'users.apps.UsersConfig', # CustomUser
省略
]
# カスタムユーザーモデル
AUTH_USER_MODEL = 'users.CustomUser'
SITE_ID = 1
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend', # allauth用
)
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
# メール認証無し
ACCOUNT_EMAIL_VERIFICATION = 'none'
# CustomUserモデルにusernameは不要
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_UNIQUE_EMAIL = True
SOCIALACCOUNT_EMAIL_REQUIRED = True
SOCIALACCOUNT_AUTO_SIGNUP = True
# トークンを保存する
SOCIALACCOUNT_STORE_TOKENS = True
# home.html
LOGIN_URL = 'home'
LOGOUT_REDIRECT_URL = 'home'
ACCOUNT_LOGOUT_REDIRECT_URL = 'home'
LOGIN_REDIRECT_URL = 'home'
SOCIALACCOUNT_ADAPTER = 'users.views.CustomSocialAccountAdapter'
# Googleのclient_idとsecretを直に記述
SOCIALACCOUNT_PROVIDERS = {
'google': {
"APPS": [
{
"client_id": 'xxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.apps.googleusercontent.com',
"secret": 'xxxxxx-xxxxxx-xxxxxx',
"key": '',
"sites": '127.0.0.1',
},
],
'SCOPE': [
'profile',
'email',
],
'AUTH_PARAMS': {
'access_type': 'online',
},
# PKCEを有効化
'OAUTH_PKCE_ENABLED': True,
}
}
'''
Cookieのセッション管理
優先順位
1. SESSION_EXPIRE_AT_BROWSER_CLOSE
2. SESSION_COOKIE_AGE
3. SESSION_SAVE_EVERY_REQUEST
'''
# Cookie有効期間(秒)
SESSION_COOKIE_AGE = 24 * 60 * 60 * 7
# 設定された期限に関係なく、ブラウザを閉じたらセッションを終了
#SESSION_EXPIRE_AT_BROWSER_CLOSE = True
# 全てのリクエストでセッションを保存する。
#SESSION_SAVE_EVERY_REQUEST = True
'''
infoとerrorで出力ファイルを分別する。
CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET
'''
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
# 時刻を自動的に挿入する。
# 例:
# 2024-06-25 15:19:48,384 : users.views : INFO : Login emailaddress
'formatters': {
'verbose': {
'format': '%(asctime)s : %(name)s : %(levelname)s : %(message)s'
},
},
'handlers': {
'info_handlers': {
#'level': 'INFO',
'level': 'WARNING',
'class': 'logging.handlers.RotatingFileHandler',
'filename': BASE_DIR / 'log/info.log',
'maxBytes': 1024*1024*5, # 5 MB
'backupCount': 5,
'formatter': 'verbose',
},
'error_handlers': {
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler',
'filename': BASE_DIR / 'log/error.log',
'maxBytes': 1024*1024*5, # 5 MB
'backupCount': 5,
'formatter': 'verbose',
},
},
'loggers': {
'': {
'level': 'INFO',
#'handlers': ['info_handlers', 'error_handlers'],
'handlers': ['info_handlers',],
},
},
}
login_cancelled.html
「構成」に記載した理由から仕方なく作成。
{% extends 'base.html' %}
{% block title %}
ページが見つかりませんでした
{% endblock title %}
{% block h1_title %}
It is just a 404 Error !
{% endblock h1_title %}
{% block error_404 %}
<p class="text-center">
Sorry, the requested URL was not found on this server.
<br>
<a href="{% url 'account_login' %}" class="btn btn-outline-danger">Login</a>
</p>
{% endblock error_404 %}
models.py
AbstractBaseUserを使ってCustomUserを作ります。
usenameは使わないのでemailで代用します。
また、ユーザデータは今回の仕様上、管理者のみ操作します。
from django.db import models
from django.contrib import auth
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.core.mail import send_mail
from django.contrib.auth.hashers import make_password
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import PermissionsMixin
import uuid
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError('The given email must be set')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.password = make_password(password)
user.save(using=self._db)
return user
def create_user(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, email=None, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
if not password:
raise ValueError('Password is required for super users.')
return self._create_user(email, password, **extra_fields)
def with_perm(self, perm, is_active=True, include_superusers=True, backend=None, obj=None):
if backend is None:
backends = auth._get_backends(return_tuples=True)
if len(backends) == 1:
backend, _ = backends[0]
else:
raise ValueError(
'You have multiple authentication backends configured and '
'therefore must provide the `backend` argument.'
)
elif not isinstance(backend, str):
raise TypeError(
'backend must be a dotted import path string (got %r).' % backend
)
else:
backend = auth.load_backend(backend)
if hasattr(backend, 'with_perm'):
return backend.with_perm(
perm,
is_active=is_active,
include_superusers=include_superusers,
obj=obj,
)
return self.none()
'''
カスタムユーザ定義
'''
class CustomUser(AbstractBaseUser, PermissionsMixin):
# Primary KeyをUUIDに変更
id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
email = models.EmailField(verbose_name='Email address', unique=True)
is_staff = models.BooleanField(
_('staff status'),
default=False,
help_text=_('Designates whether the user can log into this admin site.'),
)
is_active = models.BooleanField(
_("active"),
default=True,
help_text=_(
"Designates whether this user should be treated as active. "
"Unselect this instead of deleting accounts."
),
)
# 追加フィールド
created_time = models.DateTimeField(verbose_name='作成日時', auto_now_add=True)
updated_time = models.DateTimeField(verbose_name='更新日時', auto_now=True)
name = models.CharField(verbose_name='氏名', max_length=512)
section = models.CharField(verbose_name='所属', max_length=1024)
remarks = models.TextField(verbose_name='備考', blank=True, null=True)
objects = UserManager()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
class Meta:
verbose_name = 'ユーザー'
verbose_name_plural = 'ユーザー'
'''
ACCOUNT_USER_MODEL_USERNAME_FIELDを設定しているが、django-allauthでusernameが必要なため
usernameとしてemailを返すように設定。
'''
@property
def username(self):
return self.email
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
views.py
ログインとログアウトの処理をオーバーライドします。
登録ユーザの有無を検査してからGoogleの2段階認証を行っているので、EmailAddressテーブルにメールアドレスを登録し、CustomUserとリンクを設定するのは自前で行います。
from django.contrib.auth import get_user_model
from django.contrib.auth.signals import user_logged_in
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from django.dispatch import receiver
from django.conf import settings
from django.shortcuts import render
from django.core.exceptions import ValidationError
from allauth.account.views import LoginView, LogoutView
from allauth.account.models import EmailAddress
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from allauth.socialaccount.providers.oauth2.views import OAuth2LoginView
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from .forms import CustomLoginForm
from .models import CustomUser
import logging
logger = logging.getLogger(__name__)
# Googleの2段階認証に伴う処理
class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
email = sociallogin.user.email
try:
# CustomUserに登録されているか確認
user = CustomUser.objects.get(email=email)
# CustomUserモデルのインスタンスを保存する(ルーティングに必要)
sociallogin.user = user
sociallogin.user.save()
# メールアドレスを登録してCustomUserモデルと紐づける。
email_address, created = EmailAddress.objects.get_or_create(
user=user,
email=email,
defaults={'primary': True, 'verified': True}
)
if created:
logger.info(f'{request.META.get("REMOTE_ADDR")} {email} email successfully created. (EmailAddress table)')
except CustomUser.DoesNotExist:
# 未登録の場合はエラーを発生させる
messages.error(request, 'Your email is not registered. #1')
raise ValidationError("This email is not registered.")
# Googleの2段階認証が失敗した場合に、EmailAddressテーブルからメールアドレスを削除する。
def authentication_failed(self, request, sociallogin):
email = sociallogin.user.email
try:
email_address = EmailAddress.objects.get(email=email)
email_address.delete()
logger.info(f'{request.META.get("REMOTE_ADDR")} {email} email removed due to authentication failure.')
except EmailAddress.DoesNotExist:
logger.info(f'{request.META.get("REMOTE_ADDR")} {email} No email found to remove on authentication failure.')
# Googleの2段階認証でログイン処理を行う。
class CustomLoginView(LoginView):
template_name = 'users/login.html'
form_class = CustomLoginForm
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
next_url = request.POST.get('next', 'home')
if form.is_valid():
email = form.cleaned_data['email']
try:
# CustomUserに存在するか確認
user = CustomUser.objects.get(email=email)
# Google OAuth2 ログインを開始
oauth2_login = OAuth2LoginView.adapter_view(GoogleOAuth2Adapter)
return oauth2_login(request)
except CustomUser.DoesNotExist:
messages.error(request, 'Your email is not registered. #2')
return render(request, self.template_name, {'form': form})
except Exception as e:
logger.error(f'{request.META.get("REMOTE_ADDR")} {email} Error during login: {str(e)}')
messages.error(request, f'An error has occurred: {str(e)}')
return render(request, self.template_name, {'form': form})
return render(request, self.template_name, {'form': form})
# ログアウト処理を行う。
class CustomLogoutView(LoginRequiredMixin, LogoutView):
template_name = 'users/logout.html'
forms.py
from allauth.account.forms import LoginForm
from django import forms
from .models import CustomUser
'''
views.pyで使う。
ユーザのログイン
'''
class CustomLoginForm(LoginForm):
email = forms.EmailField(
widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'Enter your email'}),
label="Email",
required=True
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
del self.fields['login']
del self.fields['password']
# Bootstrapクラスを適用
for field in self.fields.values():
field.widget.attrs['class'] = 'form-control'
def clean(self):
return self.cleaned_data
def login(self, request, redirect_url=None):
pass
'''
admin.pyで使う。
パスワードフィールドを除外したカスタムのユーザー作成フォーム
'''
class CustomUserCreationForm(forms.ModelForm):
class Meta:
model = CustomUser
# パスワードフィールドを除外
fields = ('email', 'name', 'section', 'remarks', 'is_staff', 'is_active')
def save(self, commit=True):
user = super().save(commit=False)
# パスワードを使用不可に設定
user.set_unusable_password()
if commit:
user.save()
return user
'''
admin.pyで使う。
パスワードフィールドを除外したカスタムのユーザー変更フォーム
'''
class CustomUserChangeForm(forms.ModelForm):
class Meta:
model = CustomUser
# パスワードフィールドを除外
fields = ('email', 'name', 'section', 'remarks', 'is_staff', 'is_active')
urls.py
ログインとログアウト以外のdjango-allauthのルーティングは、全て404.htmlを表示させます。
ここでlogin_cancelled.htmlのルーティングを制御できれば良かったのですが・・・。
from django.urls import path, include
from django.shortcuts import render
from django.views.generic import TemplateView
from .views import CustomLoginView, CustomLogoutView
def redirect_to_404(request, exception=None):
return render(request, '404.html', status=404)
urlpatterns = [
# django-allauthのログインとログアウトを上書き
path('account/login/', CustomLoginView.as_view(), name='account_login'),
path('account/logout/', CustomLogoutView.as_view(), name='account_logout'),
# Googleの2段階認証で使う。
path('account/social/login/google/', include('allauth.socialaccount.providers.google.urls')),
# django-allauthのログインとログアウト以外は404エラーにリダイレクト
# 確認のためnameは同じだが、URLが異なるものも記述。
path('account/login/cancelled/', redirect_to_404, name='account_login_cancelled'),
path('account/login/confirm/', redirect_to_404, name='account_confirm_login_code'),
path('account/signup/passkey', redirect_to_404, name='account_signup_by_passkey'),
path('account/login/code/', redirect_to_404, name='account_request_login_code'),
path('account/signup/', redirect_to_404, name='account_signup'),
path('account/signup/', redirect_to_404, name='account_reauthenticate'),
path('account/password/reset/', redirect_to_404, name='account_reset_password'),
path('account/password/reset/done/', redirect_to_404, name='account_reset_password_done'),
path('account/password/reset/key/', redirect_to_404, name='account_reset_password_from_key'),
path('account/password/reset/key/done/', redirect_to_404, name='account_reset_password_from_key_done'),
path('account/password/change/', redirect_to_404, name='account_change_password'),
path('account/password/set', redirect_to_404, name='account_set_password'),
path('account/email/', redirect_to_404, name='account_email'),
path('account/inactive/', redirect_to_404, name='account_inactive'),
path('account/confirm-email/', redirect_to_404, name='account_confirm_email'),
path('account/email-verification-sent/', redirect_to_404, name='account_email_verification_sent'),
path('account/social/', redirect_to_404, name='socialaccount_connections'),
path('account/social/signup/', redirect_to_404, name='socialaccount_signup'),
path('account/signup/', redirect_to_404, name='socialaccount_signup'),
path('account/social/login/cancelled/', redirect_to_404, name='socialaccount_login_cancelled'),
path('account/login/cancelled/', redirect_to_404, name='socialaccount_login_cancelled'),
path('account/social/login/redirect/', redirect_to_404, name='socialaccount_login_redirect'),
path('account/login/error/', redirect_to_404, name='socialaccount_login_error'),
# django-allauthの定義
path('account/', include('allauth.urls')),
]
handler404 = redirect_to_404
admin.py
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext_lazy as _
from .models import CustomUser
from .forms import CustomUserCreationForm, CustomUserChangeForm
'''
カスタムユーザー
'''
CustomUser = get_user_model()
class CustomUserAdmin(UserAdmin):
save_on_top = True
save_as = True
'''
ユーザー作成フォーム
forms.pyで定義
'''
add_form = CustomUserCreationForm
'''
ユーザー変更フォーム
forms.pyで定義
'''
form = CustomUserChangeForm
# ユーザーの詳細画面、編集画面で表示するフィールド
fieldsets = [
(None, {'fields': ('email',)}),
(_('Personal info'), {'fields':('name', 'section', 'remarks')}),
(_('Permissions'), {'fields':('is_active', 'is_staff', 'is_superuser', 'groups','user_permissions')}),
(_('Important dates'), {'fields':('last_login',)}),
]
# ユーザー追加フォームで入力するフィールド
add_fieldsets = (
(None,
{
'classes': ('wide',),
'fields': (
'email',
'name',
'section',
'remarks',
),
},),
)
list_display = (
'email',
'name',
'section',
'is_staff',
'is_superuser',
'is_active',
'created_time',
'updated_time',
)
list_filter = ('is_staff', 'is_superuser', 'groups')
search_fields = ('email', 'name', 'section')
ordering = ('is_active',)
filter_horizontal = (
'groups',
'user_permissions',
)
admin.site.register(CustomUser, CustomUserAdmin)
login.html
メールアドレスを入力して、Googleマークのボタンをクリックすると、Googleの2段階認証が起動します。

{% extends 'base.html' %}
{% load static %}
{% block title %}
Login
{% endblock title %}
{% block body %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-4">
<h2 class="text-center">Login</h2>
<p>Only users who have been pre-registered by the administrator can use it.</p>
<p>Please enter your email address to continue.</p>
<form method="POST" action="{% url 'account_login' %}">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'home' %}">
<div class="form-group mb-3">
<input type="email" class="form-control" id="email" name="email" placeholder="Enter your email address" required>
</div>
<button type="submit" class="btn btn-light w-100 d-flex align-items-center justify-content-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="24px" height="24px">
<path fill="#FFC107" d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12c0-6.627,5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24c0,11.045,8.955,20,20,20c11.045,0,20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"/>
<path fill="#FF3D00" d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"/>
<path fill="#4CAF50" d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"/>
<path fill="#1976D2" d="M43.611,20.083H42V20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z"/>
</svg>
<span class="ms-2">Sign in with Google</span>
</button>
</form>
{% if messages %}
<div class="mt-3">
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
{% endblock body %}
logout.html
{% extends 'base.html' %}
{% load static %}
{% block title %}
Logout
{% endblock title %}
{% block h1_title %}
Logout
{% endblock h1_title %}
{% block body %}
<p>You are logged out.</p>
<p><a href="{% url 'account_login' %}">Login</a></p>
{% endblock body %}
base.html
login.html、logout.htmlの最初で読み込みます。
Bootstrapは、static/cssに置いています。
{% load i18n static %}
<!doctype html>
<html lang="ja" data-bs-theme="dark">
<head>
<meta charset="utf-8">
<link rel="shortcut icon" type="image/png" href="{% static '' %}">
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<title>
{% block title %}
{% endblock %}
</title>
</head>
<body>
{# container: 表示幅を可変にする。#}
<div class="container-fluid">
{# formのエラー表示 #}
{% if form.errors and request.method == 'POST' %}
{% for field, errors in form.errors.items %}
{% for error in errors %}
<div class="alert alert-danger" role="alert">
<p class="mb-0">{{ field }} {{ error }}</p>
</div>
{% endfor %}
{% endfor %}
{% endif %}
{# 「form.errors」以外のエラー表示(カスタムバリデーションなど) #}
{% if non_field_errors %}
<div class="alert alert-danger" role="alert">
{% for error in form.non_field_errors %}
<p{% if forloop.last %} class="mb-0"{% endif %}>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
{# SuccessMessageMixinのメッセージ表示 #}
{% if messages %}
<div class="text-center messages alert alert-primary" role="alert">
{% for message in messages %}
{{ message }}
{% endfor %}
</div>
{% endif %}
{% block error_404 %}
{% endblock %}
{% block body %}
{% endblock %}
{# フッタ #}
<footer class="container-fluid">
<small>
©ほげ
</small>
</footer>
</div>
</body>
</html>
404.html
django-allauthでルーティングされているログイン、ログアウト以外は、このページを表示します。
{% extends 'base.html' %}
{% block title %}
ページが見つかりませんでした
{% endblock title %}
{% block h1_title %}
It is just a 404 Error !
{% endblock h1_title %}
{% block brain_404 %}
<p class="text-center">
Sorry, the requested URL was not found on this server.
<br>
<a href="{% url 'account_login' %}" class="btn btn-outline-danger">Login</a>
</p>
{% endblock brain_404 %}
home.html
ログインが成功した場合に表示します。
各種メニューを列挙します。
{% extends "base.html" %}
{% load static %}
{% block title %}
Menu
{% endblock title %}
{% block h1_title %}
Menu
{% endblock h1_title %}
{% block body %}
{% if user.is_authenticated %}
<div class="row justify-content-center">
<p>
<div class="col-auto">
<a class="btn btn-success" href="{% url 'ほげ' %}">ほげほげ</a>
</div>
</p>
</div>
<div class="row justify-content-center">
<p>
<div class="col-auto">
<form method="post" action="{% url 'account_logout' %}">
{% csrf_token %}
<button class="btn btn-outline-secondary" type="submit">Logout</button>
</form>
</div>
</p>
</div>
{% else %}
<p class="text-center"><a class="btn btn-outline-success" href="{% url 'account_login' %}">Login</a></p>
{% endif %}
{% endblock body %}



Comments