Djangoで会員登録機能を自作するシリーズ

Python Django シリーズ・まとめ

概要

Djangoで、会員登録機能を自作していきます。メールアドレスをユーザー名として使うようにし、ログイン画面、仮登録、メールクリックで本登録、ユーザー情報変更ページ、パスワード変更ページ、パスワードを忘れた際の再設定...などなど、よくある一連の機能を実装します。動作環境はDjango2.2、Python3.5以上です。

Githubにソースコードを置いているので、ダウンロードしたい方はクローンしてください。

インストールして...

git clone https://github.com/naritotakizawa/django-register-sample
pip install django

すぐに試せます。

python manage.py migrate
python manage.py runserver
python manage.py createsuperuser

Userモデルのカスタマイズ

Djangoに用意されているUserモデルは、メールアドレスは単なるおまけのフィールドです。今回はメールアドレスをユーザー名として使えるようにしたいので、デフォルトのUserモデルをカスタマイズする必要があります。

Djangoで、Userモデルのカスタマイズ(継承)

ログイン画面

Djangoにもログイン画面は付属していますが、自分のウェブサイトのイメージと違う場合は自作することになります。

Djangoでログイン画面を自作する

ユーザー登録

ユーザー登録機能と、仮登録後にメールが届き、それにアクセスさせると本登録する、といった機能を実装します。

Djangoでユーザー作成処理(仮登録後、URLクリックで本登録)

マイページ

ユーザー情報の閲覧、更新ページです。閲覧と更新ページには、自分とスーパーユーザー以外はアクセスできないようにもします。

Djangoで、ユーザー情報閲覧・更新ページの作成

パスワード変更、パスワード忘れ

パスワードの変更ページと、パスワードを忘れた際の再設定ページを作ります。

Djangoで、パスワード変更ページと忘れた際の再設定ページ

メールアドレスの変更

メールアドレスを変更したい場合にも、会員登録時と同じように、確認メールを送ってリンクを踏むと変更されるようにします。

Djangoで、メールアドレス変更ページの作成

Tips

会員登録に関して、幾つかのTipsです。

入力内容の確認画面を作るには

素直に実装していくと、入力画面→確認画面 へどうやって入力データを渡し、確認画面からはどうやって前画面で入力されたデータを送信するのかについてを考えることになります。

色々なアプローチがありますが、Djangoの機能だけで確認画面のページを作りたい場合の例を紹介します。

Djangoで、フォームの内容を保持する(Context編)
Djangoで、フォームの内容を保持する(セッション編)

OneToOneの方が良さそうなケース

Userモデルのカスタマイズは強力ですが、OneToOneで別モデルと紐づけるほうが便利なこともあります。

  • 既に稼働中のDjangoプロジェクトに導入したい場合

    DjangoのUserモデルは、途中から切り替えるのが困難です。そのため、既に稼働中のDjangoプロジェクト内のUserモデルに手を加えたい場合は、OneToOneで拡張するのが無難な方法になります。

  • 他アプリケーションでカスタムUserを定義している場合

    言うまでもなく、Userは1種類しか使えません。
    他アプリケーションの導入を諦めるか、もしくは他アプリケーションのカスタムUserと互換性があり、かつ自分の要求を満たすカスタムUserを新しく定義するという方法になってしまいます。しかしそれが動くかは他アプリケーションの実装次第であり、労力的に見合わないことも多いです。

  • Djangoアプリケーションとして配布したい場合

    既に2つのケースを書きましたが、それらはユーザー側の視点でした。見方を変えると、ユーザー側の視点では稼働中のDjangoプロジェクトに導入できなかったり、互換性のために自前のカスタムUserが肥大化するのは避けたいはずです。

Relation Posts

DjangoでUserモデルのカスタマイズ

Djangoで、会員登録機能を自作するシリーズの1つです。デフォルトのUserモデルでは、usernameというフィールドがありますが、今回はusernameをなくし、emailをメインに扱うUserモデルを作成します。

Python Django

Djangoでログイン画面を作成する

