🎸

Django触るなら知っとけ -Djangoベストプラクティス-

2024/06/30に公開

はじめに

Djangoを多分2.x頃から触り始めて、なんだかんだで今でもよく使っているのでDjango使うならこの辺知っといた方が良いよなぁみたいな基本的なところをまとめます。
また、DRFを利用する方が多いと勝手に思っているためserializers.pyのようなものが一部登場しますがDRFのものとなりますのであまり気にせず。

プロジェクトの作成

Djangoのプロジェクトを作成する際によく以下のようなコマンドで作成するかと思います。

django-admin startproject ${application-name}

上記のようなコマンドで作成すると以下のような構成となり本来は設定周りのファイルが格納されるのにアプリケーション名となり少しややこしくなるため、別途以下のようなコマンドがよく使われます。
config .の「.」を忘れるとconfigというアプリケーション名としてセットアップされるので注意

django-admin startproject config .

設定系

環境毎に分ける

以下のような構成にして環境毎に読み込むファイルを分けてあげると管理しやすいです。

  • base.py: 共通設定
  • development.py: 開発環境用
  • local.py: ローカル用
  • production.py: 本番環境用
  • test.py: テスト環境用(テスト実行時に設定ファイルを指定して変更可能)
config/
|-settings
|    |- __init__.py
|    |- base.py
|    |- development.py
|    |- local.py
|    |- production.py
|    |- test.py
|-...

※デフォルトの設定ファイルより階層が1つ深くなるためBASE_DIRも修正する必要があります

import os
from datetime import timedelta
from pathlib import Path

import environ  # type: ignore [import-untyped]

BASE_DIR = Path(__file__).resolve().parent.parent.parent

以下はlocal.pyを例としますが他のファイルでも同様base.pyを読み込んであげたのちに各環境の設定情報を追記してあげると良いです。

from .base import *

# django debug toolbar
INSTALLED_APPS += ["debug_toolbar"]
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]

また設定ファイルの読み方を切り替える必要があるためmanage.py等でこのようにしてあげると良いです。

#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main() -> None:
    """Run administrative tasks."""
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == "__main__":
    main()

SECRET_KEYについて

DjangoではSECRET_KEYが開発時にそのままハードコーディングされています。本番でもそのまま公開した状態で利用するのはよくないため環境変数等別で管理するようにしましょう。

SECRET_KEY = env("SECRET_KEY")

こちらのSECRET_KEYの生成用のコマンドはDjangoの方でありますので、そちらを利用したセットアップ用のコマンドを作っていたりします。もちろんどこかで管理しても良いのですがローカルで完結させたいところがあったので作成したりしました。

import os

from django.core.management.utils import get_random_secret_key


def generate_env() -> None:
    """
    Generate default env file

    Generate file name is ".env"
    Regenerate django secret key
    """

    # Check exist .evn file
    is_exist_env_file: bool = os.path.exists(".env")
    if is_exist_env_file:
        print('already exists ".env"')
    else:
        secret_key: str = get_random_secret_key()
        writelines: list[str] = [
            "# Basic Setting\n",
            "ALLOWED_HOSTS=*\n",
            "INTERNAL_IPS=127.0.0.1\n",
            f"SECRET_KEY={secret_key}\n",
            "\n",
            "# Database\n",
            "DB_NAME=db\n",
            "DB_USER=admin\n",
            "DB_PASSWORD=xxx\n",
            "DB_HOST=xxx\n",
            "DB_PORT=5432\n",
            "\n",
            "# Email\n",
            "EMAIL_HOST=\n",
            "EMAIL_HOST_USER=\n",
            "EMAIL_HOST_PASSWORD=\n",
            "EMAIL_PORT=\n",
            "DEFAULT_FROM_EMAIL=\n",
            "\n",
        ]
        with open(".env", mode="w", newline="\n") as f:
            f.writelines(writelines)


if __name__ == "__main__":
    generate_env()

Templateファイルについて

TemplatesについてはBASE_DIRから探索するような形で以下のようにすることが多いです。
また、各アプリケーション内にもtemplatesフォルダを作成すると探索対象となるのですが基本的にはアプリケーション内にtemplatesフォルダを作る場合にも構成としてはapplication/templates/application/test.htmlのようにアプリケーション名フォルダを1つ間に挟んで管理するようにしてバッティングしないで探索できるようにしております。

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR, "templates"],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

アプリケーションの作成

Djangoは1プロジェクトの中に複数のアプリケーションを作成して機能を実装していきます。
Djangoを使って開発する上でここをどのように分割するのか、基準はどうするか、などかなり悩むポイントかと思いますが一旦自分はこういう基準で分けますよといったところを軽めにまとめます。
結論、曖昧ですが「依存関係が循環したり、役割が1アプリに集約されすぎないこと」ここだけ意識して分けています。

以下例でアプリ構成は概要で以下となります
サンプルで仮で作成したのですが、何か投稿+アカウント+取引のような形を表現したい場合は中間となるアプリケーション(以下例だとPostManager)のようなものを作成して実装を行なっております。

  • Util: 共通リソースを追加(共通利用するField, Serializer等)
  • Account: アカウント/プロフィール等
  • Corporation: 会社法人情報
  • Authenticator: 認証系(法人/アカウント別で認証機能を提供)
  • Post: (投稿系)
  • PostManager: (投稿したのちの管理)

アプリケーション内のファイル構成

基本と変わらず以下の構成で実装することが多いです。Djangoのロジックをどこに書くのかという課題はよくありますが基本的にModelをFatにすることが多いです。また自分は複数Modelをまたがるようなロジックをservices.pyに関数を作成して他のアプリケーションやカスタムコマンドを作成した際に流用できるようにしたりしております。

application
|- migrations/
|- tests/
|- __init__.py
|- admin.py
|- apps.py
|- models.py
|- serializers.py
|- services.py
|- urls.py
|- views.py
|- __init__.py
|- __init__.py

Test/Lint/Typeの周辺ツール

開発にあたり周辺のテストや静的解析等周辺ツールを入れてたりしますのでこちらも合わせて紹介します。

テスト

pytest-django, pytest-covを利用しています
デフォルトのテストライブラリはunittestベースになるのでpytestを採用しております

フォーマッター

isort, blackを利用しています
blackのみのところも多いかと思いますがisort一旦入れとくと結構良い感じなのでおすすめです

静的解析

flake8, mypyを利用しています
一部型ファイルがないものや、別途インポートの必要があったりしますので注意です(Djangoならdjango-stubs)

タスクランナー

taskipyを利用してタスク登録しています(poetryを利用しており本来タスクランナーとして利用するものではないため)

ER図生成

django-extensionsを利用するとmodelsからER図を生成してくれます。
python manage.py graph_models -a > gen/er.dotのようなコマンドを利用するファイルを生成してくれます。私はこのように生成して画像ファイルのような形(svg/png)へ自動変換して公開して管理するようにしております。

さいごに

一旦基本的なところをまとめました。
ViewやModel、Serializer、Urls、管理画面のカスタマイズや、バリデーション等もう少し踏み込んだところになると少し複雑になったりしますがこの辺もまた余裕があれば記事にまとめます。

Discussion