haku-maiのブログ

インフラエンジニアですが、アプリも作ります。

【Django3.1】Djangoでsignup,login機能をgenericviewとdjango.contrib.authで最短で作成する。あとで見た目も。

f:id:n-guitar:20210203003841p:plain:w800

本記事で行うこと

  • Djangoで作成したアプリケーションにsignup,login機能を実装する。
  • signup,login機能はgenericviewとdjango.contrib.auth.urlsを利用し、できるだけ少ないコード量で実現する。
  • デフォルトだと見た目が良くないので、Bootstrap5で見た目を整える。
  • loginした状態でないと、編集ページはアクセスできない用にLoginRequiredMixinをつかって設定する。
  • サンプルアプリケーションは以下で作成したcodeを利用する。
    n-guitar.hatenablog.com
    なお、すでに上記のアプリケーションでBootstrap5を導入している。

なお本記事のソースコードgithub上に公開しています。
GitHub - n-guitar/django-samples at feature/signup/sample.generic-view

モチベーション

  • Djangoを教える時のサンプルとして利用したいため。
  • 社内で使うアプリケーションを作る時、最低限のユーザ認証を付けたかったため。
  • bootstrap5をつかってlogin,signupページの見た目をトト萌えてみたかったため。

環境

$ sw_vers
ProductName:    macOS
ProductVersion: 11.1
BuildVersion:   20C69
$ python -V
Python 3.8.0

login機能の作成

login機能は公式documentを参考に作成していきます。
Using the Django authentication system | Django documentation | Django

事前準備 管理者用account作成

login確認の為に以下のコマンドで管理者ユーザを作成しておきます。

$ python manage.py createsuperuser
Username (leave blank to use 'XXXXX'): admin
Email address: test@test.com                            
Password: 
Password (again): 
Superuser created successfully.

signup,login実装用のアプリケーション作成

signup,login実装用のアプリケーションを作成しておきます。

$ django-admin startapp accounts

settings.pyにも忘れずにアプリを追加しておきます。
confug/settings.py

# <省略>
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'samplecrud',
    'accounts', # 追加
]
# <省略>

accounts/urls.pyを作成
現時点でaccountディレクトリは以下のようになっている想定です。
f:id:n-guitar:20210117195422p:plain:w200

urlの設定

まずベースとなるconfug/urls.pyからaccounts/urls.pyのrouting設定をしておきます。

confug/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('samplecrud/', include('samplecrud.urls')),
    path('accounts/', include('accounts.urls')),
]

accounts/urls.pyを作成していきます。
まずloginのURLを作成し、routing先はdjango.contrib.authLoginViewを利用します。

accounts/urls.py

from django.urls import path
from django.contrib.auth import views as auth_views

urlpatterns = [
    path('login/', auth_views.LoginView.as_view(), name='login'),
]

このPathでレンダリングされる先はデフォルトでtemplateディレクトリ配下のregistrationに配下に作成したlogin.htmlファイルになります。
path('accounts/login/', auth_views.LoginView.as_view(template_name='myapp/login.html')),とすることで任意の場所に設定することも出来ますが、デフォルトのままで行こうと思います。

テンプレートの作成

デフォルトの通りregistration/login.htmlを以下のように作成します。

templates/registration/login.html

