pythonのウェブフレームワークであるDjangoの公式チュートリアル
「はじめての Django アプリ作成」その1~5までのメモです。
ほんとにただのメモです。
本家サイトはこちら
https://docs.djangoproject.com/ja/3.1/intro/tutorial01/
1. インストール
python環境を作ってpipで入ります
pip install django
入ってるか確認してみます
$ python -m django --version
3.1.2
django 3.1.2が入ってるようです
2. Djangoプロジェクトを作る
django-admin startproject hogeを実行すると、プロジェクトに最低限必要なフォルダとファイルを自動作成してくれます。
django-admin startproject mysite
作成されるファイルは以下のような構成です
mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
asgi.py
wsgi.py
DBはpython標準のsqlite3になっています。
とりあえずこの状態でサンプルページを表示できるのでやってみましょう。manage.py runserverと実行すると開発用の簡易サーバーが起動します。
python manage.py runserver
起動したサーバーはlocal hostの8000番ポートに待機するので以下のURLをブラウザで開くと見られます。
http://127.0.0.1:8000
なお、この簡易サーバーは開発中の利用だけを考えて作られているので「絶対に運用環境では使わないでください」と公式チュートリアルに書かれています。本番環境ではApacheなどを使って公開しましょう。
3. プロジェクト内にアプリケーションを作る
Djangoは複数のアプリケーションを連携して使えるようにしてくれています。以下のように実行すればアプリに最低限必要なファイルを自動生成してくれます。
python manage.py startapp polls
polls/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
views.py
アプリはいくつでも作れるので、機能別に上手く分割すると生産性が上がりそうです。
4. pollsアプリのviewを作る
viewはviews.pyに書きます。
以下は/polls/index.htmlを読みに来たらhttpで「Hello, world. You're at the polls index.」と返すようなviewになります。
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
上記views.pyへの関連付けをurls.pyに書いていきます。
django.urls.path()に書くと関連付けてくれます。以下のように書くとmysite/urls.py → polls/urls.py → polls/views.pyの順で参照してpolls/views.py内のindex()を見つけてくれるようになります。
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('polls/', include('polls.urls')),
path('admin/', admin.site.urls),
]
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
これで関連付けできたので、簡易サーバーを起動して確認しましょう。
python manage.py runserver
urlにpolls/views.pyに書いた内容が待機してくれます。
http://localhost:8000/polls/
5. DBの変更
デフォルトではsqlite3を使うようになっていますので特にDBを設定しなくても出来るようになっています。本番ではpostgreSQLやmySQLに変更すると思いますのでその場合は以下を参照します。
https://docs.djangoproject.com/ja/3.1/intro/tutorial02/
6. TIME_ZONEの設定
標準だとUTCで設定されています
TIME_ZONE = 'UTC'
日本標準時にする場合は以下のように変更します
TIME_ZONE = 'Asia/Tokyo'
USE_TZ = True
7. migrateする
setting.pyの内容に基づいてDBを構築します
python manage.py migrate
djangoはDBのデータをオブジェクトとして扱ってくれるのでデータのハンドリングが楽になるみたいです。(書いてる僕は現時点でよく分かってません。)
8. modelを作る
import datetime
from django.db import models
from django.utils import timezone
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question_text
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
INSTALLED_APPS = [
'polls.apps.PollsConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
modelに変更があったことをdjangoに認識させます
python manage.py makemigrations polls
9. django shellを使う
djangoは整理されたディレクトリ構造を強制することで生産性を高めるように作られていますが、ファイルの繋がりが複雑になりdjangoから関数を実行したときにどうなるか確認しにくいという問題があります。これを確認しやすいように用意されているのがdjango shellで、python manage.py shellで実行することが出来ます。
>>> from polls.models import Choice, Question
>>> Question.objects.all()
<QuerySet []>
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
>>> q.save()
>>> q.id
1
>>> q.question_text = "What's up?"
>>> q.save()
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>
>>> q.question_text
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)
python manage.py shell
In [1]:
>>> from polls.models import Choice, Question
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>
>>> Question.objects.get(id=2)
Traceback (most recent call last):
...
DoesNotExist: Question matching query does not exist.
>>> Question.objects.get(pk=1)
<Question: What's up?>
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True
>>> q = Question.objects.get(pk=1)
>>> q.choice_set.all()
<QuerySet []>
>>> q.choice_set.create(choice_text='Not much', votes=0)
>>> q.choice_set.create(choice_text='The sky', votes=0)
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)
>>> c.question
<Question: What's up?>
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()
10. 監理ユーザーを作る
djangoプロジェクト監理用のsuperuserを作ります。このsuperuserはdjango用なのでLinux OSのuserとは別になります。
python manage.py createsuperuser
IDとパスワードを入力すれば登録完了です。これによりadminページにログインできるようになるので簡易サーバーを起動してadminページにログインしましょう。
python manage.py runserver
adminページのURL
http://127.0.0.1:8000/admin/
ログインすると以下のように表示されます
11. adminページからアプリを編集する
from django.contrib import admin
from .models import Question
admin.site.register(Question)
Questionsをクリックするとオブジェクト内の値を操作できます
shellから操作するよりこっちの方が分かりやすいので上手く使いたいですね。
12. Viewを足していく
はじめての Django アプリ作成、その3
https://docs.djangoproject.com/ja/3.1/intro/tutorial03/
URLによって開くviewを変える
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
from django.urls import path
from . import views
urlpatterns = [
# ex: /polls/
path('', views.index, name='index'),
# ex: /polls/5/
path('<int:question_id>/', views.detail, name='detail'),
# ex: /polls/5/results/
path('<int:question_id>/results/', views.results, name='results'),
# ex: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
]
これでpython manage.py runserverして以下URLをブラウザで開くと、polls/views.pyのdetail(), results(), vote() がそれぞれ実行されます。
http://127.0.0.1:8000/polls/34/
http://127.0.0.1:8000/polls/34/results/
http://127.0.0.1:8000/polls/34/vote/
インデックスページを作る
polls/views.pyのindex()を以下のように書き換えてQuestion.
from django.http import HttpResponse
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
output = ', '.join([q.question_text for q in latest_question_list])
return HttpResponse(output)
def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
13. templateを作る
上記ではviews.pyに直接画面レイアウトを書き込んでしまっていますが、修正しやすいようにtemplateに記述を分けるようにしましょう。views.pyにloader.get_template('polls/index.html')を書いてtemplate/polls/index.htmlを読むようにします。
from django.http import HttpResponse
from django.template import loader
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = {
'latest_question_list': latest_question_list,
}
return HttpResponse(template.render(context, request))
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
render
templateを読み込んでrenderするのをrender()で置き換えると短く書けますし、importするパッケージも少なくて済みます。
from django.shortcuts import render
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
context = {'latest_question_list': latest_question_list}
return render(request, 'polls/index.html', context)
14. 404エラー表示
try/exceptで例外になったときに404を出すようにします。
from django.http import Http404
from django.shortcuts import render
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
context = {'latest_question_list': latest_question_list}
return render(request, 'polls/index.html', context)
def detail(request, question_id):
try:
question = Question.objects.get(pk=question_id)
except Question.DoesNotExist:
raise Http404("Question does not exist")
return render(request, 'polls/detail.html', {'question': question})
get_object_or_404
いちいちtry/exceptするのは面倒なので、getして失敗したら404を返す関数であるget_object_or_404()に置き換えてみます
from django.shortcuts import get_object_or_404, render
from .models import Question
# ...
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
15. アプリの名前空間
polls/urls.pyのapp_nameを指定するとアプリの名前空間から参照できるようになります。これをやらないと別アプリに同じ名前のviewがあったときに動作が怪しくなりそうなので、忘れない内にview作るときに指定しておいた方が良いと思います。
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/results/', views.results, name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
これにより、viewの参照がdetailからpolls:detailに変更になります。他のviewも同様にpolls:hogeのように指定し直しましょう。
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
16. フォームを書く
はじめての Django アプリ作成、その4
https://docs.djangoproject.com/ja/3.1/intro/tutorial04/
投票ボタンを追加します
<h1>{{ question.question_text }}</h1>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
<input type="submit" value="Vote">
</form>
こんな感じでボタンができます
http://127.0.0.1:8000/polls/1/
formのactionを「polls:voteのviewにpostする」にしてあるので、これに対応するviewを作ります。
polls:voteのviewはpolls/urls.pyに以下のように書いてますのでpoll/views.pyに書いてやれば良いという事になります。
path('<int:question_id>/vote/', views.vote, name='vote'),
choiceされている対象を1つ足して/polls/results/にリダイレクトするようにしてやります
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from .models import Choice, Question
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
# Redisplay the question voting form.
return render(request, 'polls/detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
リダイレクトで呼ばれる/polls/results/のviewも書いてやります
from django.shortcuts import get_object_or_404, render
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
17. 汎用ビューを使う
汎用ビューを使うことで無駄な記述が減らせるようです。
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from .models import Choice, Question
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
"""Return the last five published questions."""
return Question.objects.order_by('-pub_date')[:5]
class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'
class ResultsView(generic.DetailView):
model = Question
template_name = 'polls/results.html'
まだまだ続くんですが
https://docs.djangoproject.com/ja/3.1/intro/tutorial05/