Djangoで、会員登録機能を自作するシリーズの1つです。Djangoにログイン画面は付属していますが、自分のウェブサイトのイメージと違う場合は自作することになります。Bootstrap4を使い、自作していきます。

Python Django Bootstrap4

Djangoで、ユーザー情報閲覧・更新ページ

Djangoで、会員登録機能を自作するシリーズの1つです。ユーザー情報の閲覧、更新ページを作ります。また、閲覧と更新ページには、自分とスーパーユーザー以外はアクセスできないようにもします。

Python Django Bootstrap4

フォームの内容を保持する(テンプレートファイルに変数を渡す方法)

Djangoで、会員登録機能を自作するシリーズの1つです。Djangoで、ユーザー情報確認画面を作ります。ユーザー情報に限らず、フォームオブジェクトを保持しておく必要があったり、ロジック上1つ、2つ先のビューにフォームを渡す場合にも活用できます。

Python Django

Djangoで、フォームの内容を保持する(セッションを使う方法)

Djangoで、会員登録機能を自作するシリーズの1つです。ユーザー情報の入力後に確認画面を表示したいと思います。ユーザー情報が入ったPOSTデータをセッションに保存する方法を使いますが、中々に便利です。

Python Django

Django、Userモデルのカスタマイズ(OneToOne)

Djangoで、会員登録機能を自作するシリーズの1つです。Djangoのユーザーモデルを拡張します。今回はOneToOneでUserにProfile等のモデルを紐づけ、機能を追加していきます。

Python Django

Django、メールアドレス変更ページの作成

Djangoで、会員登録機能を自作するシリーズの1つです。メールアドレスの変更ページを作ります。会員登録時と同じように、アクティベーションメールを使ってメールアドレスを変更できるようにします。

Python Django

Comment

記事にコメントする

名無し

このアプリケーションを実行後、ログイン画面からユーザを登録、送信ボタンを押した後、エラーが発生してしまいました。 私だけでしょうか?

返信する

名無し

私も同様にログイン画面からユーザを登録、送信ボタンを押した後、エラーが発生してしまいました。 requestが定義されていないというエラーが出ましたが、requestは自動で取得されるかと思ったので、不思議でしたなぜなんでしょうか。

返信する

なりと

Djangoでユーザー作成処理(仮登録後、URLクリックで本登録)にミスタイプがありました。

UserCreateビューの'protocol': request.scheme,を、'protocol': self.request.scheme,と修正すれば動くと思います。

大学生

メールアドレスの登録を必須にしたのですが、ログインなどはユーザーネームで行うようにカスタマイズしました。 そうすると、スーパーユーザーを作る時にメールアドレスが必須となってしまい、エラーを吐いてしまいます。 スーパーユーザーを作るために、何か解決法がありましたら、教えていただきたいです。 よろしくお願いします。

返信する

大学生

REQUIRED FIELDにemailを追加し、解決しました。

渡辺

大変有用なサンプルを公開していただきありがとうございます。

Githubのソースで1点。 register/views.py の141行目の内容が欠けています。本サイトのページには存在する以下の行です。

141 success_url = reverse_lazy('register:password_change_done')

