ホーム >  Python > Django >  Djangoのログイン・ユーザ登録(カスタムユーザモデル)

投稿日:   |  最終更新日:

Djangoのログイン・ユーザ登録(カスタムユーザモデル)

DjangoPython

Djangoが標準で持つUserモデルを継承してカスタマイズします。

ユーザ登録ページ

前回、Userモデルをカスタマイズし、以下のような変更を加えました。

  • idの代わりにuuidを使います。
  • usernameの代わりにemailを使います。
  • nick_nameを追加します。
  • emailを必須かつユニークに変更します。
  • usernameは消します。
  • first_nameとlast_nameを消しします。

通常のユーザモデルでは、ログイン・ユーザ登録にusernameを使いましたが、これを削除しました。usernameの代わりにemailをユーザ名として利用します。今回は、この改造したユーザモデルを使ったユーザ登録ページを作ります。

URL設計

トップページ(/index) ドメイン直下のページです。ログイン前はページタイトルのみですが、ログインすると、ヘッダーナビゲーションにログアウトのリンクを表示します。
ログイン(/login) ユーザ名とパスワードを入力してログインします。ログインが完了すると、ログイン状態になってトップページへ遷移します。
ログアウト(/logout) ログアウト状態にしてトップページへ遷移します。
ユーザ登録(/regist) ユーザ登録をします。登録が完了すると、ログイン状態にしてトップページへ遷移します。

CSS設計

bootstrap4を使います。css、jsファイルの設置場所はプロジェクトディレクトリ直下のstaticディレクトリです。

  • pj1/
  • pj1/
  • static/
  • css/
  • js/
  • users/
  • manage.py

settings.pyに、静的ファイルの設置場所を設定します。

STATICFILES_DIRS = (
    os.path.join(BASE_DIR, "static"),
)

STATIC_URL = '/static/'

仕様

①トップページです。「ドメイン/」にアクセスします。画面右上にログインページへのリンクがあります。

ログインページです。「ドメイン/login」にアクセスします。新規登録ページへのリンクもあります。

ログインページからログインした場合は「ドメイン/」(トップページ)へ遷移します。画面左上の”ログイン”が”ログアウト”になります。

新規登録ページです。「ドメイン/regist」にアクセスします。

ログアウトを押すと、ログアウト処理後トップページへリダイレクトします。

準備

Vagrantでゲスト環境(仮想環境)を作ります。

①Userモデルをカスタマイズしています。

DjangoのUserモデルカスタマイズ(UUIDを使う)

環境

OS CentOS 7.1.1503
pyenv 1.1.3-5-g7dae197
Anaconda 3-4.3.0
MariaDB 5.5.52-1.el7
Apache 2.4.6
mod_wsgi 4.5.14
Django 1.11.3

ユーザ登録概要

Userモデルの継承してカスタマイズします。usernameの代わりにemailを使うようにしています。

①以下のようなDjangoアプリ構成を作ります。プロジェクト名が「pj1」で、アプリケーション名が「users」です。プロジェクトの設定は済ませたものとします。赤字のファイルは、今回変更する部分です。

  • pj1/
  • pj1/
  • __init__.py
  • settings.py
  • urls.py
  • wsgi.py
  • users/
  • __init__.py
  • admin.py
  • apps.py
  • urls.py
  • views.py
  • models.py
  • templates/
  • base.html
  • index.html
  • login.html
  • regist.html
  • manage.py

models.py

①usersディレクトリ配下のmodels.pyを編集します。内容は前回と同じです。

DjangoのUserモデルカスタマイズ(UUIDを使う)

urls.py

①pj1ディレクトリのurls.pyを以下のように編集します。

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url('admin/', admin.site.urls),
    url(r'^', include('users.urls', namespace = 'users'))
]

②usersディレクトリのurls.pyを以下のように編集します。

from django.conf.urls import include, url
from django.contrib.auth import views as auth_views
from . import views
 
app_name = 'users'
 
urlpatterns = [
    url(r'^$', views.index, name='index'),

    # ログイン、ログアウト
    url(r'^login/$', views.login, name='login'),
    url(r'^logout/$', views.logout, name='logout'),
    url(r'^regist/$', views.regist, name='regist'),
    url(r'^regist_save/$', views.regist_save, name='regist_save'),
]

forms.py

①usersディレクトリのforms.pyを以下のように編集します。

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.forms import AuthenticationForm
from users.models import  User
#from django.contrib.auth import get_user_model User = get_user_model() 
from django.contrib.auth.forms import PasswordChangeForm

class LoginForm(AuthenticationForm):

  def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)

    self.fields['username'].widget.attrs['class'] = 'form-control'
    self.fields['username'].widget.attrs['placeholder'] = 'メールアドレス'
 
    self.fields['password'].widget.attrs['class'] = 'form-control'
    self.fields['password'].widget.attrs['placeholder'] = 'パスワード'


