Django ベスト・プラクティス

最終：2018/10/13


# 参考
* 現場で使える Django の教科書《基礎編》 by 横瀬 明仁　　
https://www.amazon.co.jp/gp/product/B07GK7BWB7

* Djangoでモジュールを複数ファイルで構成する by Fujiwara Ryoma　　
https://qiita.com/RyoMa_0923/items/c4ca5bd070e823403fdf


# メンテナンスしやすいディレクトリ構成
**＜デフォルト、チュートリアルの問題点＞**

* プロジェクト名と設定ディレクトリ名が同一で分かりにくい
* プロジェクト名=メインアプリケーション名が個人的にしっくり

__→ 設定ディレクトリを分かりやすくする__


## 設定ディレクトリを分かりやすくする
``` shell
$ mkdir <projectName>
$ cd <projectName>
$ django-admin.py startproject config . # 第1引数：設定ディレクトリ名 第2引数：Djangoの起点
```
```diff:出来上がるディレクトリ構成
<projectName>/
             |-- manage.py
+            `-- config/
                       |-- __init__.py
                       |-- settings.py
                       |-- urls.py
                       `-- wsgi.py
```
~~※プロジェクト作成後にディレクトリ名を変えると色々動かなくなる~~
以下の変更で、既存のプロジェクトで設定ディレクトリ名を変更しても動作する。

```:settings.py
ROOT_URLCONF = 'config.urls'
```

# まとめる
**＜デフォルト、チュートリアルの問題点＞**

* アプリケーションごとにstaticファイルやテンプレートを配置すると煩雑

__→ staticファイルをベースディレクトリ直下にまとめる__


## staticファイルをベースディレクトリ直下にまとめる
```diff:目的のディレクトリ構成
<projectName>/
             |-- config/
             |         |-- __init__.py
             |         |-- settings.py
             |         |-- urls.py
             |         `-- wsgi.py
+            |-- static/
             |         |-- app1/
             |         |       `-- images/
             |         |                 :
             |         |-- app2/
             |         |        `-- images/
             |         |                  :
             |         |-- js/
             |         |     |-- jquery.*
             |         |     :
             |         |-- css/
             |         |      |-- base.css
             |         :      :
+            |-- templates/
             |            |-- base.html
             |            |-- app1/
             |            |       |-- tmplt.html
             |            |       :
             |            |-- app2/
             |            |       |-- tmplt.html
             |            :       :
             |-- app1/
             |       |-- __init__.py
             |       |-- admin.py
             |       |-- apps.py
             |       |-- models.py
             |       |-- tests.py
             |       |-- views.py
             |       `-- migrations/
             |                      |-- __init__.py
             |                      |-- <migrateファイル>
             |                      :
             |-- app2/
             :       |-- __init__.py
                     |-- admin.py
                     |-- apps.py
                     |-- models.py
                     |-- tests.py
                     |-- views.py
                     `-- migrations/
                                    |-- __init__.py
                                    |-- <migrateファイル>
                                    :
```

上記ディレクトリ構成を用いるには以下の設定が必要。

```diff:settings.py
  BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  PROJECT_NAME = os.path.basename(BASE_DIR)
  :
  TEMPLATES = [
      {
          'BACKEND': 'django.template.backends.django.DjangoTemplates',
+         'DIRS': [os.path.join(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',
               ],
           },
       },
  ]
  :
  STATIC_URL = '/static/'
+ STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
  STATIC_ROOT = '/var/www/{}/static'.format(PROJECT_NAME)
```

# 分ける
**＜デフォルト、チュートリアルの問題点＞**

* 複数モデル、複数ビュー、複数urlがそれぞれ1ファイルにまとめられていて分かりにくい 
* テーブルごとのmodelや機能ごとのビューに分けたい

__→ 適切に分ける__

## model、ビューの分割
```diff:アプリケーションディレクトリの中身
|-- app1/
:       |-- __init__.py
        |-- admin.py
        |-- apps.py
+       |-- models/
        |         |-- __init__.py
        |         |-- model1.py
        |         :
        |-- tests.py
+       |-- views/
        |        |-- __init__.py
        |        |-- view1.py
        |        :       
         `-- migrations/

```

以下の設定が必要。

```python:models/__init__.py
from .models.model1 import Model # model1.pyで定義しているモデルクラス
:
```
```python:views/__init__.py
from .views.view1 from View # view1で定義しているクラスまたはメソッドのビュー
:
```
viewを分割した場合はURLとビューの紐づけ指定もポイント。

```python:config/urls.py
urlpatterns = [
    path('/hoge/', アプリケーション名.views.ファイル名.メソッド名),         # メソッドベースのビュー
    path('/fuga/', アプリケーション名.views.ファイル名.クラス名.asView()), # クラスベースのビュー
    :
]
```

## urls.pyの分割
上記を実行すると、ビューが分割された分urls.pyに記述する項目が多くなり、管理が難しくなる。
　そこで、urls.pyもアプリケーションごとに分散させる。

```diff:目的のディレクトリ構成(一部省略)
<projectName>/
             |-- config/
             |         |-- __init__.py
             |         |-- settings.py
             |         |-- urls.py
             |         `-- wsgi.py
             :
             |-- app1/
             |       |-- __init__.py
             |       |-- admin.py
             |       |-- apps.py
             |       |-- models.py
             |       |-- tests.py
             |       |-- views.py
+            |       |-- urls.py
             |       `-- migrations/
             |-- app2/
             :       |-- __init__.py
                     |-- admin.py
                     |-- apps.py
                     |-- models.py
                     |-- tests.py
                     |-- views.py
+                    |-- urls.py
                     `-- migrations/
```

```diff:config/urls.py
+ from django.urls import include

  urlpatterns = [
      path('admin/', admin.site.urls),
      path('', views.index, name='index),
+     path('app1/', include('app1.urls'),
+     path('app2/', include('app2.urls'),
  ]
```
includeしたapp1/urls.py、app2/urls.pyは以下の感じで記述する。

```python:app1/urls.py
# '/app1/'まではconfig/urls.pyで解析済みなので、それ以降のURLをここで解析する。
from . import views

urlpatterns = [
    path('login/', admin.site.urls),            # '/app1/login/'
    path('logout', views.index, name='index'),  # '/app1/logout/'
    path('register/', views.register, name='register'), # '/app1/register/'
]
```