Django templateでネストされた辞書変数に動的に(keyを変数で)指定する方法

# 1.この記事の内容

Django templateからネストされた辞書変数に対してkeyを動的に変えながらアクセスする方法を紹介します．
実装例全体は，[筆者のGitHub](https://github.com/ryoma-jp/samples/tree/master/web_app/05_how_to_access_to_nested_dict_objects)で公開していますので，詳細はこちらをご確認ください．

# 2.背景

Django templateから辞書変数にアクセスするには下記のように``. (ドット)``指定することでアクセスできます．

```python:python
dict_data = {
    'key1': 0,
    'key2': 1,
}
```

```html:template
{{ dict_data.key1 }}
```

アクセスするキーが決まっているならこれでよいのですが，キー名(key1, key2)が決まっていない場合に，単純に変数を``. (ドット)``で繋ぐことはできません．

```python:python
dict_data = {
    'key1': 0,
    'key2': 1,
}
dict_key = 'key1'
```

```html:template
{{ dict_data.dict_key }} ← このような指定はできない(Key Error)
```

この問題を，ネストされた辞書変数に対しても解決できる実装例を紹介します．

# 3.対策例

## 3-1.単純な辞書変数

カスタムフィルタを使って，値を取得することができます．
こちらは実装方法がシンプルなので，記事本文のみに例を記載します．

```python:custom_filter
@register.filter
def get_value(value, key):
    if (key in value.keys()):
        return value[key]
    else:
        return None
```

```python:application
dict_data = {
    'key1': 0,
    'key2': 1,
}
dict_key = 'key1'
```

```html:template
{% load custom_filter %}

{{ dict_data|get_value:dict_key }}
```


## 3-2.ネストされた辞書変数

ネストされた辞書変数にアクセスするには複数のキーをカスタムフィルタに渡す必要がありますが，カスタムフィルタは引数を1つだけしか扱えず，少し工夫が必要です．

実装のポイントは2点，

1. 複数のキーを区切り文字を含めた一つの文字列を作り，カスタムフィルタの引数とする
1. カスタムフィルタの処理内で，区切り文字でキーを分解する

です．

以下，実装例ですが，少し長いので折りたたんでます．

<details><summary>カスタムフィルタの実装例(ここをクリックで展開)</summary><div>

```python:custom_filter
@register.filter
def dict_value(value, args):
    """ Get Dict Value from Nested Dict Object
    
    Get value from nested dictionary object.
    
    Args:
        value: object
        args (string): specify the strings comma separated
    """
    
    if ((args is not None) and (args != '')):
        arg_list = [arg.strip() for arg in args.split(',')]
        
        keys = None
        _value = value
        for key in arg_list[:-1]:
            keys = _value[key].keys()
            _value = _value[key]
        
        return _value[arg_list[-1]]
    else:
        return None
```
</div></details>


<details><summary>ネストされた辞書変数の実装例(ここをクリックで展開)</summary><div>

- 記事での説明用に，GitHub上のコードから少し変えています

```python:application
def implementations(request):

    nested_dict = {
        'key1': {
            'key1-1': {
                'val_A': '11A',
                'val_B': '11B',
                'val_C': '11C',
                'val_D': '11D',
                'val_E': '11E',
            },
            'key1-2': {
                'val_A': '12A',
                'val_B': '12B',
                'val_C': '12C',
            },
            'key1-3': {
                'val_A': '13A',
                'val_B': '13B',
                'val_C': '13C',
                'val_D': '13D',
            },
            'key1-4': {
                'val_A': '14A',
                'val_B': '14B',
            },
        },
        'key2': {
            'key2-1': {
                'val_A': '21A',
                'val_B': '21B',
            },
            'key2-2': {
                'val_A': '22A',
                'val_B': '22B',
                'val_C': '22C',
                'val_D': '22D',
            },
            'key2-3': {
                'val_A': '23A',
                'val_B': '23B',
                'val_C': '23C',
                'val_D': '23D',
                'val_E': '23E',
            },
        },
        'key3': {
            'key3-1': {
                'val_A': '31A',
                'val_B': '31B',
            },
            'key3-2': {
                'val_A': '32A',
                'val_B': '32B',
                'val_C': '32C',
                'val_D': '32D',
                'val_E': '32E',
            },
        },
    }
    
    key1 = 'key2'
    key2 = 'key2-2'
    key3 = 'val_B'

```
</div></details>

<details><summary>templateの実装例(ここをクリックで展開)</summary><div>

- 記事での説明用に，GitHub上のコードから少し変えています
- この例ではカンマ区切りの文字列をカスタムフィルタの引数としています
- ``with``文を用いて変数``key``に連結した文字列を代入し，カスタムフィルタ``dict_value``に渡します
- この実装方法で，``key1``，``key2``，``key3``を変えることで，ネストされた辞書変数``nested_dict``へ動的にアクセスすることができます

```html:template
{% load custom_filter %}

{% with key1|:","|add:key2|:","|add:key3 as key %}
  {{ nested_dict|dict_value:key }}
{% endwith %}
```
</div></details>

# 4.さいごに

templateからネストされた辞書変数に動的に指定する実装例を紹介しました．
ネストされた辞書変数に限らず，カスタムフィルタへ複数の引数を指定したいときにも流用できると思います．

# 5.関連リンク

* [Djangoのテンプレートで文字列を連結する方法](https://intellectual-curiosity.tokyo/2019/12/17/django%E3%81%AE%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%81%A7%E6%96%87%E5%AD%97%E5%88%97%E3%82%92%E9%80%A3%E7%B5%90%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/)
* [Djagno template 内で文字列を結合する](https://gist.github.com/kemsakurai/5a1669a9fd5ffb64c21b4db5f341952b)
* [How do I add multiple arguments to my custom template filter in a django template?](https://stackoverflow.com/questions/420703/how-do-i-add-multiple-arguments-to-my-custom-template-filter-in-a-django-templat)