ホーム >  Python >  Userモデルを使用してユーザ情報変更ページとパスワード変更ページを作る(Django)


Posted:2017/09/08 9:00:00 AM|Category : Python

Userモデルを使用してユーザ情報変更ページとパスワード変更ページを作る(Django)

Vagrantで作ったCentOS7のAnaconda・Django環境に、ユーザ情報変更ページとパスワード変更ページを作成します。

ユーザ情報変更ページとパスワード変更

前回、ユーザ登録ページを作りました。今回は、以下のようなページ構成になります。

前回から引き継ぐページ

トップページ(/index) ドメイン直下のページです。ログイン前はページタイトルのみですが、ログインすると、ヘッダーナビゲーションにようこそ○○さんと表示します。
プロフィール(/profile) ログインているユーザ(自分自身)の情報を表示します。ログイン前の状態の場合、ログインページへ遷移します。
ログイン(/login) ユーザ名とパスワードを入力してログインします。ログインが完了すると、ログイン状態になってトップページへ遷移します。
ログアウト(/logout) ログアウト状態にしてトップページへ遷移します。

今回作るページ

ユーザ登録ページ(/new) ユーザ登録をします。登録が完了すると、ログイン状態にしてトップページへ遷移します。
ユーザ情報変更ページ(/change_data) ユーザ情報を変更します。このページではパスワードを変更できません。ログイン前の状態の場合、ログインページへ遷移します。
パスワード変更ページ(/change_password) パスワード変更ページを変更します。ログイン前の状態の場合、ログインページへ遷移します。

仕様

前回から引き継ぐページ

トップページです。「ドメイン/」にアクセスします。

ログインページです。「ドメイン/login」にアクセスします。

ログインページからログインした場合は「ドメイン/」(トップページ)へ遷移します。

プロフィールページです。「ドメイン/profile」にアクセスします。ログイン中のユーザの情報を表示します。

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

今回作るページ

ユーザ登録ページです。登録後、トップページへリダイレクトします。

ユーザ情報変更ページです。変更後、トップページへリダイレクトします。

パスワード変更ページです。変更後、トップページへリダイレクトします。

準備

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

①Virtualboxをインストールします。Virtualboxのインストールまでで結構です。

VirtualBoxをインストールする for Ubuntu

②Vagrantをインストールします。centos7環境を作りますので、Vagrantのインストールのみ済ませてください。

Vagrantをインストールしてテスト環境を作る for Ubuntu

Vagrantのcentos7環境の詳細を調べる場合は以下のように確認します。

VagrantのCentOS7の環境を確認(lsb_releaseコマンドなど)

③バージョン管理ツールのpyenvインストールします。Anacondaインストールに使います。

VagrantのCentOS7にバージョン管理ツールをインストール(pyenv)

④Anaconda・Djangoインストールします。

Vgrant(CentOS7)にAnaconda+Django+Apache環境を作る(Anaconda・Djangoインストール)

⑤mariadb、mod_wsgiをインストールし、apacheで動作するように設定します。

Vgrant(CentOS7)にAnaconda+Django+Apache環境を作る(MariaDBのインストール・設定)

⑥管理画面(admin)にCSSを適用します。

Vgrant(CentOS7)にAnaconda+Django+Apache環境を作る(管理画面のCSS)

⑦Bootstrapをインストールします。

VagrantのDjangoで作るWebアプリケーション(その6 Bootstrapのインストール)

⑧自作のログインページを作成します。

自作のログインページ(Django)

⑨ユーザ登録ページを作成します。

ユーザ登録(Django)

ホスト環境

OS Ubuntu 16.04.1 LTS 64bit
Virtualbox 5.1
Vagrant 1.9.5

ゲスト環境

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

Vagrantへログイン

①【ホスト環境】端末を開き、前回構築したVagrantのディレクトリまで移動します。

cd vagrant/django_apps/

②【ホスト環境】仮想マシンを起動します。

vagrant up

③【ホスト環境】ログインします。

vagrant ssh

プロジェクトの設定

プロジェクトディレクトリを「pj」、アプリケーションディレクトリを「app1」とします。プロジェクトの設定は、前回と同じです。

アプリケーションの設定

①urls.pyに、URLの設定をします。

vi pj/app1/urls.py

以下のように編集します。上から順に、トップページ、ユーザ情報ページ、ユーザ追加ページ、ログインページ、ログアウト、ユーザ情報変更ページ、パスワード変更ページです。

from django.conf.urls import url
from . import views
from .forms import LoginForm
from django.contrib.auth.views import login,logout

urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^profile/$', views.profile, name='profile'),
    url(r'^new/$', views.new, name='new'),
    url(r'^login/$', login,
        {'template_name': 'login.html', 'authentication_form': LoginForm},
        name='login'),
    url(r'^logout/$', logout,
        {'template_name': 'index.html'},
        name='logout'),
    url(r'^change_data/$', views.change_data, name='change_data'),
    url(r'^change_password/$', views.change_password, name='change_password'),
]

②アプリケーションディレクトリのforms.pyを編集します。

vi pj1/app1/forms.py

以下のように編集します。

from django import forms
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.forms import User
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.contrib.auth.forms import AuthenticationForm

class LoginForm(AuthenticationForm):
    def __init__(self, *args, **kwargs):
       super().__init__(*args, **kwargs)
       self.fields['username'].widget.attrs['class'] = 'form-control'
       self.fields['password'].widget.attrs['class'] = 'form-control'

class UserCreateForm(UserCreationForm):

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

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['username'].widget.attrs['class'] = 'form-control'
        self.fields['email'].widget.attrs['class'] = 'form-control'
        self.fields['password1'].widget.attrs['class'] = 'form-control'
        self.fields['password2'].widget.attrs['class'] = 'form-control'
        self.fields['first_name'].widget.attrs['class'] = 'form-control'
        self.fields['last_name'].widget.attrs['class'] = 'form-control'

    class Meta:
       model = User
       fields = (
           "username", "email", "password1", "password2", "first_name", "last_name",
       )

    def clean_email(self):
        email = self.cleaned_data["email"]
        try:
            validate_email(email)
        except ValidationError:
            raise ValidationError("正しいメールアドレスを指定して下さい。")

        try:
            User.objects.get(email=email)
        except User.DoesNotExist:
            return email
        else:
            raise ValidationError("このメールアドレスは既に使用されています。別のメールアドレスを指定してください")

class UserChangeForm(forms.ModelForm):

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


    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.user = kwargs.get('instance', None)
        self.fields['username'].widget.attrs['class'] = 'form-control'
        self.fields['email'].widget.attrs['class'] = 'form-control'
        self.fields['first_name'].widget.attrs['class'] = 'form-control'
        self.fields['last_name'].widget.attrs['class'] = 'form-control'

    class Meta:
        model = User
        fields = (
            "username", "email", "first_name", "last_name",
        )

    def clean_email(self):
        email = self.cleaned_data["email"]

        try:
            validate_email(email)
        except ValidationError:
            raise ValidationError("正しいメールアドレスを指定してください。")

        try:
            user = User.objects.get(email=email)
        except User.DoesNotExist:
            return email
        else:
            if self.user.email == email:
                return email

            raise ValidationError("このメールアドレスは既に使用されています。別のメールアドレスを指定してください")

class UserPasswordChangeForm(PasswordChangeForm):
    pass

これはユーザ追加フォームです。UserCreationFormを継承し、ユーザ追加のためのフォームを作成します。Userモデルではemail、first_name、last_nameは本来必須ではありませんが、上書きし必須にしています。

class UserCreateForm(UserCreationForm):

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

///省略///

    class Meta:
        model = User
        fields = (
            "username", "email", "password1", "password2", "first_name", "last_name",
        )

clean_emailは、emailの入力チェックを行います。validate_emailは、emailの形式がメールアドレスになっているかを確認します。そして、そのemailがまだ使われていないか重複チェックしています。

    class Meta:
       model = User
       fields = (
           "username", "email", "password1", "password2", "first_name", "last_name",
       )
 
    def clean_email(self):
        email = self.cleaned_data["email"]
        try:
            validate_email(email)
        except ValidationError:
            raise ValidationError("正しいメールアドレスを指定して下さい。")
 
        try:
            User.objects.get(email=email)
        except User.DoesNotExist:
            return email
        else:
            raise ValidationError("このメールアドレスは既に使用されています。別のメールアドレスを指定してください")

ユーザ情報変更用ページです。「UserCreateForm」とほぼ同じですが、ModelFormにしています。「django.contrib.auth.forms」には「UserChangeForm」という便利なものがありますが、今回は使いません。

class UserChangeForm(forms.ModelForm):

このフォームをviews.pyで使用する場合は、ユーザ情報変更ページなので以下のように使います。「instance」には、ログインしているユーザが渡されます。

def change_data(request):

    form = UserChangeForm(request.POST or None, instance=request.user)