原因を見つけるのにsite-packagesの中まで追ってしまい手こずりました(^^;

返信する

なりと

ご連絡ありがとうございます、修正しておきます。

名無し

djangoの初心者で大変参考になりました。

twitterなどのSNS連携の記事を書いていただければ非常にありがたいです。 こちらの記事を参考に、カスタムユーザーを作ったのですが、 twitterとの連携ができずに悩んでいます。

もし可能であればぜひよろしくお願いいたします。

返信する

なりと

時間があれば、ぜひ取り上げたいと思います。

名無し

基本的にはここと同じように進めたのですが、createsuperuserをした後にadminにログインできず、operationalerror at admin/login no such table django_session と出てしまいます。どうすればsuperuserを登録できるのか教えてください。

返信する

なりと

migrateが正しく行われていないと思われます。一度データベースを削除し、再度migrateコマンドを実行してください。

名無し

migrateしなおした後にcreatesuperuserしようとすると、You have 3 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, sessions. Run 'python manage.py migrate' to apply them.と出ます。

なりと

Djangoのバージョンを上げて、もう一度db.sqlite3削除→migrateを行ってください。

なりと

解決しない場合、情報が足りないので使用しているDjangoやPythonのバージョン、どのOSかを教えてください。また、可能であればDjangoプロジェクトを送付してください。

名無し

最新版にアップデートしたところ、治りました。ありがとうございました。

名無し

Userモデルのカスタマイズで示されているカスタムユーザーモデルにusernameとuserIDのフィールドを追加して、 (class AbstractUserにuserID)を加えた状態でmigrationを行うと、以下のようなエラーを吐き出してしまうのですが、 何か解決策等ございませんか。

File "C:\Users\ーーー\AppData\Local\Programs\Python\Python37\lib\site-packages\django\db\models\options.py", line 566, in get_field raise FieldDoesNotExist("%s has no field named '%s'" % (self.object_name, field_name)) django.core.exceptions.FieldDoesNotExist: User has no field named 'username'

ここで質問するような内容でないのかもしれませんが、もしわかればよろしくお願いします。

返信する

なりと

    @property
    def username(self):
            ...

の部分を削除して、もう一度試してください。

名無し

お世話になっております。Djangoの勉強をし始めて、二ヶ月くらいたちますがいつも参考にさせていただいております。 一点、会員登録機能において、メール送信する際(新規登録時、メールアドレス変更時)に添付のエラーが出ます。 BadHeaderError subject.txtの実装も特に問題は無さそうなので何が原因わからず困っているので、解決策を共に考えてくださると幸いです。 何卒よろしくお願い申し上げます。

返信する

名無し

申し訳ございません、添付できておりませんでした。 エラー内容、以下になります。

名無し

BadHeaderError at /hr/register/email/change/ Header values can't contain newlines (got '会員登録まであともう少しです。\n' for header 'Subject')

なりと

can't contain newlines とあります。nelinesは改行を意味します。subject.txtの末尾に改行があるので、削除してください。件名には改行をいれれないのです。

名無し

ありがとうございます。Atomの設定で自動で最終行が改行されるようになっていたみたいです。 設定を変更したら無事通りました。

名無し

有用な記事をありがとうございます。

完成品をcloneし、migrateしたのですが、以下のエラーが出ております。

Traceback (most recent call last):
  File "manage.py", line 15, in <module>
    execute_from_command_line(sys.argv)
  File "C:\Users\ALEX\AppData\Local\Continuum\Anaconda3\envs\py36\lib\site-packages\django\core\management\__init__.py", line 381, in execute_from_command_line
    utility.execute()
  File "C:\Users\ALEX\AppData\Local\Continuum\Anaconda3\envs\py36\lib\site-packages\django\core\management\__init__.py", line 375, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "C:\Users\ALEX\AppData\Local\Continuum\Anaconda3\envs\py36\lib\site-packages\django\core\management\base.py", line 316, in run_from_argv
    self.execute(*args, **cmd_options)
  File "C:\Users\ALEX\AppData\Local\Continuum\Anaconda3\envs\py36\lib\site-packages\django\core\management\base.py", line 353, in execute
    output = self.handle(*args, **options)
  File "C:\Users\ALEX\AppData\Local\Continuum\Anaconda3\envs\py36\lib\site-packages\django\core\management\base.py", line 83, in wrapped
    res = handle_func(*args, **kwargs)
  File "C:\Users\ALEX\AppData\Local\Continuum\Anaconda3\envs\py36\lib\site-packages\django\core\management\commands\migrate.py", line 82, in handle
    executor = MigrationExecutor(connection, self.migration_progress_callback)
  File "C:\Users\ALEX\AppData\Local\Continuum\Anaconda3\envs\py36\lib\site-packages\django\db\migrations\executor.py", line 18, in __init__
    self.loader = MigrationLoader(self.connection)
  File "C:\Users\ALEX\AppData\Local\Continuum\Anaconda3\envs\py36\lib\site-packages\django\db\migrations\loader.py", line 49, in __init__
    self.build_graph()
  File "C:\Users\ALEX\AppData\Local\Continuum\Anaconda3\envs\py36\lib\site-packages\django\db\migrations\loader.py", line 273, in build_graph
    raise exc
  File "C:\Users\ALEX\AppData\Local\Continuum\Anaconda3\envs\py36\lib\site-packages\django\db\migrations\loader.py", line 247, in build_graph
    self.graph.validate_consistency()
  File "C:\Users\ALEX\AppData\Local\Continuum\Anaconda3\envs\py36\lib\site-packages\django\db\migrations\graph.py", line 243, in validate_consistency
    [n.raise_error() for n in self.node_map.values() if isinstance(n, DummyNode)]
  File "C:\Users\ALEX\AppData\Local\Continuum\Anaconda3\envs\py36\lib\site-packages\django\db\migrations\graph.py", line 243, in <listcomp>
    [n.raise_error() for n in self.node_map.values() if isinstance(n, DummyNode)]
  File "C:\Users\ALEX\AppData\Local\Continuum\Anaconda3\envs\py36\lib\site-packages\django\db\migrations\graph.py", line 96, in raise_error
    raise NodeNotFoundError(self.error_message, self.key, origin=self.origin)
django.db.migrations.exceptions.NodeNotFoundError: Migration register.0001_initial dependencies reference nonexistent parent node ('auth', '0011_update_proxy_permissions')

どのように対応すればよいかお教えいただけますと幸いです。

返信する

なりと

django2.2以上で試していただけますか。

名無し

django version 2.1.2 を使用していました。 2.2.22.2にしたところ、問題なく動作できました。 ありがとうございます。

Nana

プログラム初心者です。 Naritoさん、いつも参考にさせていただいております。

自作の掲示板アプリを作成して、そこにこの会員登録機能アプリを組み込みさせていただこうと思っておりますが、 組み込み方がわかりません。

本当の初歩的な質問で申し訳ありませんが、自作アプリに組み込む方法と手順をおしえていただけないでしょうか?

自作のモデルにはすでに以下のUserモデルがあります。 Models.py

import datetime
from django.db import models
from django.utils import timezone


class User(models.Model):

    class Meta:
        db_table = 'user'

    name = models.CharField(verbose_name='名前', max_length=255)
    email = models.EmailField(verbose_name='メール', unique=True)

    def __str__(self):
        return self.name


class Record(models.Model):
    class Meta:
        db_table = 'record'

    user = models.ForeignKey(User, verbose_name='名前', max_length=255, on_delete=models.CASCADE,
                                related_name='records')
    lesson_date = models.DateField(verbose_name='日付', blank=False, default=datetime.date.today)
    content = models.TextField(verbose_name='内容', blank=True)
    created_at = models.DateTimeField(verbose_name='作成日時', default=timezone.now)
    updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)

    def __str__(self):
        return str(self.student)

