Djangoにテンプレートが存在しません(TemplateDoesNotExist)的な事を言われたので解決する

# はじめに
Djangoでテンプレートが存在しません的なことを言われたときに見るべき部分をまとめてみました。設定と挙動を一緒に見ていくと、理解しやすいのではないかと思います。


# 参考環境
ひとまずは以下のような環境を構築したものとします。

・作業フォルダはprojectとし、「プロジェクトの設定に関わるファイルがここにある」という意味を明確にするため、プロジェクトフォルダはconfigとします。

・django-admin startproject config .（ドットがある）と　django-admin startproject config の違いは、configフォルダ内にさらにconfigフォルダを作成するかしないかの違いである。
階層が深くなるため、やや冗長に感じるのでdjango-admin startproject config .（現在のフォルダにプロジェクトを作成）とします。
これにより、manage.pyはproject直下に配置され、.\manage.py runserver のように呼び出すことができます。

```cmd:mkdjango.bat
mkdir project
cd project
python -m venv env
env\Scripts\python.exe -m pip install --upgrade pip
env\Scripts\pip.exe install django
django-admin startproject config .
.\env\Scripts\python.exe .\manage.py startapp app1
.\env\Scripts\python.exe .\manage.py startapp app2
.\env\Scripts\python.exe .\manage.py migrate
.\env\Scripts\python.exe .\manage.py runserver
pause
```

コマンド操作後に作成するファイルは以下の通り。作成方法は割愛し、必要な部分だけ説明します。

```:フォルダ構成
project/
├── config/                         
│   ├── __init__.py                 
│   ├── asgi.py                     
│   ├── settings.py                 
│   ├── urls.py                     
│   ├── wsgi.py                     
│   └── __pycache__/                
│       ├── ...                    
├── app1/                           
│   ├── migrations/                 
│   │   ├── __init__.py             
│   │   └── ... 
│   ├── templates/                 # 新たに作成
│   │   ├── app1/                  # 新たに作成
│   │   │   ├── index.html         # 新たに作成
│   │   │   └── ... 
│   │   └── ...              
│   ├── __init__.py                
│   ├── admin.py                    
│   ├── apps.py         
│   ├── urls.py                    # 新たに作成
│   ├── models.py                   
│   ├── tests.py                    
│   ├── views.py                    
│   ├── urls.py                     
│   └── __pycache__/                
│       ├── ...
├── app2/                           
│   ├── migrations/                 
│   │   ├── __init__.py             
│   │   └── ...
│   ├── templates/                 # 新たに作成
│   │   ├── app2/                  # 新たに作成
│   │   │   ├── index.html         # 新たに作成
│   │   │   └── ... 
│   │   └── ...                
│   ├── __init__.py                 
│   ├── admin.py                    
│   ├── apps.py                     
│   ├── urls.py                    # 新たに作成
│   ├── models.py                   
│   ├── tests.py                    
│   ├── views.py                    
│   ├── urls.py                     
│   └── __pycache__/               
│       ├── ...
├── env/                            
│   ├── Scripts/                    
│   └── ...
├── templates/                     # 新たに作成                    
│   ├── base.html                  # 新たに作成
│   └── ...
├── db.sqlite3                      
├── manage.py                       
```
# Djangoがテンプレートを探す流れについて

