Django REST frameworkで、Webアプリケーション開発をしましょう。
とは言いつつ、今回は、Djanog REST frameworkは出てきません。先にDjangoの基本的な開発をカスタムユーザーを作りながら体験してみましょう。
<目次>
記事まとめ
プロジェクト作成
次の記事で、開発に必要なディレクトリやファイルを準備してください。
最終的に以下の構成になっていれば、OKです。
1 2 3 4 5 6 7 |
tree . ├── Dockerfile ├── Makefile ├── app ├── docker-compose.yml └── requirements.txt |
準備が整ったら次のコマンドで、ビルドします。
1 |
docker-compose build |
ビルドが完了したらmakeコマンドでDjangoプロジェクトを作成します。
1 |
make django |
このコマンドが正常に完了した場合、appフォルダ内にDjangoプロジェクトが作成されます。
accountアプリケーション
accountアプリケーションには、「カスタムユーザー」を作成します。
カスタムユーザーとは、Djangoの既存のユーザー設定をカスタマイズ版にしたものです(その名の通りですね、すみません)。
セットアップ
まずは、次のコマンドでaccountアプリケーションを作成してください。
1 |
make app app=account |
app/app/settings.pyを開いて、INSTALLED_APPSにaccountを登録します。
こうすることで、Djangoにaccountの存在を認識させます。
1 2 3 4 5 6 7 8 9 10 11 |
INSTALLED_APPS = [ # new 'account', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] |
Modelの設定
機能を実装する前に、最初にテストを書きましょう。
最初にsettings.pyにPostgresの設定しておきます。
1 2 3 4 5 6 7 8 9 |
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'HOST': os.environ.get('DB_HOST'), 'NAME': os.environ.get('DB_NAME'), 'USER': os.environ.get('DB_USER'), 'PASSWORD': os.environ.get('DB_PASS'), } } |
次に、accountフォルダ配下にあるtests.pyを削除し、testsフォルダおよびtest_models.pyを作成してください。
1 2 3 |
rm app/account/tests.py mkdir app/account/tests touch app/account/tests/test_models.py |
テストコードは、機能ごとに分けて書くのが普通です。具体的に「Model」に対してのテストコードをtest_models.pyに記載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# app/account/tests/test_models.py from django.test import TestCase from django.contrib.auth import get_user_model class ModelTests(TestCase): def test_create_user_with_email_successful(self): '''正しいパラメータ(email,password)が設定されているかテストする''' email = 'test@selfnote.work' password = 'selfnote' user = get_user_model().objects.create_user( email=email, password=password) self.assertEqual(user.email, email) self.assertTrue(user.check_password(password)) |
続いて、appフォルダ内にpytestの設定ファイルを作成してください。
1 |
touch app/pytest.ini |
1 2 3 4 5 6 7 8 |
# app/pytest.ini [pytest] DJANGO_SETTINGS_MODULE = app.settings python_classes = *Test python_functions = test_* python_files = tests.py test_*.py *_tests.py norecursedirs = static templates env |
テストコードの実行は、pytestを使います。上記の設定は、pytestで実行するテストファイル、クラス、関数の設定とテスト実行を無視するディレクトリの指定をしています。
テストを実行します。
1 2 3 4 5 6 7 8 |
make test user = get_user_model().objects.create_user( email=email, > password=password) E TypeError: create_user() missing 1 required positional argument: 'username' account/tests/test_models.py:15: TypeError |
TypeErrorになりましたね。デフォルトのDjangoのユーザーは、Emailプロパティを持っていませんので、当然の結果と言えます。
それでは、モデルを実装していきます。
app/account/models.pyを次のように編集してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# app/account/models.py from django.db import models from django.contrib.auth.models import (AbstractBaseUser, BaseUserManager, PermissionsMixin) class UserManager(BaseUserManager): def create_user(self, email, password=None, **extra_fields): '''Create and saves a new user''' print('UserManager Self {}'.format(self)) user = self.model(email=email, **extra_fields) user.set_password(password) user.save(using=self._db) return user class User(AbstractBaseUser, PermissionsMixin): '''Setting Custom user model using email instead of username''' email = models.EmailField(max_length=255, unique=True) name = models.CharField(max_length=255) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) objects = UserManager() USERNAME_FIELD = 'email' |
Djangoのモデルは「model.objects.all()」などで、データベースから値を取得することができます。
このobjectsは、”マネージャー“とも呼ばれており、自分で作成することもできます。
実際に作成したものは、「UserManager」です。
これをuserモデルのobjectsプロパティにセットすることで、「User.objects.create_user()」で、特定の情報を取得できるようになります。
Userモデルは、「AbstractBaseUser」と「PermissionsMixin」を継承したモデルでクラスを作成することで、既存のユーザーモデルをカスタマイズすることができます。
このカスタムユーザーをDjangoに認識させるために、settings.pyの末尾に次の設定を追加します。
1 |
AUTH_USER_MODEL = 'account.User' |
最後にデータベースの設定を適用させましょう。
1 |
make migrate |
もしかしたら次のエラーに遭遇するかもしれません。
1 2 |
django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency account.0001_initial on database 'default'. |
これは、カスタムユーザーを設定したことによる既存のユーザーデータとの競合を指しています。
カスタムユーザー設定前にマイグレーションを実行していた場合、新しく設定したカスタムユーザーのデータと競合を起こしてしまうので、一度データを削除する必要があります。
エラーになってしまった場合は、postgresコンテナに入りましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
docker-compose up # 別のターミナルを立ち上げる make exec cn=db # container id を確認 $ docker container ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7a25ed04848d postgres:10-alpine "docker-entrypoint.s…" 54 minutes ago Up 29 seconds 5432/tcp django-recipe-api_db_1 # postgresコンテナ内に入る $ docker container exec --it 7a25ed04848d bash bash-5.0# psql -U postgres app # appDBを削除 postgres=# drop database app; DROP DATABASE # appDBを作成 postgres=# create database app; CREATE DATABASE # postgresコンテナから抜ける postgres-# \q bash-5.0# exit exit |
マイグレーションを実行しましょう。
1 |
make migrate |
今度は、成功したはずです。
次にテストを実行します。
1 2 3 |
make test ========== 1 passed in 1.26s =========== |
テストがパスしたと思います。
passedの後に、次のような文言が出力されるかもしれません。
1 |
./app/settings.py:93:80: E501 line too long (91 > 79 characters) |
これは、flake8のコードスタイルチェックに引っかかっています。
特に直さなくてもこの後の開発は行えるので、余力のある方はメッセージ内容に従って直してみてください。
Emailの正規化
次は、Emailの正規化のテストを行います。
正規化とは例えば、「test@SELFNOTE.WORK」と入力した場合でも「test@selfnote.work」で認識させるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# app/account/tests/test_models.py from django.test import TestCase from django.contrib.auth import get_user_model class ModelTests(TestCase): def test_create_user_with_email_successful(self): email = 'test@selfnote.work' password = 'selfnote' user = get_user_model().objects.create_user( email=email, password=password) self.assertEqual(user.email, email) self.assertTrue(user.check_password(password)) # new def test_new_user_email_normalized(self): email = 'test@SELFNOTE.WORK' user = get_user_model().objects.create_user(email, 'selfnote') self.assertEqual(user.email, email.lower()) |
テストを実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
make test ==================== FAILURES ========================== _____________ ModelTests.test_new_user_email_normalized ______________ > self.assertEqual(user.email, email.lower()) E AssertionError: 'test@SELFNOTE.WORK' != 'test@selfnote.work' E - test@SELFNOTE.WORK E + test@selfnote.work email = 'test@SELFNOTE.WORK' self = <account.tests.test_models.ModelTests testMethod=test_new_user_email_normalized> user = <User: test@SELFNOTE.WORK> account/tests/test_models.py:23: AssertionError ================================================ 1 failed, 1 passed in 1.55s ================================================ make: *** [test] エラー 1 |
エラーになりましたね。
models.pyのcreate_userを修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
app/account/models.py class UserManager(BaseUserManager): def create_user(self, email, password=None, **extra_fields): '''Create and saves a new user''' print('UserManager Self {}'.format(self)) user = self.model(email=self.normalize_email(email), **extra_fields) # fix user.set_password(password) user.save(using=self._db) return user |
組み込み関数の「normalize_email」を使って、正規化をしました。
テストを実行します。
1 2 3 4 |
make test ============ 2 passed in 1.36s ============ |
Emailテスト
Emailは、必須項目です。設定されていない場合は、ValueErrorを発生するようにしましょう。
テストコードを書きます。
1 2 3 4 5 6 |
app/account/tests/test_models.py # new def test_new_user_invalid_email(self): with self.assertRaises(ValueError): get_user_model().objects.create_user(None, 'selfnote') |
テストを実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
make test ====================== FAILURES ========================== _______________ ModelTests.test_new_user_invalid_email __________________ def test_new_user_invalid_email(self): with self.assertRaises(ValueError): > get_user_model().objects.create_user(None, 'selfnote') E AssertionError: ValueError not raised self = <account.tests.test_models.ModelTests testMethod=test_new_user_invalid_email> account/tests/test_models.py:28: AssertionError ===================== 1 failed, 2 passed in 1.54s ========================== make: *** [test] エラー 1 |
models.pyのcreate_userを修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# app/account/models.py class UserManager(BaseUserManager): def create_user(self, email, password=None, **extra_fields): '''Create and saves a new user''' print('UserManager Self {}'.format(self)) # new if not email: raise ValueError('Users must have an email address') user = self.model(email=self.normalize_email(email), **extra_fields) user.set_password(password) user.save(using=self._db) return user |
テストを実行します。
1 2 3 4 |
make test ============ 3 passed in 1.33s ============== |
管理者ユーザー作成
次は、管理者ユーザーを作成できるようにします。
テストコードを書きましょう。
1 2 3 4 5 6 7 8 9 10 |
# app/account/tests/test_models.py def test_create_new_superuser(self): user = get_user_model().objects.create_superuser( 'test@selfnote.work', 'selfnote') self.assertTrue(user.is_superuser) self.assertTrue(user.is_staff) |
テストを実行しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
make test ======================= FAILURES ====================== _________________ ModelTests.test_create_new_superuser _________ self = <account.tests.test_models.ModelTests testMethod=test_create_new_superuser> def test_create_new_superuser(self): > user = get_user_model().objects.create_superuser( 'test@selfnote.work', 'selfnote') E AttributeError: 'UserManager' object has no attribute 'create_superuser' self = <account.tests.test_models.ModelTests testMethod=test_create_new_superuser> account/tests/test_models.py:31: AttributeError =========== 1 failed, 3 passed in 1.47s ============== |
エラーになりましたね。
app/account/models.pyのUserManagerに次の関数を追加しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# app/account/models.py class UserManager(BaseUserManager): def create_user(self, email, password=None, **extra_fields): ... def create_superuser(self, email, password): """Creates and saves a new super user""" user = self.create_user(email, password) user.is_staff = True user.is_superuser = True user.save(using=self._db) return user |
テストを実行します。
1 2 3 4 |
make test ================= 4 passed in 1.48s ================= |
テストがパスしましたね。
それでは、実際に次のコマンドで管理ユーザーを作成してみましょう。
1 2 3 4 5 6 7 |
make admin Email: test@selfnote.work Password: Password (again): UserManager Self account.User.objects Superuser created successfully. |
管理者が作成できましたね。
次のコマンドで、dockerコンテナを起動しましょう。
1 |
docker-compose up |
「localhost:8000/admin」にアクセスして、管理画面に入れるか確認しましょう。

