この記事は、Qiita Advent Calendar 2018
9日目の記事になります。16日目とはなりますが、投稿がなかったため、代わりに投稿しました。
Django
を使って、Webアプリを開発しております。
当初から同時に二つのアプリケーション(同じ場所で使うが、機能がだいぶ異なるので分かれている)を構築することとなっており、一つ作った後にもう一つ、というときにいろいろ躓いたため、どこで困ったか書いてみます。
Django
は、本番環境で動かす際どうするかについて書いてある記事がそれほど多くない印象です。その一助になれば幸いです。
主にファイル管理関連です。
バージョン等
名称 | バージョン |
---|---|
Python | 3.7.0 |
Django | 2.1.1 |
サンプル
プロジェクトとしてdjango_complex_sample
というものを作り、その中にapp1
、app2
というアプリケーションを作っています。
事前準備
settings.py
のINSTALLED_APPS
にapp1
とapp2
を追加します。DBは特に使わないですが、SQLite
にしておきます。また、URLも指定しておきます。
- settings.py
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app1', 'app2', ]
- urls.py
from django.contrib import admin from django.urls import path from app1 import views as v1 from app2 import views as v2 urlpatterns = [ path('admin/', admin.site.urls), path('app1/', v1.HomeView.as_view(), name="app1home"), path('app2/', v2.HomeView.as_view(), name="app2home"), ]
テンプレート
結論
標準のまま使うなら、
/templates/アプリ名
というフォルダを作り、そこにファイルを格納する- ビューからテンプレートを参照する場合、
アプリ名/ファイル名
と記述する
根拠
テンプレート | Django documentation | Django
テンプレートは、view
でパス指定されて、ページを描画する際に使われます。
標準のまま使う場合、各サイトにtemplates
というフォルダを作って、そこにHTML
を配置すると、サイト表示時にそのテンプレートを表示します。
ただ、このテンプレートの決定方法は、フォルダを探索して最初に見つかったファイルを使用するという動作をしています。自動的にアプリ別に判定、とはなっていません。
よって、確実にファイルを判別するために、templates
直下にアプリ名のフォルダを作り、そこを参照するようにしたほうが良いです。
例
例として、app2
のtemplates
直下にhome.html
を配置します。app1
の同フォルダは空にします。そして、app1
のビューでテンプレート名home.html
を指定します。
- app1/views.py
from django.shortcuts import render from django.views.generic import TemplateView # Create your views here. class HomeView(TemplateView): template_name = 'home.html' # app1のhome.htmlを期待しているが、まだ作っていない def get(self, request, *args, **kwargs): return render(request, self.template_name)
- app1/home.html
<h1>Hello App1!</h1>
- app2/home.html
<h1>Hello App2!</h1>
- デバッグ結果
上記のように、app2
のhome.html
を参照して、表示してしまいます。これは、templates
の検索で、app2
にあるhome.html
が見つかるためです。
これを避けるために、全てのアプリで以下のようにフォルダを切り、
- app1/views.py
from django.shortcuts import render from django.views.generic import TemplateView # Create your views here. class HomeView(TemplateView): template_name = 'app1/home.html' # templates/app1フォルダを参照 def get(self, request, *args, **kwargs): return render(request, self.template_name)
フォルダを固定で指定できるようにしましょう*1。
静的ファイル(static)
結論
- 各アプリケーション直下に
/static/アプリ名
というフォルダを作り、そこにファイルを格納する - テンプレートでファイルを参照する場合、
アプリ名/ファイル名
と記述する
根拠
静的ファイル (画像、JavaScript、CSS など) を管理する | Django documentation | Django
静的ファイルとは、JavaScript
や画像など、ページに埋め込む形で使うファイルを指します。開発時は、各アプリ直下にstatic
というフォルダを作り、そこにファイルを格納します。
このファイル、本番環境では一か所に集めて管理します。collectstatic
*2コマンドで、静的ファイルを集められます。
試しに、static
フォルダに一つだけファイル(main.css
)を置いて、collectstatic
を実行します。
すると、static
の直下に配置されてしまいます。これだと、複数のサイトで同じファイル名のファイルがあると、お互い上書きされます*3。
なので、各アプリのstatic
直下に、アプリ名のフォルダを作成して、ファイルが衝突しないようにしましょう。
テンプレートで参照する場合も、
- app1/home.html
{% load static %} <script type="text/javascript" src="{% static 'app1/main.css' %}"></script>
のように、パスにアプリ名を付ける必要があるため、開発着手時から対応しておいたほうが良いです。
static
内にアプリ名のフォルダを作って、collectstatic
を実施すると、以下のようになります。
URL(urls.py)
結論
- アプリごとに、
urls.py
を作成する - 各アプリの
urls.py
で、一意のapp_name
を定義する - プロジェクトの
urls.py
で、各アプリの定義をinclude
する - テンプレートでアプリ名を指定する
根拠
URL ディスパッチャ | Django documentation | Django
現状、プロジェクトにあるurls.py
は、以下のようになっています。
- django_complex_sample/urls.py
from django.contrib import admin from django.urls import path from app1 import views as v1 from app2 import views as v2 urlpatterns = [ path('admin/', admin.site.urls), path('app1/', v1.HomeView.as_view(), name="app1home"), path('app2/', v2.HomeView.as_view(), name="app2home"), ]
アプリでURLが増えた場合に、ここに定義を増やして対応することは可能です。
しかし、そうすると、
というような問題が発生します。
こういうときのためにDjango
では、include
という機能が用意されています。
例
まず、各アプリの直下(どこでもよい)に、urls.py
を作成します。
そして、自身のアプリに対するURLを記述します。
- app1/urls.py
"""app1のURL定義""" from django.urls import path from .apps import App1Config as config from . import views as v app_name = config.name # app1が入る urlpatterns = [ path('', v.HomeView.as_view(), name='home'), ]
そして、プロジェクトのurls.py
でそれをinclude
します。
- django_complex_sample/urls.py
from django.contrib import admin from django.urls import path, include from app1 import urls as v1 from app2 import views as v2 urlpatterns = [ path('admin/', admin.site.urls), path('app1/', include(v1)), # 変更(app1のURLを追加) path('app2/', v2.HomeView.as_view(), name="app2home"), ]
こうすると、自動的にapp1
のURLへのルーティングを追加してくれます。
また、この機能を使った場合、view
でのURL指定方法が変わります。
まず、app1
に適当なビューを追加します。
- app1/views.py
from django.shortcuts import render from django.views import View from django.views.generic import TemplateView from django.http import HttpResponse # Create your views here. class HomeView(TemplateView): template_name = 'app1/home.html' def get(self, request, *args, **kwargs): return render(request, self.template_name) class ApplicationView(View): def get(self, request, *args, **kwargs): return HttpResponse('GET request!')
- app1/urls.py
"""app1のURL定義""" from django.urls import path from .apps import App1Config as config from . import views as v app_name = config.name # app1が入る urlpatterns = [ path('', v.HomeView.as_view(), name='home'), path('app/', v.ApplicationView.as_view(), name='app'), # 追加 ]
このViewに対し、home.html
からリンクを作成します。(いろいろ加筆しています)
- app1/templates/home.html
{% load static %} <!DOCTYPE html> <html lang="ja"> <head> <link href="{% static 'app1/main.css' %}" rel="stylesheet"> </head> <body> <h1>Hello App1!</h1> <a href={% url 'app1:app' %}>リンク</a> </body>
重要なのは
<a href={% url 'app1:app' %}>リンク</a>
の部分です。url
というDjango
のタグで、指定した名前のURLを設定してくれますが、{% url '[アプリ名]:[name]' %}
とhref
で指定すると、アプリ名を判定してそのリンクを作ってくれます。
これにより、アプリ間の名前競合を避けられます。
おわりに
まだまだ、複数アプリ開発での注意点はあるように思います。
現状、筆者が体験した中での話となっています。
原則としては、アプリの処理はアプリ内で完結させることと、アプリ外で扱いやすいような設計にしておくことの二つだと感じています。
理想は、pip
でインストールしたときに簡単に扱えるようにしておくことです。
ほかにも気づいたことあったら、ちょっとずつ整理していきます。