（１）ルーティング
①　ブラウザにURL(http://localhost:8000/app1/ )を入力、プロジェクトの config/urls.py から"app1/"を探し一致したらそのアプリのurls.py(app1/urls.py)に飛ぶ。
②　app1/urls.pyの"app1"と対応する関数(views.app1func)を実行する。
③　views.app1funcによってindex.htmlを探す。

（２）テンプレート(index.html)を探す
①　project/templates/にindex.htmlを探しに行く。ここに置かれているのはbase.htmlなので、スルーされる。
②　project/config/settings.py の 'APP_DIRS': True なら、さらにアプリのindex.html(app1/temalates/app1/index.html)を見に行く。それを返す（表示する）。


# １　アプリが登録されているかを確認する
同じように作成したapp1とapp2について、「app1ではindex.htmlが見つかるのに、app2では見つからない」みたいな事が起こった場合はアプリの追加忘れかもしれません。

「app1はチュートリアルを見ながら作成したけど、app2はINSTALLED_APPSに追加する手順だけ忘れる」とかありがちだと思います。

```project/config/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 追加
    'app1',
    # 追加忘れ
    'app2',
]
```


# ２　プロジェクトの設定

project/templates/を見に行くための設定を行います。

プロジェクトは以下のように設定します。

## ２．１　DIRSの設定
[BASE_DIR / 'templates']　は、BASE_DIRがルートディレクトリ（project）を表し、"/"でその直下を表し、あさらに'templates'フォルダを文字列で表現しています。

つまり、project/templatesということになります。

## ２．２　APP_DIESの設定

'APP_DIRS': True　とすることで、アプリ内のtemplatesを探しにいくことができます。

注意点としては、'APP_DIRS': Trueにしていても、project/templates/は見に行くということです。

ファイル名がapp1/templates/やapp2/templates/にあるものとかぶらないように注意する必要があります。

```python:config/settings.py
from pathlib import Path

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        # プロジェクト直下のtemplatesフォルダ(app1, app2共通)を参照する
        'DIRS': [BASE_DIR / 'templates'],  
        # Trueの場合、Djangoはプロジェクトに含まれる各アプリケーションの中にある
        # 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',
            ],
        },
    },
]

```

# ３　各アプリの設定

## ３．１　プロジェクトのルーティング

app1の場合を例に考えます。

ブラウザでhttp://localhost:8000/app1/ のようなURLにアクセスしたとします。

config/urlsを見に行き、'app1/'があるか見にいきます。見つかった場合はapp1.urls.pyを見に行きます。

※ここで、include関数に渡す文字列は'app1.urls'のように*.pyがつかないことに注意してください。

```python:config/urls.py
from django.contrib import admin
from django.urls import path, include # includeの追加も忘れずに
urlpatterns = [
    path('admin/', admin.site.urls),   # Django管理画面
    path('app1/', include('app1.urls')),  # app1のURL設定をインクルード
]
```

## ３．２　アプリのルーティング

アプリ内のurlsを見に行きます。

"appl/"にアクセスしたときに、views.app1funcを呼び出すというものです。

（name をapp1indexとしておき、ビュー側でreverse(app1index)などとすることによって動的にアドレスを利用できます。それはまた別の話になるのでここでは割愛します。）

```python:app1/urls.py
from django.urls import path
from . import views # . は同じディレクトリ内という意味 ここではviewsを追加する
urlpatterns = [
    # `app1/`にアクセスしたときにviews.app1funcを呼び出す
    # 'app1index'などとしておくことにより、後からURLを動的に利用できます（その際にreverse関数を使う）。
    path('app1/', views.app1func, name='app1index'),  
]
```


## ３．３　ビューの実行

app1/views.py内のapp1funcが実行されます。

例の場合、app1/templates以下にある"app1/index.html"を探しにいきます。
つまり、app1/templates/app1/index.htmlを見にいきます。

settings.py で APP_DIRS = True を設定しているので、Django は各アプリの templates フォルダも自動的に読み込み、app1/templates/index.html でも問題なく動作します。

ただし、アプリの名前でテンプレートが分けられるため、app1/templates/app1/index.htmlとするのが推奨されるようです。

```python:app1/views.py
from django.shortcuts import render

def app1func(request):
    return render(request, "app1/index.html")
```

ここから、djangoはテンプレートを探しにいきます。

④ まず、djangoはproject直下のtemplatesを見に行きます。その時点でindex.htmlがあった場合はそれを表示します。なので、ここに置くデータはindex.htmlではなくbase.htmlなどします。（ちなみに、各アプリのindex.htmlはそれを継承して作成すると良いようですが、それはまた別の話なので割愛します。）
＊templatesフォルダはapp1内のtemapatesとapp2内のtemplates合わせて全部で3箇所作成するので注意してください。

③が問題なければ、project/app1/templates/app1/index.htmlの内容を返します。
確認のため、3つとも異なる内容にしておきます。

```html:project/templates/base.html
<!DOCTYPE html>
<html lang="ja">
  <body>
    <h1>Hello base.html </h1>
  </body>
</html>
```

```html:project/app1/templates/app1/index.html
<!DOCTYPE html>
<html lang="ja">
  <body>
    <h1>Hello app1 index.html </h1>
  </body>
</html>
```

```html:project/app2/templates/app2/index.html
<!DOCTYPE html>
<html lang="ja">
  <body>
    <h1>Hello app2 index.html </h1>
  </body>
</html>
```

アクセスしてみて、問題がなければOKです。

# ４　確認してみる

設定はほとんど上と同じで、dateapp,testappというアプリでやっています。

サーバーを起動します。

```bash:bash
python manage.py runserver     
```

トップページの設定を特に行っていないため、最初はこのページが表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809517/4935a89b-0740-807a-4261-0506d8b1314f.png)

testappは問題なく表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809517/a494411a-4264-3c29-7e4d-81ba686354ff.png)

dateappが表示されない。TemplateDoesNotExist。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809517/f6e81daf-3341-7af2-7072-48fcc9947b9a.png)

INSTALLED_APPS への未登録が原因でした。

```python:config/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 追加
    'testapp',
    # コメントアウトしているため、dateappのみテンプレートが見つからない。
    # 'dateapp',
]
```

# まとめ

自分の場合はアプリ未登録が原因でTemplateDoesNotExistになってましたが、設定項目が多いのでルーティングミスや単純なタイポもままあるかと思います。

TemplateDoesNotExistのページはChromeの翻訳機能で日本語化できるので、エラーの挙動を確認してみるといいかもしれません。

何かの足しになれば幸いです。