はてなの金次郎

とあるエンジニアの技術系ブログ

【Django】ワンライナーでスーパーユーザーを作成する方法

Django Adminのスーパーユーザーをワンライナーで作成する方法を紹介します。

Django Adminのスーパーユーザーの作成は本来、createsuperuser コマンドで作成できますが、インタラクティブ(対話的)に実行されます。

$ python manage.py createsuperuser
Username (leave blank to use 'root'): admin
Email address: admin@example.com
Password:
Password (again):
Superuser created successfully.

命令的に実行する場合はこれでもいいのですが、宣言的に実行したいケースがあります。

例えば、Docker Composeによる開発環境構築で、環境を立ち上げるたびにスーパーユーザーを作成するのは手間ですしスマートではありません。

環境を立ち上げるときにスーパーユーザーも作成されるのが理想です。

宣言的に実行するケースではパスワードを設定できないのが現在のDjangoの仕様です。パスワードを設定しないままスーパーユーザーを作成することも可能ですが、パスワードを設定するまでログインはできません。

そのためワンライナーで実行できるようにするためには createsuperuser コマンドを --password のようなオプションでパスワードを指定できるようにカスタマイズしてあげる必要があります。

management/commands/ 配下に custom_createsuperuser.py のようなファイルを作成します。

from django.contrib.auth.management.commands import createsuperuser
from django.core.management import CommandError


class Command(createsuperuser.Command):
    help = 'Create a superuser with a password non-interactively'

    def add_arguments(self, parser):
        super(Command, self).add_arguments(parser)
        parser.add_argument(
            '--password', dest='password', default=None,
            help='Specifies the password for the superuser.',
        )

    def handle(self, *args, **options):
        options.setdefault('interactive', False)
        username = options.get('username')
        email = options.get('email')
        password = options.get('password')
        database = options.get('database')

        if not (username and email and password):
            raise CommandError('--username, --email and --password are required options')

        user_data = {
            'username': username
            'email': email,
            'password': password,
        }

        exists = self.UserModel._default_manager.db_manager(database).filter(username=username).exists()
        if not exists:
            self.UserModel._default_manager.db_manager(database).create_superuser(**user_data)

これで以下のようにワンランナーでスーパーユーザーを作成することができます。

$ python manage.py custom_createsuperuser --username admin --email admin@example.com --password admin

ついでにpytestで書いたユニットテストも掲載しておきます。

import pytest

from django.core.management import CommandError, call_command
from django.test.client import Client


@pytest.mark.django_db
@pytest.mark.parametrize('username,email,password', [
    ('admin', 'admin@example.com', 'admin')
])
def test_success_case(username, email, password):
    call_command(
        'createsuperuser_with_password',
        '--username', username,
        '--email', email,
        '--password', password
    )
    client = Client()
    response = client.login(username=username, password=password)
    assert response is True


@pytest.mark.django_db
@pytest.mark.parametrize('username,email', [
    ('admin', 'admin@example.com')
])
def test_CommandError_case(username, email):
    with pytest.raises(CommandError):
        call_command(
            'createsuperuser_with_password',
            '--username', username,
            '--email', email
        )

参考: https://stackoverflow.com/questions/6244382/how-to-automate-createsuperuser-on-django

【Python】loggingのerror()とexception()の違い

loggingのerror()とexception()は同じ ERROR レベルのロギング関数なのですが、どういった違いがあるのでしょうか?

結論はPython公式ドキュメントの「Logging HOWTO」に記載されています。

Logger.exception() は Logger.error() と似たログメッセージを作成します。違いは Logger.exception() がスタックトレースを一緒にダンプすることです。例外ハンドラでだけ使うようにしてください。

docs.python.org

実際にコーディングして確かめてみます。

例外処理をまずはerror()でログ出力してみます。 ZeroDivisionError の例外が投げられるようなコーディングをします。

import logging

logger = logging.getLogger('example')
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

try:
    result = 1 / 0
except ZeroDivisionError as e:
    logger.error(f'{e}')

これの出力は以下のようになりました。

2019-01-24 14:27:39,163 - example - ERROR - division by zero

続いて、exception()です。

try:
    result = 1 / 0
except ZeroDivisionError as e:
    logger.exception(f'{e}')

この出力は以下のようになりました。

2019-01-24 14:27:39,175 - example - ERROR - division by zero
Traceback (most recent call last):
  File "<ipython-input-2-6a7fc51b65bc>", line 2, in <module>
    result = 1 / 0
ZeroDivisionError: division by zero

上記のように、exception() のログにはスタックトレースが出力されているのに対し、error() のログには確認できません。

実際に試してみると非常にシンプルな違いですね。

例外処理のとき、exception() でログを出力するとスタックトレースとともに詳細なログが出力されます。 よって、例外処理のときは exception() を、それ以外のときは error() を使うと良いということでした。

上記のスクリプトGitHubでも公開していますので、よろしければご確認ください。

github.com

f:id:gyuuuutan:20190124145333p:plain
error()の場合

f:id:gyuuuutan:20190124145402p:plain
exception()の場合

GitLab 11.6の新機能「Suggest Changes」が便利なのでオススメ

「Suggest Changes」は一言でいうと、レビューと修正がブラウザで完結しちゃう機能です。具体的には、

  1. レビュワーはソースコードに対して変更の提案ができる
  2. デベロッパーは提案をApplyできる

ということができます。GitHubでも同じような機能がリリースされています。

f:id:gyuuuutan:20190122200050p:plain

使い方は insert suggestion というボタンを押して修正したいソースコードに書き換えるだけです。

f:id:gyuuuutan:20190122200107p:plain

f:id:gyuuuutan:20190122200124p:plain

詳細は以下のドキュメントをご確認ください。

gitlab.ssl.iridge.jp

タイポなどの細かい修正を提案したり複数の修正を提案したりする時に便利ですが、提案したコードでパイプラインが失敗したらかっこ悪いので用法用量にはお気をつけください。

あと、既知のバグかはわかりませんがハイライトのテーマがブラック系(特にMonokai)だと該当コードが見えにくいという罠があるので注意してください。

f:id:gyuuuutan:20190122200544p:plain

f:id:gyuuuutan:20190122200558p:plain

追記:
上記のバグですが、11.8で修正されるみたいです。 id:tnir さんにご指摘いただきました。