<form action="" method="POST"> {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="login">
</form>

たったこれだけです。
この状態で
http://127.0.0.1:8000/accounts/login/ にアクセスしてみると以下のように表示されます。
f:id:n-guitar:20210117185314p:plain:w400

試しに先程作成した管理者ユーザでloginすると、以下のエラーになります。
f:id:n-guitar:20210117185408p:plain:w600
デフォルトではlogin後は/accounts/profile/が表示されるようになっています。
今回は特にprofileページは必要ないので、 login後に表示されるページを変更してみます。

login後のリダイレクトページ設定

実はsettings.pyに以下の用に記述を追加するだけです。
listはサンプルアプリに設定してあるpath('list/', ListPage.as_view(), name='list'),としておきます。
confug/settings.py

# <上記省略>
LOGIN_REDIRECT_URL = 'list'

※一番下に追加しました。

この状態でもう一度、 http://127.0.0.1:8000/accounts/login/ にアクセスし、loginしてみましょう。
無事login出来ました。
f:id:n-guitar:20210117190255p:plain:w600
しかしながら本当にlogin出来ているのか分かりにくいのでloginユーザを表示してみたいと思います。

login userの表示

{{ user.username }}をtemplateに加えるだけなのですがloginしていない時表示出来ないので、条件分岐を入れます。
{% if user.is_authenticated %}でtrueの時{{ user.username }}を表示し、loginしていないときはloginページのリンク先を表示しておくことにします。

(例)

{% if user.is_authenticated %}
   {{ user.username }}
{% else %}
      <a href="{% url 'login' %}">Login</a></li>
 {% endif %}

loginユーザ名はどのページでも表示させたいので、一旦これをbase.htmlのbodyに入れてみます。

templates/base.html

{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}">
    <script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>

    <title>sample app</title>
</head>
<header>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container-fluid">
          <a class="navbar-brand" href="#">Todo Sample App</a>
          <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
            <div class="navbar-nav">
              <a class="nav-link" href="{% url 'list' %}">List</a>
              <a class="nav-link" href="{% url 'create' %}">Create</a>
            </div>
          </div>
        </div>
    </nav>
</header>
<body>
    {% if user.is_authenticated %}
        {{ user.username }}
    {% else %}
        <a href="{% url 'login' %}">Login</a></li>
    {% endif %}
    {% block Content %}
    {% endblock Content %}
</body>
<footer>
    <div class="fixed-bottom">
        <p class="float-end m-2">create by haku-mai</p>
    </div>
</footer>
</html>

この状態で http://127.0.0.1:8000/samplecrud/list/ にアクセスしてlogin前後を見てみましょう。

login前
f:id:n-guitar:20210117192457p:plain:w600
login後
f:id:n-guitar:20210117192518p:plain:w600

無事表示出来ましたね!
ちょっとかっこ悪いですが、見た目は後の章でなおします。

logout

login出来ましたがlogoutする機能が有りません。
logout機能もdjango.contrib.authに用意されているため以下の用に設定します。

accounts/urls.py

from django.urls import path
from django.contrib.auth import views as auth_views

urlpatterns = [
    path('login/', auth_views.LoginView.as_view(), name='login'),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
]

この状態で、 http://127.0.0.1:8000/accounts/logout/ にアクセスすると以下が表示されます。

f:id:n-guitar:20210117193541p:plain:w600
公式documentとおりだとregistration/logged_out.htmlレンダリングされるはずなのですが、存在していないとadmin siteに飛ばされてしまうようです。
今回はloginした場合は再度loginページに飛ばしてほしいので以下の用に設定します。

confug/settings.py

# <上記省略>
LOGIN_REDIRECT_URL = 'list'
LOGOUT_REDIRECT_URL = 'login' #追加

この状態で、 http://127.0.0.1:8000/accounts/logout/ にアクセスすると無事loginページにリダイレクトされました。
f:id:n-guitar:20210117185314p:plain:w400

signup機能の作成

今の状態ではadmin site以外でユーザを作成するすべがないので、signup機能を実装します。
loginみたく、デフォルトで用意されている機能は機能はないものの、UserCreationFormCreateViewを組み合わせることで簡単に作成することが出来ます。
まずURLの設定

accounts/urls.py

from django.urls import path
from django.contrib.auth import views as auth_views
from .views import SignupPage

urlpatterns = [
    path('login/', auth_views.LoginView.as_view(), name='login'),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
    path('signup/', SignupPage.as_view(), name='signup'),
]

viewの設定

accounts/views.py

from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm

class SignupPage(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('login')
    template_name = 'registration/signup.html'

レンダリング先の設定

templates/registration/signup.html

<form action="" method="POST"> {% csrf_token %}
  {{ form.as_p }}
  <input type="submit" value="signup">
</form>

全くloginと同じでOKです。 この状態で http://127.0.0.1:8000/accounts/signup/ にアクセスすると以下の用に表示されます。
f:id:n-guitar:20210117200904p:plain:w600
簡単ですね。
ちゃんとpasswordの確認まで行ってくれています。
試しにユーザを作成してログインしてみると、ちゃんとログインできていることが分かります。
f:id:n-guitar:20210117201235p:plain
※user1と表示されていますね。

アクセス制御

signup,login機能は実装したものの、今の状態ではすべてのページが表示出来てしまいます。
なので編集機能だけ、ログインしていないと操作出来ないようにしたいと思います。

具体的には、以下のCreatePageUpdatePageDeletePageが編集機能にあたるのでそれを制御します。
samplecrud/views.py

from django.shortcuts import render
from .models import Todo
from django.views import generic
from django.urls import reverse_lazy

class ListPage(generic.ListView):
    model = Todo
    template_name = 'list.html'

class CreatePage(generic.CreateView):
    model = Todo
    template_name = 'create.html'
    fields = ('title','memo')
    success_url = reverse_lazy('list')

class UpdatePage(generic.UpdateView):
    model = Todo
    template_name = 'update.html'
    fields = ('title','memo')
    success_url = reverse_lazy('list')

class DeletePage(generic.DeleteView):
    model = Todo
    template_name = 'delete.html'
    success_url = reverse_lazy('list')

と言っても制御は簡単で、LoginRequiredMixinを利用し、制御したいviewに継承すればいいだけです。

samplecrud/views.py

from django.shortcuts import render
from .models import Todo
from django.views import generic
from django.urls import reverse_lazy
from django.contrib.auth.mixins import LoginRequiredMixin #追加

class ListPage(generic.ListView):
    model = Todo
    template_name = 'list.html'

class CreatePage(LoginRequiredMixin, generic.CreateView): #変更
    model = Todo
    template_name = 'create.html'
    fields = ('title','memo')
    success_url = reverse_lazy('list')

class UpdatePage(LoginRequiredMixin, generic.UpdateView): #変更
    model = Todo
    template_name = 'update.html'
    fields = ('title','memo')
    success_url = reverse_lazy('list')

class DeletePage(LoginRequiredMixin, generic.DeleteView): #変更
    model = Todo
    template_name = 'delete.html'
    success_url = reverse_lazy('list')

この状態で http://127.0.0.1:8000/samplecrud/list/ にアクセスするとアクセスできますが、、、
f:id:n-guitar:20210117202024p:plain:w600

編集ボタンを押すと、login画面にリダイレクトされます。
f:id:n-guitar:20210117202206p:plain:w600
しかも、URLがhttp://127.0.0.1:8000/accounts/login/?next=/samplecrud/update/1になっているのですが
ログインすると、もともとアクセスしたかったページに戻ってくることが出来ます。
f:id:n-guitar:20210117202339p:plain:w600
すごく便利ですよね。

見た目の変更

ユーザ表示の変更

ユーザ表示が少しかっこ悪かったので、navbarに表示させ、ドロップダウンでlogoutを表示させるようにします。

templates/base.html

{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}">
    <script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>

    <title>sample app</title>
</head>
<header>
  <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container-fluid">
      <a class="navbar-brand" href="#">Todo Sample App</a>
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarSupportedContent">
        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
          <li class="nav-item">
            <a class="nav-link" href="{% url 'list' %}">List</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="{% url 'create' %}">Create</a>
          </li>
        </ul>
        {% if user.is_authenticated %}
          <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">{{ user.username }}</a>
          <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
            <li><a class="dropdown-item" href="{% url 'logout' %}">Logout</a></li>
          </ul>
        {% else %}
        <a class="btn btn-outline-success btn-sm" href="{% url 'login' %}">Login</a></li>
        {% endif %}
      </div>
    </div>
  </nav>
</header>

<body>
    {% block Content %}
    {% endblock Content %}
</body>
<footer>
    <div class="fixed-bottom">
        <p class="float-end m-2">create by haku-mai</p>
    </div>
</footer>
</html>

login前
f:id:n-guitar:20210117203206p:plain:w600

login後
f:id:n-guitar:20210117202932p:plain:w600

ドロップダウンでlogoutを表示します。
f:id:n-guitar:20210117203118p:plain:w600

すこしかっこよくなりましたね。

base templateの修正

本筋とは少しずれますが、見た目の変更をしやすくするために、base系のtemplateを変更しておきます。
signup,loginの見た目をいいかんじにしたいのですが、base.htmlを継承すると、navbarまで表示してしまうので、独自で画面を作成できるように編集します。
新しくbase_global.htmlを作成し、以下のように設定します。

templates/base_global.html

{% load static %}

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}">
    <script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>

    {% block HeadContent %}
    {% endblock HeadContent %}

    <title>
        {% block TitleContent %}
        sample app
        {% endblock TitleContent %}
    </title>
</head>

<header>
{% block HeaderContent %}
{% endblock HeaderContent %}
</header>

<body>
{% block BodyContent %}
{% endblock BodyContent %}
</body>

<footer>
{% block FooterContent %}
{% endblock FooterContent %}
</footer>

</html>

それぞれ、title,head,header,body,footerを差し込めるようにし、bootstrapは全体でロード出来るようにしておきます。

そしてbase.htmlを以下のように変更します。

templates/base.html

{% extends 'base_global.html' %}
{% load static %}

{% block HeaderContent %}
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">Todo Sample App</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        <li class="nav-item">
          <a class="nav-link" href="{% url 'list' %}">List</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="{% url 'create' %}">Create</a>
        </li>
      </ul>
      {% if user.is_authenticated %}
        <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">{{ user.username }}</a>
        <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
          <li><a class="dropdown-item" href="{% url 'logout' %}">Logout</a></li>
        </ul>
      {% else %}
      <a class="btn btn-outline-success btn-sm" href="{% url 'login' %}">Login</a></li>
      {% endif %}
    </div>
  </div>
</nav>
{% endblock HeaderContent %}

{% block BodyContent %}
    {% block Content %}
    {% endblock Content %}
{% endblock BodyContent %}

{% block FooterContent %}
<div class="fixed-bottom">
    <p class="float-end m-2">create by haku-mai</p>
</div>
{% endblock FooterContent %}

base_head.htmlから読み込み、header,footerを差し込みます。
{% block BodyContent %}の中に{% block Content %}を元のまま用意しておき、他のtemplateファイルには変更がないようにしておきます。
上記の変更は、今までの見た目には何も変更は有りません。

現時点で以下のフォルダ構成になっているはずです。
f:id:n-guitar:20210117224043p:plain:w200

login画面の修正

以下のリンクを参照するとわかるのですが、{{ form.as_p }}はいろいろな機能を持っているので、これを分解しなければなりません。
【Django3.1】Djangoで作成したアプリケーションにBootstrap5-beta1を導入し、見た目を整える。 - 米が食べたいぜぇ〜 haku-maiのブログ

なのでまず最初に{{ form.as_p }}を展開して記述します。

templates/registration/login.html

{% if form.errors %}
<p>Please enter a correct username and password. Note that both fields may be case-sensitive.</p>
{% endif %}

<form action="" method="POST"> {% csrf_token %}
    <p>
        <label for="{{ form.username.id_for_label }}">{{ form.username.label }}:</label>
        <input type="text" name="{{ form.username.html_name }}" autofocus=""
        autocapitalize="none" autocomplete="{{ form.username.html_name }}" maxlength="150" required="" id="{{ form.username.id_for_label }}">
    </p>
    <p>
        <label for="{{ form.password.id_for_label }}">{{ form.password.label }}:</label>
        <input type="password" name="{{ form.password.html_name }}"
        autocomplete="current-password" required="" id="{{ form.password.id_for_label }}">
    </p>
            {% if form.errors %}
                {% for key,value in form.errors.items %}
                    {{ value }}
                {% endfor %}
            {% endif %}
    <input type="submit" value="login">
</form>

f:id:n-guitar:20210117185314p:plain:w600
同じ画面を表示することができ、問題なくlogin可能です。
{% for item in form %}でform文を作成することも出来るのですが、上記のほうが個人的にわかりやすいので、上記のママとします。
分解できたので、見た目を整えていきます。

templates/registration/login.html

{% extends 'base_global.html' %}

{% block HeadContent %}
<style>
    .login-form {
        width: 340px;
        margin: 100px auto;
        font-size: 15px;
    }
    .form-group {
        margin-bottom: 1rem;
    }
    .form-control, .btn {
        min-height: 38px;
    }
    .btn {
        font-size: 15px;
        font-weight: bold;
    }
    .btn-block {
        display: block;
        width: 100%;
    }
</style>
{% endblock HeadContent %}

{% block BodyContent %}
<div class="login-form">
    <dev class="text-center">
        <form style="max-width:300px;margin:auto" action="" method="POST"> {% csrf_token %}
            <h1 class="h3 mb-3 font-weight-normal">ToDo App</h1>

            <div class="form-group">
                <input class="form-control" type="text" placeholder="Username"
                name="{{ form.username.html_name }}" autofocus="" autocapitalize="none"
                autocomplete="{{ form.username.html_name }}" maxlength="150"
                required="" id="{{ form.username.id_for_label }}">
            </div>

            <div class="form-group">
                <input class="form-control" type="password" placeholder="Password"
                name="{{ form.password.html_name }}" autocomplete="current-password"
                required="" id="{{ form.password.id_for_label }}">
            </div>

            <div class="form-group">
                <button type="submit" class="btn btn-primary btn-block">Login</button>
            </div>
            <p class="text-center"><a href="{% url 'signup' %}">Create an Account</a></p>

            {% if form.errors %}
                {% for key,value in form.errors.items %}
                    {{ value }}
                {% endfor %}
            {% endif %}
        </form>
        <p class="mt-5 mb-3 text-muted">© 2021 haku-mai</p>
    </dev>
</div>
{% endblock BodyContent %}

labelは不要なので消し、class="login-form"class="form-group"でくくることで見た目を整えます。
細かい部分はstyleで修正すると少しはイケてる感じになりました。

f:id:n-guitar:20210202213423p:plain:w600

signup画面の修正

login画面の同様、{{ form.as_p }}を一度分解してみます。

templates/registration/signup.html

<form action="" method="POST"> {% csrf_token %}
  <p>
    <label for="{{ form.username.id_for_label }}">Username:</label>
    <input type="text" name="{{ form.username.html_name }}" maxlength="150"
    autocapitalize="none" autocomplete="{{ form.username.html_name }}"
    autofocus="" required="" id="{{ form.username.id_for_label }}">
    <span class="helptext">Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.</span>
  </p>
  <p>
    <label for="{{ form.password1.id_for_label }}">Password:</label>
    <input type="password" name="{{ form.password1.html_name }}"
    autocomplete="new-password" required="" id="{{ form.password1.id_for_label }}">
    <span class="helptext"></span>
  </p>
  <ul>
    <li>Your password can’t be too similar to your other personal information.</li>
    <li>Your password must contain at least 8 characters.</li>
    <li>Your password can’t be a commonly used password.</li>
    <li>Your password can’t be entirely numeric.</li>
  </ul>
  <p></p>
  <p>
    <label for="{{ form.password2.id_for_label }}">Password confirmation:</label>
    <input type="password" name="{{ form.password2.html_name }}"
    autocomplete="new-password" required="" id="{{ form.password2.id_for_label }}">
    <span class="helptext">Enter the same password as before, for verification.</span>
  </p>
  {% if form.errors %}
      {% for key,value in form.errors.items %}
          {{ value }}
      {% endfor %}
  {% endif %}
  <input type="submit" value="signup">
</form>

f:id:n-guitar:20210202220335p:plain:w600
同じ画面を表示することができ、問題なくsignup可能です。
分解できたので、見た目を整えていきます。

templates/registration/signup.html

{% extends 'base_global.html' %}

{% block HeadContent %}
<style>
    .signup-form {
        width: 340px;
        margin: 100px auto;
        font-size: 15px;
    }
    .form-group {
        margin-bottom: 1rem;
    }
    .form-control, .btn {
        min-height: 38px;
    }
    .btn {
        font-size: 15px;
        font-weight: bold;
    }
    .btn-block {
        display: block;
        width: 100%;
    }
</style>
{% endblock HeadContent %}

{% block BodyContent %}
<div class="signup-form">
  <form action="" method="POST"> {% csrf_token %}
    <h1 class="h3 mb-3 font-weight-normal text-center">Welcome ToDo App</h1>
    <p class="text-center">Please fill in this form to create an account!</p>

    <div class="form-group">
      <input class="form-control" type="text" placeholder="Username" name="{{ form.username.html_name }}" maxlength="150"
      autocapitalize="none" autocomplete="{{ form.username.html_name }}"
      autofocus="" required="" id="{{ form.username.id_for_label }}">
    </div>

    <div class="form-group">
      <input class="form-control" type="password" placeholder="password" name="{{ form.password1.html_name }}"
      autocomplete="new-password" required="" id="{{ form.password1.id_for_label }}">
    </div>

    <div class="form-group">
      <input class="form-control" type="password" placeholder="Password confirmation" name="{{ form.password2.html_name }}"
      autocomplete="new-password" required="" id="{{ form.password2.id_for_label }}">
    </div>

    <div class="form-group">
      <button type="submit" class="btn btn-success btn-block">Signup</button>
    </div>

    {% if form.errors %}
        {% for key,value in form.errors.items %}
            {{ value }}
        {% endfor %}
    {% endif %}

    <p class="text-center">Already have an account?  <a href="{% url 'login' %}">login</a></p>
  </form>
  <p class="text-center mt-5 mb-3 text-muted">© 2021 haku-mai</p>
</div>
{% endblock BodyContent %}

ちょっとはイケてる感じになりましたね。
f:id:n-guitar:20210203003224p:plain:w600

以上、Djangoでsignup,login機能をgenericviewとdjango.contrib.auth.urlsで最短で作成する。あとで見た目も。でした。