【Django】一つのviewに対して二つのurlを対応させる

#はじめに
Djangoプロジェクトを開発する際に「このページのURLのパターンが二つ欲しいな」と思う場面がありました．自分なりにやってみたところ現状うまくいってるのでご紹介します．

#どんな状況で複数のurlを対応させる必要があるのか？
「そもそも，そんな機能を実装することあるの？」と思う方もいらっしゃると思います．なので，私がこの機能を実装したいなと考えたきっかけとなる状況を簡単に共有します．

私が開発していたプロジェクトではアプリケーション上に地図を表示するもの（以下，地図表示ページ）がありました．そのページでは，対象の人などの位置情報を更新する必要があり，私は定期的にこのページを自動リロードさせることでその更新を実現していました．
プロジェクト内の別ページからこの地図表示ページにアクセスする場合は表示する地図座標は設定してあるデフォルト値で問題ないのですが，地図表示ページがリロードされたときにも毎回デフォルト座標に戻されてしまうと、「うざい，，」って感じますよね。
そこでリロードされた場合は，リロード前に閲覧してた座標を使って地図を表示してやろうと考えました．

##要件定義
上記の状況から要件をまとめると，こうなります．
1.プロジェクト内の別ページから地図表示ページにアクセスした場合には，設定されたデフォルト座標を基に地図を表示する．
2.地図表示ページがリロードされた場合は，リロード前の地図座標を基に地図を表示する．

#実装手順
この記事の内容を取り入れようと考えて下さる方に向けて実装手順を記載致します．
今回は地図の表示に「地図の拡大率」と「表示する中心座標」が必要なので，この二つの要素を表示するまでを目標としましょう．

##環境
macOS Catalina 10.15.7
Python: 3.8.5
Django: 3.2.8

##前提条件
この記事では，「myproject」プロジェクト内の「example」アプリケーションでの開発を想定してます．

##1.「example」アプリケーションを作成する

```sh
$ python manage.py startapp example
```

##2.設定ファイルにexampleアプリケーションを追加する
※設定ディレクトリ名は`config`であると仮定してます

```diff:myproject/config/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
+   'example.apps.ExampleConfig', # アプリケーション追加
    …
]

```

##3.exampleアプリケーションのURLを定義する

```diff:diff:myproject/config/urls.py
urlpatterns = [
    …
+   path('example/', include('example.urls')),
    …
]
```

##4.Viewを定義する
ここで，Viewを定義する際にURLパラメータを`kwargs.get('<URLパラメータ>')`で取得します．このとき，URLパラメータの有無で処理を変えることで一つのviewを複数のURLに対応させます．

```python:myproject/example/views.py
from django.views.generic import TemplateView

class Index(TemplateView):
    template_name = 'index.html'

    # 「地図の拡大率」と「表示する中心座標」のデフォルト値をグローバル変数に定義しておく（数字自体に特に意味はないです）
    global def_map_zoom
    def_map_zoom = 20
    global def_center_lonlat
    def_center_lonlat = [139.7673068, 35.6809591]

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        # 呼び出しURLによって処理を変更
        try:
            # URL内に変数値がある場合はその値を入力する
            context['map_zoom'] = int(kwargs.get('map_zoom'))
            context['center_lonlat'] = [float(kwargs.get('longitude')), float(kwargs.get('latitude'))]
        except:
            # URL内に変数値がない場合はデフォルト値を入力する
            context['map_zoom'] = def_map_zoom
            context['center_lonlat'] = def_center_lonlat

        return context
```


##5.アプリケーション内のURLを定義する

```python:myproject/example/urls.py
from django.urls import path
from . import views

app_name = 'example'
urlpatterns = [
    # 以下二つのURLは同じViewを呼び出しています
    path('', views.Index.as_view(), name='def_index'),
    path('<str:map_zoom>/<str:longitude>/<str:latitude>', views.Index.as_view(), name='index'),
]
```

##6.Templateを作成する
今回は簡単に表示するまでが目標なので，以下のようなtemplateを作成します．

```html:myproject/example/template/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>My test page</title>
  </head>
  <body>
    <p>マップズーム拡散率:{{ map_zoom }}</p>
    <p>表示中心座標:{{ center_lonlat }}</p>
  </body>
</html>
```

##7.Djangoプロジェクトを起動する

```sh
$ python manage.py runserver

Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
November 25, 2021 - 08:40:51
Django version 3.2.9, using settings 'config.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```

#確認
下記のURLにアクセスしてページ内の表示結果を確認してみてください．
また，二つ目のURLのURLパラメータを変更してみて実際に適応できているかも確認してもらえるといいと思います．

デフォルト値のURL
http://127.0.0.1:8000/example/
URLパラメータ付きのURL
http://127.0.0.1:8000/example/19/139.0000000/35.0000000

#まとめ
今回の記事の内容を使うこと自体珍しいとは思いますが，できると知っているだけでもその壁に当たったときに解決までの時間が全然違ってくると思うので把握しておいて損はないかなと思います．
最後まで読んで頂きありがとうございます．今回はここで終わりです．

※ご意見や疑問点などお待ちしております！

#参考文献
[HTML の基本 - ウェブ開発を学ぶ - MDN Web Docs](https://developer.mozilla.org/ja/docs/Learn/Getting_started_with_the_web/HTML_basics)