Django: 再利用可能なAPPの設定をDjangoRestFrameWorkにならって実装

# Django: 再利用可能なAPPの設定をDjangoRestFrameWorkにならって実装

Djangoで開発しはじめて、再利用可能なAPPを作り別のProjectでInstallしたときに、APPで設定しているデフォルトの設定が変更できない.... 
となったのでアプリケーションごとの設定を**project/settings.py**から変更できる方法を探していました。
**Django Rest Framework** にいいコードがあったので、参考にやってみました。

簡単な **Todo Task Api** を例にやってみます。

動くサンプルの[Repositoryはこちらから](https://github.com/Saknowman/django_setting_sample_project/)

## Django Rest Framework　での例

```python:project/settings.py

...

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 10
}

...
```

こんな感じで変更してはりました。
これでPaginationを使ったときに、PAGE_SIZEを変更できたりします。

## Todo Task Api　での例
では実際に簡単なTodo Task Apiでの動作の例を見てみましょう。

**Model**は下記のTaskのみです。

```python:todo_api/models.py

from django.db import models
from .settings import api_settings


class Task(models.Model):
    title = models.CharField(max_length=api_settings.TASK_TITLE_MAX_LENGTH)
    status = models.IntegerField(choices=api_settings.TASK_STATUS_CHOICES)
    
```

**todo_api.settings.api_settings**がAPPの設定値を参照できるインスタンスです。
**api_settings.KEY**で値を取得できます。  
今回の例では下記の二つをsettings.pyで変更できます。
- **title: max_length**
- **status: choices**

例えば**TASK_STATUS_CHOICES**のデフォルトの設定値がtodo_api/settings.pyで下記の通りに設定されているとします。

```python:todo_api/settings.py

DEFAULTS = {
    'TASK_TITLE_MAX_LENGTH': 30,
    'TASK_STATUS_CHOICES': [
        (1, 'TODO'),
        (2, 'DOING'),
        (3, 'DONE'),
    ]
}

...
```

これを変更する場合はproject/settings.pyを変更することで可能になります。

```python:project/settings.py

...

TODO_API = {
    'TASK_STATUS_CHOICES': [
        (1, 'TODO'),
        (2, 'DOING'),
        (3, 'DONE'),
        (4, 'NEW_STATUS'),
    ],
}
```

上記のコードを追加することでタスクのステータスの選択肢に**NEW_STATUSを追加**することができます。  
では実際に詳細なコードを見ていきましょう。

## APISettings クラス
**Django Rest Framework**を参考に**APISettings** クラスをすごく簡略化して作成しました。

```python:utils/api_settings.py

from django.conf import settings


class APISettings:
    """
    A settings object, that allows API settings to be accessed as properties.

    Set default settings in your app settings.py like this:
        from app_utils.setting import APISettings
        api_settings = APISettings('TODO_API', DEFAULTS)

    For example:

        from todo_api.settings import api_settings
        print(api_settings.TASK_STATUS_CHOICES)

    """

    def __init__(self, setting_root_name, defaults):
        self._setting_root_name = setting_root_name
        self._defaults = defaults
        self._user_settings = getattr(settings, self._setting_root_name, {})

    def __getattr__(self, item):
        if item not in self._defaults:
            raise AttributeError("Invalid {} setting: {}".format(self._setting_root_name, item))

        try:
            return self._user_settings[item]
        except KeyError:
            return self._defaults[item]

```

__getattr__メソッドを使用しているのが重要です。  
これにより、api_settings.KEYで設定にアクセスすることができます。  
**self._user_settings**にはproject/settings.pyで設定されている**setting_root_name**以下の値が代入されます。

## APP側の settings.py
では先ほど作成した**APISettings**を使用して**TODO APP用の設定インスタンスの作成**をしましょう。

```python:todo_api/settings.py

from utils.app_setting import APISettings

DEFAULTS = {
    'TASK_TITLE_MAX_LENGTH': 30,
    'TASK_STATUS_CHOICES': [
        (1, 'TODO'),
        (2, 'DOING'),
        (3, 'DONE'),
    ],
}

api_settings = APISettings('TODO_API', DEFAULTS)

```

APISettingsの第一引数に**設定のルート名**を、第二引数に**デフォルトの設定値**を渡して生成します。

これで設定が完了しました。

## 使用する
先ほど作成したTODO_API用の設定を使用するには、Importしてアクセスするだけです。

```python:todo_api/models.py

from .settings import api_settings

print(api_settings.TASK_TITLE_MAX_LENGTH)
# > 30

```


## まとめ
これで、Package化したAPPの設定をProject側のsettings.pyから変更できるようになりました。
流れとしては、
1. APISettingsクラスをコピペ
2. APP設定のルート名を決める
3. APP側のsettings.pyでDefaultを定義
4. APP側のsettings.pyでAPISettingsのインスタンスを作成
5. 使用する

以上です。

動くサンプルの[Repositoryはこちらから](https://github.com/Saknowman/django_setting_sample_project/)

ではでは、よいDjangoライフを！