これを閲覧・記載するときに、会員登録をさせようと思うのですが、どのように変更すればよいでしょうか? それと、views.pyにはどのようにすれば、会員登録アプリのUserを利用できるのでしょうか? いろんなサイトを見て調べたのですが、どうしてもわかりません。 大変基本的な質問で申しわけございません。 どこをどのように修正すれば会員登録アプリを活用できるのか、ご指導よろしくお願いします。

返信する

なりと

やり方はいくつかありますが、GithubにあるこのシリーズのソースコードをクローンやZIPでダウンロードし、その中に掲示板アプリを移してみると如何でしょうか。

その後、掲示板アプリのmodels.pyでは次のようにして、このシリーズのUserモデルを利用します。


import datetime
from django.conf import settings
from django.db import models
from django.utils import timezone

class Record(models.Model):
    class Meta:
        db_table = 'record'

    # ここ
    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='名前', max_length=255, on_delete=models.CASCADE,
                                related_name='records')
    lesson_date = models.DateField(verbose_name='日付', blank=False, default=datetime.date.today)
    content = models.TextField(verbose_name='内容', blank=True)
    created_at = models.DateTimeField(verbose_name='作成日時', default=timezone.now)
    updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)

    def __str__(self):
        return str(self.student)

views.pyで新ユーザーモデルを利用するには、次のようにします。