また、以下のようにログインしているユーザを保持することができます。viewsと違い、formにはrequest引数が渡されないため、viewsから渡す必要があります。

    def __init__(self, *args, **kwargs):
        self.user = kwargs.get('instance', None)
        super().__init__(*args, **kwargs)

なぜログインユーザ情報を取得するかというと、メールアドレスが重複していないかチェックするロジックで利用するためです。例えば、ユーザ情報変更フォームでユーザ名だけ変更したとします。すると、下記の処理で自分のメールアドレスを取得してしまい、「raise ValidationError」で重複と判断されてしまいます。

        try:
            user = User.objects.get(email=email)

よって、以下のようにメールアドレスは重複なしで、又は自分自身のメールアドレスなら許す、という処理をしています。

        try:
            user = User.objects.get(email=email)
        except User.DoesNotExist:
            return email
        else:
            # もし、自分のメールアドレスならば許可。(メールアドレスを変更しない場合)
            if self.user.email == email:
                return email
 
            # フォームのメールアドレス変更後、そのメールアドレスがすでに使われていれば、重複していると見なす
            raise ValidationError("このメールアドレスは既に使用されています。別のメールアドレスを指定してください")

パスワード変更ページ用のフォームです。django.contrib.auth.formsにPasswordChangeFormを使います。

class UserPasswordChangeForm(PasswordChangeForm):
    pass

③アプリケーションディレクトリのviews.pyを編集します。

vi pj1/app1/views.py

以下のように編集します。

from django.contrib.auth.decorators import login_required
from django.shortcuts import render, get_object_or_404, redirect
from django.views.decorators.http import require_POST
from .forms import (
    UserCreateForm,
    UserChangeForm,
    UserPasswordChangeForm
)

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


@login_required
def profile(request):
   context = {
       'user': request.user,
   }
   return render(request, 'profile.html')


def new(request):

    form = UserCreateForm(request.POST or None)
    if request.method == "POST" and form.is_valid():
        user = form.save(commit=False)
        user.is_staff = True
        user.save()
        return redirect("app1:index")

    context = {
        "form": form,
    }
    return render(request, 'form.html', context)


@login_required
def change_data(request):

    form = UserChangeForm(request.POST or None, instance=request.user)
    if request.method == "POST" and form.is_valid():
        form.save()
        return redirect("app1:index")

    context = {
        "form": form,
    }
    return render(request, 'change_data.html', context)


@login_required
def change_password(request):

    form = UserPasswordChangeForm(request.user, request.POST or None)
    if request.method == "POST" and form.is_valid():
        form.save()
        return redirect("app1:index")

    context = {
        "form": form,
    }
    return render(request, 'change_password.html', context)

ユーザ作成用view関数です。

ifブロックの中には、ユーザ情報を入力してエラーがないときだけユーザが作成されます。その後、トップページへリダイレクトです。エラーがあったり単純なGETアクセス(リンクを踏んできたとき)の場合は、「return render」まで行きます。

def new(request):
 
    form = UserCreateForm(request.POST or None)
    if request.method == "POST" and form.is_valid():
        user = form.save(commit=False)
        user.is_staff = True
        user.save()
        return redirect("app1:index")
 
    context = {
        "form": form,
    }
    return render(request, 'form.html', context)

ユーザ情報変更ページです。

このページは、ログインした場合だけ入れます。「instance=request.user」で、ログイン中のユーザをフォームに紐づけていることに気を付けましょう。

@login_required
def change_data(request):

    form = UserChangeForm(request.POST or None, instance=request.user)
    if request.method == "POST" and form.is_valid():
        form.save()
        return redirect("app1:index")

    context = {
        "form": form,
    }
    return render(request, 'change_data.html', context)

テンプレートの設定

アプリケーションディレクトリ配下のtemplatesディレクトリへhtmlテンプレートを作成します。

index.html、login.html、logout.html、profile.htmlは前回と同じです。

①base.htmlを作成します。base.htmlは、テンプレート共通で使用したいhtmlを記述します。

vi pj1/app1/templates/base.html

以下のように記述します。

