はじめに
Djangoで開発を始める時にやっておいたほうが良いこと。
後々になると変更が難しくなるので、必ずではないのですが初期段階のうちに設定しておいた方が良いことがあります。
「settings.pyが入っている設定ディレクトリ名の設定」 と 「拡張ユーザーモデル」 を作っておくことです。
他にも色々あるかもしれませんが、現段階での備忘録として。
この記事の対象者
- Djangoのチュートリアル的な教材を一通り終えたぐらいで、これからアプリ制作を始めようという人
- ディレクトリ構造やプロジェクト、アプリケーションについて基礎知識を整理しておきたい人
- ユーザーモデルをちょろっとカスタマイズしたいという人
(1)プロジェクト制作時に設定ディレクトリの名前を変更しておく
これは、プロジェクトのディレクトリ構造をわかりやすくするための措置です。
普通に$ django-admin startproject myproject
なとしてプロジェクト制作を始めると、ベースディレクトリ名と設定ディレクトリ名が同じ名前(ここではmyproject
)で作られるため区別がつきづらい、という弊害が起こってしまいます。
どういうことかというと、このコマンドでプロジェクト制作を始めると、こんなディレクトリ構造になります。
myproject <- ベースディレクトリ
|-- manage.py
`-- myproject <- 設定ディレクトリ
|-- __init__.py
|-- settings.py
|-- urls.py
|-- manage.py
`-- wsgi.py
プロジェクト名(ここではmyproject
)と同じ名前のベースディレクトリがあり、その中に同名の設定ディレクトリができています。
(ちなみに、学習に使用している現場で使える Django の教科書《基礎編》の書籍の中では、区別がつきやすいよう
- ベースディレクトリ=
django-admin startproject
で作られるプロジェクトのディレクトリ - 設定ディレクトリ=
settings.py
のあるディレクトリ
と呼んでいるので、その呼び方に準じることにします)
で、構造をわかりやすくするために設定ディレクトリの方の名前を変えてやる方法があります。
プロジェクト作成時に、ベースディレクトリを任意の名前(プロジェクト名)で作成した後にベースディレクトリの下へ移動し、第一引数に設定ディレクトリ名(ここではconfig
)、第二引数に.
を指定してstartproject
を実行します。
$ mkdir myproject(ここにプロジェクト名を入れます)
$ cd myproject/
$ django-admin startproject config .
これを実行することで、ディレクトリ構造は
myproject <- ベースディレクトリ
|-- manage.py
`-- config <- 設定ディレクトリ *この名前が変わる
|-- __init__.py
|-- settings.py
|-- urls.py
|-- manage.py
`-- wsgi.py
このようになり、わかりやすい構成になるわけです。
(2)ユーザーモデルを拡張しておく
DjangoにはデフォルトでUserモデルが用意されており、簡単にログイン機能などが実装できるようになっています。
それだけでとても便利なのですが、よりUserモデルを使いやすくするために、カスタマイズしたユーザーモデルを作っておくことが公式でも推奨されているようです。
Userモデルを拡張する主な方法は3種類あって、
1.AbstractBaseUser
を継承する
2.AbstractUser
を継承する
3. 別モデルを作ってOneToOneField
で関連させる
それぞれのやり方で長所短所があるのですが、
開発が進んでデータベースに既にデータがある場合は3のやり方が良さそうです。
アプリ制作を始める最初の段階なら1か2推奨ですが、1は入門〜初心者には難しそうなので、初めは2のやり方で慣れるのが無難かなというところです。
2の方法では、ユーザーモデルに独自のカラムを追加することができます。
カスタムユーザーモデルの作り方は簡単これだけ。
①app/models.py
に記述(app
はアプリケーションディレクトリ名)
②config/settings.py
にAUTH_USER_MODEL
を設定(config
はプロジェクト作成時に作られる設定ディレクトリの名前)
③ マイグレートする
①models.py
に記述`
ここにカスタムユーザーモデルを定義します。(AbstractUser
というモデルにユーザーモデルのベースとなる情報が定義されているので、それを継承して使う形になります)
ここでテーブル名や、追加したいカラムを指定することができます。
例えば、custome_user
というテーブルに外部キーを用いたCalendar
というカラムを設定できます。
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
class Meta(AbstractUser.Meta):
db_table = 'custom_user'
swappable = 'AUTH_USER_MODEL'
calendar = models.ForeignKey(Calendar, verbose_name='アクティブカレンダー', on_delete=models.PROTECT, blank=True, null=True)
これは僕の実際のコードの例なので、ユーザーモデルにCalendar
というモデルを紐づけていますが、例えばユーザーモデルに性別や年齢、メールアドレス、パスワードなどを持たせたいときには、ここでカラム指定してあげると良いかと思います。
デフォルトのUserモデルにカラムを追加する形になるので、もともとあるカラムも残ります(デフォルトのUserモデルの中身はこの記事の最後のMEMO参照)
② settings.py
にAUTH_USER_MODEL
を設定
ここに使用するカスタムユーザーモデルを定義します。「アプリ名.モデル名」のように記述します
AUTH_USER_MODEL = app.CustomUser
③マイグレートする
モデルを変更したら定番のデータベースのマイグレート。
$ Python manage.py makemigrations
$ Python manage.py migrate
Django入門向けのいろいろ
バージョンについて
どのバージョンを指定するかですが、LTS (Long-Term Support)(=長期間のセキュリティサポートがあるバージョンのこと)に該当するバージョンを採用すると間違いがなさそう。
- 2.2 => 2022年4月までサポート
- 1.11 => 2020年4月までサポート
ちなみに2019年12月にDjango3系が出ているのですが、LTSに当たる3.2は2021年の4月にリリース予定とのこと。
特に理由がなければ現時点で最新のLTSで始めておけば間違いがないのかな?という印象です。
(もしデプロイ先が決まっているなら、そのサーバーが対応しているかなどは調査の必要があるのかも)
プロジェクトとアプリケーションについて
Djangoには「プロジェクト」と「アプリケーション」という概念があるので、少し整理しておきます。
プロジェクト
制作するWEBアプリの全体のこと。
$ django-admin startproject myproject
(myproject
がプロジェクト名)のコマンドで作られるやつ
アプリケーション
プロジェクトに属する小さな機能のひとまとまりのこと
python3 manage.py startapp myapp
(myapp
がアプリケーション名)のコマンドで作られるやつ
プロジェクトが大きな箱で、アプリケーションはその箱の中に機能ごとに分けて作られるイメージです。
1つのWEBサービスを作る際に1つのプロジェクトを作り、小さなサービスならその中に1つのアプリケーションを作成して実装していきます。
プロジェクトの規模が大きくなってくると、1つのプロジェクトに複数のアプリケーションを作って運用することもできます。
- アプリケーションは他のアプリケーションとなるべく依存しないように作るとよい
- プロジェクトにアプリケーションを次々と追加する形で機能を追加していくのが基本スタイル
複数のアプリケーションを作る場合は、このように考慮するとメンテナンスがしやすそうですね。
おわりに
一通りチュートリアル的な教材を終えた後は、途中にも紹介させていただきましたが
こちらの本がとてもわかりやすく勉強が捗ります!
基礎的なことが広く網羅されていて、知識の定着や理解のために良いです。つまみ食いしながら読んでもよし。
今回まとめたようなベストプラクティスな手法が多く掲載されていて、かなり重宝しております。
MEMO:デフォルトのUserモデルについて
参考までに、Django 2.1.7で確認したUserモデルのフィールドを記載。
デフォルトのUserモデルはAbstractUserモデルを継承している形になるので、AbstractUserモデルのカラム名が、カスタムユーザーモデルを作った際にも引き継がれます。
class AbstractUser(AbstractBaseUser, PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
Username and password are required. Other fields are optional.
"""
username_validator = UnicodeUsernameValidator()
username = models.CharField(
_('username'),
max_length=150,
unique=True,
help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
validators=[username_validator],
error_messages={
'unique': _("A user with that username already exists."),
},
)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=150, blank=True)
email = models.EmailField(_('email address'), 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 = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
abstract = True
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
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)
class User(AbstractUser):
"""
Users within the Django authentication system are represented by this
model.
Username and password are required. Other fields are optional.
"""
class Meta(AbstractUser.Meta):
swappable = 'AUTH_USER_MODEL'