管理画面テスト
管理画面にカスタムユーザーが表示されていませんね。
Djangoでは、admin.pyにModelを登録することで、表示することができます。
では、表示させてみましょう。
まずは、テストファイルを用意して、テストコードを書きます。
1 |
touch app/account/tests/test_admin.py |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# app/account/tests/test_admin.py from django.test import TestCase, Client from django.contrib.auth import get_user_model from django.urls import reverse class AdminSiteTests(TestCase): def setUp(self): self.client = Client() # Create Admin User self.admin_user = get_user_model().objects.create_superuser( email='admin@selfnote.work', password='selfnote') # Do Login self.client.force_login(self.admin_user) # Create Normal User self.user = get_user_model().objects.create_user( email='test@selfnote.work', password='sefnote', name='SELF NOTE') def test_users_listed(self): # Get user list on Admin Page url = reverse('admin:account_user_changelist') r = self.client.get(url) self.assertContains(r, self.user.name) self.assertContains(r, self.user.email) |
上記のコードは、管理画面にUserモデルの一覧が表示されるかテストします。
公式サイトによると管理画面に表示するChangelistの構文は次のようになっています。
1 |
Changelist {{ app_label }}_{{ model_name }}_changelist |
reverseメソッドは、指定の文字列からURLを逆引きする関数で、「admin:account_user_changelist」と指定すれば、「admin/account/user/」のURLを取得します。
テストを実行してみましょう。
1 2 3 4 5 6 7 8 9 10 11 |
make test ================================== FAILURES =================================== ______________________ AdminSiteTests.test_users_listed _______________________ self = <URLResolver <URLResolver list> (None:None) '^/'> /usr/local/lib/python3.7/site-packages/django/urls/resolvers.py:622: NoReverseMatch ========================= 1 failed, 4 passed in 1.90s ========================= |
「NoReverseMatch」が出力されました。
管理画面にUserモデルを登録していないので、当然です。
app/account/admin.pyを編集しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# app/account/admin.py from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from account import models class UserAdmin(BaseUserAdmin): ordering = ['id'] list_display = ['email', 'name'] admin.site.register(models.User, UserAdmin) |
テストを実行します。
1 2 3 4 |
make test ============ 5 passed in 1.78s =========== |
テストがパスしましたね。
「localhost:8000/admin/account/user/」にアクセスしましょう。

