Djangoのカスタムエラーハンドリングを学ぶ

## はじめに
 　こんにちは。@toamoku-20220418と申します。現在Djangoを使用したアプリケーションの作成を目標に、勉強を進めております。
　そこで今回はDjangoのエラーハンドリングについて学んで行きたいと思います。エラーが発生した時、デフォルトでは Djangoのエラー画面が表示されます。しかしユーザー目線で考えるとあまりいけていないと思います。そこで今回はエラーの種類に応じてカスタムしたエラー画面が表示されるようにします。
　ぜひ最後までご覧いただき、皆さんの参考になれば幸いです。
## 前提条件
　PCはmacを使用しております。pythonはすでインストールされている前提でお話しして行きます。まだDjangoやpythonの詳細な解説は省いており、Linuxコマンドについても同様です。
## 今回の目標
 カスタムのエラーハンドリングを作成して、ユーザーに優しいエラー画面を作成します。
## projectディレクトリを作成
```bash
projectディレクトリを作成
$ mkdir myproject

myprojectディレクトリへ移動
$ cd myproject
```
仮想環境を作成し、Djangoをインストール
```bash
※ virtualenvの場合
仮想環境を作成
$ virtualenv mailenv

仮想環境を有効化
$ source mailenv/bin/activate
```
Djangoのprojectディレクトリとアプリケーションを作成
```bash
※ 先にprojectディレクトリを作成しているので、最後にドットを付与
プロジェクトを作成
$ django-admin startproject errorproject . 

アプリケーションを作成
$ python manage.py startapp errorapp
```
settings.pyにerrorappを追加
```python:errorproject/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'errorapp' # ここを追加
]
```
## ホーム画面を作成
ホーム画面を作成します。最初にurls.pyを設定します。
```python:errorproject/urls.py
from django.contrib import admin
from django.urls import path, include # includeを追加

urlpatterns = [
    path('admin/', admin.site.urls),
    # admin/以外のPATHはmailappのurls.pyから行う
    path('', include('errorapp.urls'))
]
```
errorappのurls.pyを設定します。
```python:errorapp/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.home, name='home')
]
```
templatesディレクトリを作成し、HTMLファイルを保存します。
```bash
mkdir templates
```
home.htmlを作成します。
```html:templates/home.html
<h1>home<h1>
```
settings.pyにtemplatesを登録します。
```python:errorproject/settings.py
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',
            ],
        },
    },
]
```
views.pyにホーム画面を作成します。
```python:errorapp/views.py
from django.shortcuts import render

def home(request):
  return render(request, 'home.html')
```
サーバーを起動します。
```bash
$ python manage.py runserver
```
その後ブラウザでlocalhost:8000にアクセスし「home」と表示されれば成功です。
## カスタムエラーハンドラを作成
最初にエラー画面用のHTMLファイルを格納するerrorsディレクトリをtemplatesの下に作成します。
```bash
templatesディレクトリへ移動
cd templates

templatesディレクトリでerrorsディレクトリを作成
mkdir errors
```
続いて404番エラーのカスタム画面を作成します。
```html:templates/errors/404.html
<!DOCTYPE html>
<html>
<head>
    <title>Page Not Found</title>
</head>
<body>
    <h1>404 - Page Not Found</h1>
    <p>お探しのページは見つかりませんでした。</p>
    <a href="/">ホームに戻る</a>
</body>
</html>
```
500番エラーのカスタム画面を作成します。
```html:templates/errors/500.html
<!DOCTYPE html>
<html>
<head>
    <title>Server Error</title>
</head>
<body>
    <h1>500 - Internal Server Error</h1>
    <p>サーバーエラーが発生しました。しばらくしてから再度お試しください。</p>
    <a href="/">ホームに戻る</a>
</body>
</html>
```
urls.pyにカスタムエラーハンドラを追加します。
```python:errorproject/urls.py
from django.contrib import admin
from django.urls import path, include

# カスタムエラーハンドラ ここを追加
handler404 = 'errorapp.views.custom_404'
handler500 = 'errorapp.views.custom_500'

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('errorapp.urls'))
]
```
次に500番エラーを発生させるパスを作成します。
```python:errorapp/urls.py
from django.urls import path
from . import views


urlpatterns = [
    path('', views.home, name='home'),
    path('trigger-500/', views.trigger_500, name='trigger500'), # 500エラーを発生させるためのルート
]
```
エラーが発生したときにカスタムエラーを表示するviewを作成します。
```python:errorapp/views.py
from django.shortcuts import render

def home(request):
    return render(request, 'home.html')
# ここから下を追記
def custom_404(request, exception):
    return render(request, 'errors/404.html', status=404)

def custom_500(request):
    return render(request, 'errors/500.html', status=500)

def trigger_500(request):
　　# raiseを使用し、500エラーを強制的に発生させる
    raise Exception("テストで500番エラーを発生させるトリガーです")
```
エラーを発生させるためのホーム画面に変更します。
```html:templates/home.html
<!DOCTYPE html>
<html>
<head>
    <title>Home</title>
</head>
<body>
    <h1>Welcome to the Home Page</h1>
    <p>ここはホームページです。</p>
    <!-- このルートが存在しないので404になる -->
    <a href="/nonexistent/">404エラーページを見る</a>
    <!-- 500エラーのルートを指定 -->
    <a href="{% url 'trigger500' %}">500エラーページを見る</a>
</body>
</html>
```
最後にsettings.pyでエラーハンドル用に設定を修正します。
```python:errorproject/settings.py
DEBUG = False # Falseに変更

# localhostの場合。localhost:8000でも127.0.0.1:8000両方で対応
ALLOWED_HOSTS = ['localhost', '127.0.0.1'] 
```
これでディレクトリ構成は下記のようになっていると思います。
```bash
myproject/
├── errorapp/
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── errorproject/
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── templates/
│   ├── errors
│	│   ├── 404.html
│	│   └── 500.html
│   └── home.html
└── manage.py
```
最後に動作確認を行います。
1. サーバーを起動し、ブラウザでlocalhost:8000に接続します
2. ホーム画面から、404と500それぞれのページを確認してみましょう
3. それぞれ404.htmlと500.htmlが表示されればOKです
4. そこからホーム画面にも戻ってみましょう
## 最後に
　いかがでしたか。今回はDjangoのカスタムエラーハンドリングを学んでみました。セキュリティでは余計な情報は与えないようにすることが大事です。カスタムエラーを作成することにより、セキュリティの安全性向上とユーザーにもわかりやすく、WEBサイトからの離脱を防ぐ効果も期待できます。また他にもエラーが存在するので、それを追加で作成してもいいと思います。
　最後までご覧いただきありがとうございました。今後も記事を投稿して参りますので、引き続きよろしくお願いいたします。