class RegisterForm(UserCreationForm):

  # 入力を必須にするため、required=Trueで上書き
  email = forms.EmailField(required=True)
  nick_name = forms.CharField(required=True)

  class Meta:
    model = User

    fields = (
      "email", "password1", "password2", 
      "nick_name",
    )

  def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)

    self.fields['nick_name'].widget.attrs['class'] = 'form-control'
    self.fields['nick_name'].widget.attrs['placeholder'] = 'お名前'

    self.fields['email'].widget.attrs['class'] = 'form-control'
    self.fields['email'].widget.attrs['placeholder'] = 'メールアドレス'

    self.fields['password1'].widget.attrs['class'] = 'form-control'
    self.fields['password1'].widget.attrs['placeholder'] = 'パスワード'
 
    self.fields['password2'].widget.attrs['class'] = 'form-control'
    self.fields['password2'].widget.attrs['placeholder'] = 'パスワード(確認)'

解説

from users.models import  User 

models.pyで定義したカスタムUserモデルをインポートします。

from django.contrib.auth import get_user_model User = get_user_model() 

Djangoの定義済みを使用する場合は、上記のようにインポートします。

Manager isn't available; 'auth.User' has been swapped for 'users.User'

カスタムユーザモデルを使用する場合、「from django.contrib.auth.models import User」をインポートしてユーザ登録すると、上記のようなエラーが表示されてしまいます。

  # 入力を必須にするため、required=Trueで上書き
  email = forms.EmailField(required=True)
  nick_name = forms.CharField(required=True)

  class Meta:
    model = User

    fields = (
      "email", "password1", "password2", 
      "nick_name",
    )

もともとUserCreationFormでは、MetaのmodelにUserを指定しています。また、「fields = (“username”,)」のように不要なフィールドが指定がされています。今回は、設定しなおし「email、nick_name、password1、password2」も使用するようにしています。更に、「email = forms.EmailField(required=True)」で必須項目にしました。

views.py

①usersディレクトリのviews.pyを以下のように編集します。

from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_POST
from .forms import (
  LoginForm,
  RegisterForm,
  UserPasswordChangeForm
)
from django.contrib.auth.models import User
from django.urls import reverse_lazy
from django.contrib.auth import views as auth_views


def index(request):
  context = {
    'users':request.user,
  }
  return render(request, 'index.html', context)


def login(request):
    context = {
        'template_name': 'login.html',
        'authentication_form': LoginForm
    }
    return auth_views.login(request, **context)
 
 
def logout(request):
    context = {
        'template_name': 'index.html',
    }
    return auth_views.logout(request, **context)


def regist(request):
  form = RegisterForm(request.POST or None)
  context = {
    'form':form,
  }
  return render(request, 'regist.html', context)


@require_POST
def regist_save(request):
    form = RegisterForm(request.POST)
    if form.is_valid():
        form.save()
        return redirect('users:index')
 
    context = {
        'form': form,
    }
    return render(request, 'regist.html', context)

解説

@login_required

デコレータです。「ログインしたユーザのみ見せる」という意味です。ログインしていない場合は、ログインページへリダイレクトします。

def regist(request):
  form = RegisterForm(request.POST or None)
  context = {
    'form':form,
  }
  return render(request, 'regist.html', context)

ユーザ新規登録画面を表示します。

@require_POST
def regist_save(request):
    form = RegisterForm(request.POST)
    if form.is_valid():
        form.save()
        return redirect('users:index')
 
    context = {
        'form': form,
    }
    return render(request, 'regist.html', context)

「RegisterForm」を使って、ユーザ登録画面に入力した情報を保存します。登録できた場合は「index」ページへリダイレクトします。

テンプレート

{% load static %}
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="shortcut icon" href="{% static 'main/img/favicon.ico' %}">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <title>ユーザ登録</title>
 
    <!-- Bootstrap -->
    <link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
    <link href="{% static 'css/bootstrap-reboot.min.css' %}" rel="stylesheet">
    <link href="{% static 'css/bootstrap-grid.min.css' %}" rel="stylesheet">
    <script src="{% static 'js/jquery-3.2.1.min.js' %}"></script>
    <script src="{% static 'js/bootstrap.min.js' %}"></script>
    <style>
      .my-form {
        width: 640px;
        margin: 0 auto;
      }
 
      @media screen and (max-width: 768px) {
        .my-form {
          width: 100%;
        }
      }
 
      .errorlist li {
        list-style-type: none;
      }
 
      .errorlist {
        color: red;
        margin-left: 0;
        padding-left: 0;
      }
    </style>
  </head>
 
  <body>
    <nav class="navbar navbar-toggleable-md navbar-inverse bg-inverse">
      <button class="navbar-toggler navbar-toggler-right"
      type="button" data-toggle="collapse" data-target="#navbarNav"
      aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
      </button>
      <a class="navbar-brand" href="#">App1</a>
      <div class="collapse navbar-collapse" id="navbarNav">
        <ul class="navbar-nav">
          <li class="nav-item active">
            <a class="nav-link" href="{% url 'users:index' %}">Home</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="{% url 'users:profile' %}"">プロフィール</a>
          </li>
        </ul>
        </ul>
        <ul class="nav navbar-nav ml-auto">
          <li class="nav-item">
          {% if user.is_authenticated %}
          <a class="nav-link" href="{% url 'users:logout' %}">ログアウト</a>
          {% else %}
          <a class="nav-link" href="{% url 'users:login' %}">ログイン</a>
          {% endif %}
          </li>
        </ul>
      </div>
    </nav>
      {% block content %}
      {% endblock %}
    <!-- Bootstrap -->
    <link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
    <link href="{% static 'css/bootstrap-reboot.min.css' %}" rel="stylesheet">
    <link href="{% static 'css/bootstrap-grid.min.css' %}" rel="stylesheet">
    <!-- js -->
    <script src="{% static 'js/bootstrap.min.js' %}"></script>
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </body>
</html>