今度は、表示されていますね。
注意)UsersのEmailをクリックするとエラー画面が表示されますが、後ほど修正します


ユーザー編集ページ
次は、ユーザーの編集ページをテストしてみましょう。
1 2 3 4 5 6 7 8 |
# app/account/tests/test_admin.py # new def test_user_change_page(self): url = reverse('admin:account_user_change', args=[self.user.id]) r = self.client.get(url) self.assertEqual(r.status_code, 200) |
テストします。
1 2 3 4 5 6 7 8 9 |
make test ================================== FAILURES =================================== ____________________ AdminSiteTests.test_user_change_page _____________________ /usr/local/lib/python3.7/site-packages/django/forms/models.py:266: FieldError E django.core.exceptions.FieldError: Unknown field(s) (first_name, last_name, username, date_joined) specified for User. Check fields/fieldsets/exclude attributes of class UserAdmin. |
「FieldError」が表示されましたね。
admin.pyを次のように修正しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# app/account/admin.py from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.utils.translation import gettext as _ # new 多言語対応のモジュール from account import models class UserAdmin(BaseUserAdmin): ordering = ['id'] list_display = ['email', 'name'] # new fieldsets = ( (None, {'fields': ('email', 'password')}), (_('Personal Info'), {'fields': ('name',)}), ( _('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser')} ), (_('Important dates'), {'fields': ('last_login',)}), ) admin.site.register(models.User, UserAdmin) |
gettextは、多言語対応用のモジュールです。今回作るアプリケーションには関係ないのですが、Djangoではこのような実装が慣習なので、参考にしてください。
fieldsetsは、画面の表示項目を指定するプロパティです。カスタムユーザーのフィールドを指定しました。
テストを実行してみます。
1 2 3 4 |
make test =========== 6 passed in 2.00s ============ |
パスしましたね。カスタムユーザーを設定したときは、fieldsetsが必須のようですね。
公式サイトにもそのようなことが書かれています。
If you are using a custom ModelAdmin which is a subclass of django.contrib.auth.admin.UserAdmin, then you need to add your custom fields to fieldsets (for fields to be used in editing users) and to add_fieldsets (for fields to be used when creating a user). For example:
公式サイトより
「localhost:8000/admin/account/user/1/change/」を表示させてみましょう。
エラーが回避できていると思います。

