🎓

【Django】可読性の高いディレクトリ構造を目指したら、urls.pyで詰まった件

2024/05/26に公開

はじめに

この記事の対象読者

  • Django初心者
  • プログラミング初心者

環境

  • Mac M2
  • Python 3.9
  • Django 4.2.11

背景

Djangoのデフォルトのディレクトリ構造が、アプリケーションディレクトリを作成するたびに見えづらくなるなと感じたので、appsディレクトリを作成し、その中に全てのアプリケーションディレクトリを作成しようと考えたのが事の発端です。

やりたかったこと

可読性の高いディレクトリ構造の作成

※一部の今回の内容に不要なファイル等は記載しておりません。

root
├── Dockerfile
├── README.md
├── compose.yaml
├── requirements.txt
└── src
    ├── apps
    │   ├── accounts
    │   │   ├── __init__.py
    │   │   ├── admin.py
    │   │   ├── apps.py
    │   │   ├── migrations
    │   │   ├── models.py
    │   │   ├── templates
    │   │   │   ├── base.html
    │   │   │   └── login.html
    │   │   ├── tests.py
    │   │   ├── urls.py
    │   │   └── views.py
    │   └── api
    │       ├── __init__.py
    │       ├── admin.py
    │       ├── apps.py
    │       ├── migrations
    │       ├── models.py
    │       ├── tests.py
    │       └── views.py
    ├── config
    │   ├── __init__.py
    │   ├── asgi.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    └── manage.py

上記のtreeを見てもらうと分かる通り、appsディレクトリ内に複数のアプリを作成しています。
1つのディレクトリにまとめることで、ぱっと見の構造をシンプルにしようと思ったわけです。

URLの設定

よし!! あとは、config の urls.py で include の記述を書いて、各アプリケーションディレクトリの urls.py を読み込むように設定すればOKだな!! と思ったのも束の間、、、 下記のような記述をして login/ 先のページが not Found になりました。

config/urls.py

from django.contrib import admin
from django.urls import path
from django.urls import include


urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', include('apps.accounts.urls')),
]

accunts/urls.py

from django.urls import path
from . import views


urlpatterns = [
    path('login/', views.Login.as_view(), name="login"),
]

対応その1

調査

調べた結果、下記のような要因を見つける事ができました。

  • メインURL設定ファイルとアプリURL設定ファイルの両方に同じURLパスがある場合:
    メインの urls.py ファイルとアプリケーションの urls.py ファイルの両方に同じパス(例えば 'login/')が含まれている場合、URLがどのビューにルーティングされるべきかをDjangoが混乱する可能性がある。

なるほど、つまり config/urls.py で login/ を設定したのち、 account/urls.py で login/ を設定してしまうと、どっちの login/ を参照していいのかわからなくなるのか。

修正

ということで、下記に修正すると直りました。

accunts/urls.py

from django.urls import path
from . import views


urlpatterns = [
    path('', views.Login.as_view(), name="login"),
]

accunts/urls.py を修正するだけで正常に動作しました。

対応その2

修正

ただ、このやり方だとURLが増えるたびに config/urls.py の urlpatterns を増やさないといけないので、可読性めちゃくちゃ悪いなーと思ったので、下記のように アプリケーション名/login/ となるように記述してみました。

config/urls.py

from django.contrib import admin
from django.urls import path
from django.urls import include


urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('apps.accounts.urls')),
]

accunts/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('login/', views.Login.as_view(), name="login"),
]

このように、頭のURLに各アプリケーションの名前をつけることで、 config/urls.py 内がぐちゃぐちゃにならずに済みました!

対応その3

調査

この記述でも十分いいのですが、このままだと、アプリケーションディレクトリが増えた際に「name が被る」という事象が起きてしまう可能性があることに気づきました。例えば以下のような状況です。

  • accountsアプリ: 自身のアカウント情報の入力やプロフィールの表示を行う
  • blogアプリ: 投稿したり削除したりする一連の機能や、投稿記事を書いた人のプロフィールなどを表示できる

accounts/urls.py

from django.urls import path
from . import views


urlpatterns = [
    path('profile/', views.Profile.as_view(), name="profile"),
]

blog/urls.py

from django.urls import path
from . import views


urlpatterns = [
    path('profile/', views.Profile.as_view(), name="profile"),
]

どちらもプロフィールを閲覧する先の名前が profile になっています。
この場合、名前の重複でエラーになってしまいます。エラー回避のために name の値を長くするのはなんとなく嫌だったので、別の方法がないか探りました。

修正

namespace(名前空間) というなんとも便利な include の引数を見つけました。
この名前空間は、プロジェクト内でURLを参照するときに使われます。たとえば、テンプレート内で {% url 'accounts:login' %} といった形で使用します。これにより、他のアプリケーションで同じ名前のURLパターンが存在しても混乱を避けられます。

ということで記述してみました。
手順は下記の通りです。

  1. config/urls.py の include の引数に、 namespace='accounts' を指定する
  2. accounts/urls.py で app_name 変数を用意し、そこに namespace で指定した値と同じ値を代入する

config/urls.py

from django.contrib import admin
from django.urls import path
from django.urls import include


urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('apps.accounts.urls', namespace='accounts')),
]

accunts/urls.py

from django.urls import path
from . import views


app_name = 'accounts'

urlpatterns = [
    path('login/', views.Login.as_view(), name="login"),
]

完璧です。文句ないです。
この urls.py の設定であれば、名前の設定を気にする必要はないし、URLの重複も防げるし、 config/urls.py の可読性が悪くなることもないですね!

参考記事

https://qiita.com/sr2460/items/11a1129975913ed584d3

https://tokibito.hatenablog.com/entry/20100211/1265884683

Discussion