{% load staticfiles %}
<!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">
    <!-- js -->
    <script src="{% static 'js/jquery-3.2.1.min.js' %}"></script>
    <script src="{% static 'js/bootstrap.min.js' %}"></script>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.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]-->
    <style>
      body {
        /*padding-top: 70px;*/
      }

      .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 'app1:index' %}">Home</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="{% url 'app1:profile' %}">プロフィール</a>
          </li>
        </ul>
        </ul>
        <ul class="nav navbar-nav ml-auto">
          {% if user.is_authenticated %}
          <li class="nav-item" style="margin-right: 40px;">
            <span class="navbar-text">ようこそ {{  user.get_username }}さん</span>
          </li>
          {% endif %}
          <li class="nav-item">
            <a class="nav-link" href="{% url 'app1:login' %}">ログイン</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="{% url 'app1:logout' %}">ログアウト</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="{% url 'app1:change_data' %}">ユーザ情報変更</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="{% url 'app1:change_password' %}">パスワード変更</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="{% url 'app1:new' %}">ユーザ登録</a>
          </li>
        </ul>
      </div>
    </nav>
    <div class="container" style="margin-top: 60px;">
      {% block content %}
      {% endblock %}
    </div>
    <!-- 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>
    <!--
    <script src="{% static 'js/jquery-3.2.1.min.js' %}"></script>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    -->
  </body>
</html>

②form.htmlを作成します。form.htmlは「/new」(ユーザ登録)で使います。

vi pj1/app1/templates/form.html

以下のように記述します。

{% extends "base.html" %}
{% block content %}
<form class="my-form" action="" method="POST">
  <div class="form-group">
    <label for="id_username">ユーザネーム</label>
    {{ form.username }}
    {{ form.username.errors }}
    <p class="help-block">{{ form.username.help_text }}</p>
  </div>
  <div class="form-group">
    <label for="id_email">Email</label>
    {{ form.email }}
    {{ form.email.errors }}
    <p class="help-block">{{ form.email.help_text }}</p>
  </div>
  <div class="form-group">
    <label class="control-label" for="id_password1">パスワード</label>
    {{ form.password1 }}
    {{ form.password1.errors }}
  </div>
  <div class="form-group">
    <label class="control-label" for="id_password2">パスワード(確認)</label>
      {{ form.password2 }}
      {{ form.password2.errors }}
      <p class="help-block">{{ form.password2.help_text }}</p>
  </div>
  <div class="form-group">
    <label for="id_firstname">FirstName</label>
    {{ form.first_name }}
    {{ form.first_name.errors }}
    <p class="help-block">{{ form.first_name.help_text }}</p>
  </div>
  <div class="form-group">
    <label for="id_lastname">LastName</label>
    {{ form.last_name }}
    {{ form.last_name.errors }}
    <p class="help-block">{{ form.last_name.help_text }}</p>
  </div>
  <div class="form-group">
    <div>
      <input type="submit" class="btn btn-default" value="登録">
    </div>
  </div>
  {% csrf_token %}
</form>
{% endblock %}

③change_data.htmlを作成します。

vi pj1/app1/templates/change_data.html

以下のように記述します。

{% extends "base.html" %}
{% block content %}

<form class="my-form" action="" method="POST">
  <div class="form-group">
    <label for="id_username">ユーザネーム</label>
    {{ form.username }}
    {{ form.username.errors }}
    <p class="help-block">{{ form.username.help_text }}</p>
  </div>

  <div class="form-group">
    <label for="id_email">Email</label>
    {{ form.email }}
    {{ form.email.errors }}
    <p class="help-block">{{ form.email.help_text }}</p>
  </div>

  <div class="form-group">
    <label for="id_firstname">FirstName</label>
    {{ form.first_name }}
    {{ form.first_name.errors }}
    <p class="help-block">{{ form.first_name.help_text }}</p>
  </div>

  <div class="form-group">
    <label for="id_lastname">LastName</label>
    {{ form.last_name }}
    {{ form.last_name.errors }}
    <p class="help-block">{{ form.last_name.help_text }}</p>
  </div>


  <div class="form-group">
    <div>
      <input type="submit" class="btn btn-default" value="登録">
    </div>
  </div>
  {% csrf_token %}
</form>
{% endblock %}

④change_password.htmlを作成します。

vi pj1/app1/templates/change_password.html

以下のように記述します。

{% extends "base.html" %}
{% block content %}
<form action="" method="POST">
{{ form.as_p }}
{% csrf_token %}
<input type="submit" value="送信">
</form>
{% endblock %}

「form.as_p」は、各フィールドとラベルをpタグで囲って出力します。

「{{ form }}」だとテーブル(tableタグは自分で書く必要がある)になります。「.as_table」と同じです。「.as_ul」は、リストとして出力されます。


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

名前:イワサキ ユウタ 職業:システムエンジニア、ウェブマスター 誕生:1986年生まれ 出身:静岡県 特技:ウッドベース 略歴 2008年04月 金融機関系I

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