ユーザー追加ページ
ユーザー追加ページのテストもしてみましょう。
1 2 3 4 5 6 7 |
# app/account/tests/test_admin.py def test_crate_user_page(self): url = reverse('admin:account_user_add') r = self.client.get(url) self.assertEqual(r.status_code, 200) |
テストを実行します。
1 2 3 4 5 6 7 8 |
make test ================================== FAILURES =================================== _____________________ AdminSiteTests.test_crate_user_page _____________________ usr/local/lib/python3.7/site-packages/django/contrib/admin/options.py:707: FieldError |
先ほどと同じエラーになりましたね。
admin.pyにadd_fieldsetsを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.utils.translation import gettext as _ from account import models class UserAdmin(BaseUserAdmin): ordering = ['id'] list_display = ['email', 'name'] fieldsets = ( (None, {'fields': ('email', 'password')}), (_('Personal Info'), {'fields': ('name',)}), ( _('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser')} ), (_('Important dates'), {'fields': ('last_login',)}), ) # new add_fieldsets = ( (None, { 'classes': ('wide',), 'fields': ('email', 'password1', 'password2'), }), ) admin.site.register(models.User, UserAdmin) |
「add_fieldsets」を設定しました。
テストを実行します。
1 2 3 |
make test ====== 7 passed in 2.38s ======== |
テストがパスしましたね。
「localhost:8000/admin/account/user/add/」にアクセスしてみましょう。

次回
カスタムユーザーの作り方は以上です。
次回は、Djangoのマネジメントコマンドを作成します。
関連記事
基礎編はこちらです。
Djangoおすすめ書籍
Djangoを学ぶなら以下の書籍がオススメです。
緑 -> 赤 -> 紫の順でやればOKです。読みやすい英語で書かれているので、英語力もついでに上がるかもしれません^^
コメントを残す
コメントを投稿するにはログインしてください。