「Base.html」は、すべてのページに表示するウィジェット(ページヘッダー)を定義します。

{% extends "base.html" %}
{% block content %}
<div class="container-fluid" style="margin-top: 60px;">
  <div class="row">
    <div class="col-md-6 offset-md-3">
      <p>インデックスです</p>
    </div><!-- end col-md-6 offset-md-3 -->
  </div><!-- end row -->
</div><!-- end container-fluid-->
{% endblock %}

「index.html」は、トップページの表示です。

{% extends 'base.html' %}
{% block content %}
<div class="container-fluid" style="margin-top: 60px;">
  <div class="row">
    <div class="col-md-6 offset-md-3">
      <div class="card">
        <div class="card-header"> ログイン</div>
        <div class="card-body">
          <div class="card-block"> 
            <form class="my-form" action="{% url 'users:login' %}" method="POST">
            {% csrf_token %}
              <div class="row">
                <div class="form-group col-md-11">
                  <label for="id_email">メールアドレス</label>
                  {{ form.username }}
                  {{ form.username.errors }}
                  <p class="help-block mb-0">{{ form.username.help_text }}</p>
                </div>
                <div class="form-group col-md-11 pb-3">
                  <label for="id_email">パスワード</label>
                  {{ form.password }}
                  {{ form.password.errors }}
                </div>
                <div class="form-group col-md-11">
                  <input type='hidden' name='next' value='{{ next }}' />
                  <button type="submit" class="btn btn-block btn-outline-primary"> ログイン</button>
                </div>
              </div>
            </form>
            <div class="card-text">
              <p class="text-right mb-0"><a class="nav-link" href="#">パスワードを忘れた方</a></p>
              <p class="text-right mb-0"><a class="nav-link" href="{% url 'users:regist' %}">新規登録会員</a></p>
            </div><!-- end card-text -->
          </div>
        </div><!-- end card-body-->
      </div><!-- end card-->
    </div>
  </div><!-- end container-fluid -->
</div><!-- end container-fluid-->
{% endblock %}

「login.html」は、ログインページの表示です。

{% extends "base.html" %}
{% block content %}
<div class="container-fluid" style="margin-top: 60px;">
  <div class="row">
    <div class="col-md-6 offset-md-3">
      <div class="card">
        <div class="card-header">会員登録用URL送信</div>
          <div class="card-body">
            <div class="card-block">
              <form class="my-form" action="" method="POST">
              {% csrf_token %}
                <div class="row">
                  <div class="form-group col-md-11">
                    <label for="id_nick_name">お名前</label>
                    {{ form.nick_name }}
                    {{ form.nick_name.errors }}
                  </div>
                  <div class="form-group col-md-11 my-0">
                    <label for="id_email">メールアドレス</label>
                    {{ form.email }}
                    {{ form.email.errors }}
                    <p class="help-block">{{ form.email.help_text }}</p>
                  </div>
                  <div class="form-group col-md-11">
                    <label for="id_password1">パスワード</label>
                    {{ form.password1 }}
                    {{ form.password1.errors }}
                  </div>
                  <div class="form-group col-md-11">
                    <label for="id_password2">パスワード(確認用)</label>
                    {{ form.password2 }}
                    {{ form.password2.errors }}
                    <p class="help-block">{{ form.password2.help_text }}</p>
                  </div>
                  <div class="form-group col-md-11">
                    <input type='hidden' name='next' value='{{ next }}' />
                    <button type="submit" class="btn btn-block btn-outline-primary">メールを送信する</button>
                  </div>
                </div>
              </form>
            </div><!-- end card-block -->
          </div><!-- end card-body -->
      </div><!-- end card -->
    </div><!-- end col-md-6 offset-md-3 -->
  </div><!-- end row -->
</div><!-- end container-fluid -->
{% endblock %}

「regist.html」は、新規登録ページの表示です。

トラックバック用のURL
プロフィール

名前:イワサキ ユウタ 職業:システムエンジニア、ウェブマスター、フロントエンドエンジニア 誕生:1986年生まれ 出身:静岡県 特技:ウッドベース 略歴 20

最近の投稿
人気記事
カテゴリー
広告