from django.contrib.auth import get_user_model

User = get_user_model()

# Userを使った様々な処理をする

これを閲覧・記載するときに、会員登録をさせようと思うのですが、どのように変更すればよいでしょうか?

どういった意味でしょうか。

Nana

なりとさん、ありがとうございました。無事に設定することができました。

これを閲覧・記載するときに、会員登録をさせようと思うのですが、どのように変更すればよいでしょうか?

これは大丈夫です。動かすことができて理解することができました。

それと、無事に設定できたのですが、自作のアプリのadmin画面でuserが名前ではなくメールメールアドレスで表示されてしまいます。 admin.pyには

admin.site.register(Record)

とだけ登録してあります。

どのようにすれば、ユーザー名で表示させることができるのでしょうか? 教えていただけませんか。よろしくお願いします。

なりと

Userモデルの、__str__メソッドを上書きしてください。

    def __str__(self):
        return self.first_name  # これはファーストネームを表示する例

Nana

返信ありがとうございます。 何度もすみません。 教えていただいた通りUserモデルを上書きしようとしましたが、 ご指示いただいた通り、Userモデルを削除しており、 settings.AUTH_USER_MODEL を使用しております。

わからなかったので、Recordモデルの

class Record(models.Model): class Meta: db_table = 'record'

student = models.ForeignKey(Student, verbose_name='生徒名', max_length=255, on_delete=models.CASCADE,
                            related_name='records')
lesson_date = models.DateField(verbose_name='レッスン日', blank=False, default=datetime.date.today)
content = models.TextField(verbose_name='レッスン内容', blank=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='担当教師', max_length=255, on_delete=models.CASCADE,
                            related_name='records')
created_at = models.DateTimeField(verbose_name='作成日時', default=timezone.now)
updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)

def __str__(self):
    return str(self.student)

のを最後を

def __str__(self):
    return self.first_name

にしたところ、以下のようなエラーが出てしまいました。

モデルのuserを追加する場所が悪いのでしょうか? 何度も申し訳ありません。 よろしくお願いします。

なりと

registerアプリケーション内に、Userというモデルがあると思うので、その__str__メソッドを変更してください。

Nana

registerの中のmodels.pyを見ましたが、Userモデルの中にdef str(self):メソッドを見つけることができませんでした。 このモデルだと思うのですが、このモデルの何行目にありましょうでしょうか? ちなみに最後の行(95行目)を def username(self): return self.first_name このようにしても、変化はありませんでした。 何度も申し訳ございません。 よろしくお願いします。

class User(AbstractBaseUser, PermissionsMixin):   ←43行目から始まっています """カスタムユーザーモデル

usernameを使わず、emailアドレスをユーザー名として使うようにしています。

"""
email = models.EmailField(_('email address'), unique=True)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=150, blank=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.'
    ),
)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)

objects = UserManager()

EMAIL_FIELD = 'email'
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []

class Meta:
    verbose_name = _('user')
    verbose_name_plural = _('users')

def get_full_name(self):
    """Return the first_name plus the last_name, with a space in
    between."""
    full_name = '%s %s' % (self.first_name, self.last_name)
    return full_name.strip()

def get_short_name(self):
    """Return the short name for the user."""
    return self.first_name

def email_user(self, subject, message, from_email=None, **kwargs):
    """Send an email to this user."""
    send_mail(subject, message, from_email, [self.email], **kwargs)

@property
def username(self):
    return self.email

なりと

__str__メソッドを新しく定義してください。

Nana

ありがとうございます。無事に名前で表示することができました。 何度も丁寧におしえていただきありがとうございました。

名無し

Django初心者です。 貴重なサンプルありがとうございます。 デバッグしながら勉強させてもらいますm(_ _)m

返信する