はじめに
今回は、Djangoの公式チュートリアルを通して、Djangoアプリケーション作成に挑戦しました。
Django公式チュートリアルの項目に沿って、基本的な使い方や開発の流れを紹介しながら、自分なりに学んだことや気づきをまとめました。
はじめてDjangoに触れる方でも、この記事を通じて参考にしてもらえると嬉しいです。
はじめての Django アプリ作成、その 1
mkdir mysite
cd mysite
python3 -m django startproject mysite .
- 以下のコマンドでサーバを起動(manage.pyのあるディレクトリで実行すること)
python3 manage.py runserver
- 以下のURLにブラウザからアクセスする。Djangoのウェルカムページが表示される。
http://localhost:8000/
k_tanaka@tanakaknyanoAir mysite % python3 manage.py startapp polls
k_tanaka@tanakaknyanoAir mysite % ls
db.sqlite3 manage.py mysite polls
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
polls/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
urls.py
views.py
from django.urls import path
from . import views
urlpatterns = [
path("", views.index, name="index"),
]
- プロジェクト(mysite)配下のurls.pyに以下に修正
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("polls/", include("polls.urls")),
path("admin/", admin.site.urls),
]
はじめての Django アプリ作成、その2
- migrate コマンドは INSTALLED_APPS の設定を参照するとともに、 mysite/settings.py ファイルのデータベース設定に従って必要なすべてのデータベースのテーブルを作成します。
k_tanaka@tanakaknyanoAir mysite % python3 manage.py migrate
polls/models.py¶
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField("date published")
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
- mysite/setting.pyに以下の記載を修正
INSTALLED_APPS = [
"polls.apps.PollsConfig",
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
- makemigrations を実行することで、Djangoにモデルに変更があったこと(この場合、新しいものを作成しました)を伝え、そして変更を マイグレーション の形で保存することができました。
python manage.py makemigrations polls
- migrate を再度実行し、 モデルのテーブルをデータベースに作成
$ python3 manage.py migrate
k_tanaka@tanakaknyanoAir mysite % python3 manage.py shell
Python 3.9.6 (default, Feb 3 2024, 15:58:27)
[Clang 15.0.0 (clang-1500.3.9.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> 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())
File "<console>", line 1
q = Question(question_text="What's new?", pub_date=timezone.now())
IndentationError: unexpected indent
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
>>> q.save()
>>> q.id
1
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2024, 10, 15, 12, 24, 7, 807229, tzinfo=datetime.timezone.utc)
>>> q.question_text = "What's up?"
>>> q.save()
>>> q.question_text
"What's up?"
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>
>>> q = Question(question_text="testtest", pub_date=timezone.now())
>>> q.save()
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>, <Question: Question object (2)>]>
>>>
from django.db import models
class Question(models.Model):
# ...
def __str__(self):
return self.question_text
class Choice(models.Model):
# ...
def __str__(self):
return self.choice_text
python manage.py createsuperuser
$ python manage.py runserver
from .models import Question
admin.site.register(Question)
- 再度管理側サイトを覗くと、model.pyに記載したテーブルが表示される
はじめての Django アプリ作成、その 3
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"),
]
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))
- polls/templates/polls/index.htmlを作成
{% 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 %}
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)
- detailのビューを以下
get() を実行し、オブジェクトが存在しない場合には Http404 を送出することは非常によく使われるイディオムです。
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()を使って省力して記述できる。
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, "polls/detail.html", {"question": question})
- polls/templates/polls/detail.htmlを以下のように修正。
- choice_set()はそのquestionに紐づくchoiceが取得される。
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
- polls/index.htmlを以下のように修正
- 以下のように書くことでurls.pyのnameの値がdetailのpath()を呼び出せる。
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
- pollsのurls.pyにて名前空間を設定し、他のアプリのurls.py特別できるようにしてあげる
# polls/urls.py
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"),
]
- 今回はアプリが1つだけなので、上記の書き方で、pollsのurls.pyが呼び出されるが、アプリが複数の場合、どのアプリのurls.pyかを指定する必要がある。それがいか。
# polls/templates/polls/index.html
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
はじめての Django アプリ作成、その 4
- polls/templates/polls/detail.htmlを以下のように修正
- legend
- fieldsetタグでグループ化されたフォームの入力項目に対して、タイトルを付けるためのタグ。
- forloop.counter は、 for タグのループが何度実行されたかを表す値。
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
<legend><h1>{{ question.question_text }}</h1></legend>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
{% 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 %}
</fieldset>
<input type="submit" value="Vote">
</form>
- polls/views.pyのvote関数を以下のように修正
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,)))
def results(request, question_id):
# response = "You're looking at the results of question %s."
# return HttpResponse(response % question_id)
question = get_object_or_404(Question, pk=question_id)
return render(request, "polls/results.html", {"question": question})
- polls/templates/polls/results.htmlを作成
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
- polls/urls.pyを以下のように修正( <question_id> から に変更)
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"),
]
はじめての Django アプリ作成、その 5
- polls/tests.py
- ssertIs(a, b) というメソッドは、「a is b」となるかどうかのテストを実行しており、was_published_recently() の出力が False となっていれば、テストは成功となる
import datetime
from django.test import TestCase
from django.utils import timezone
from .models import Question
class QuestionModelTests(TestCase):
def test_was_published_recently_with_future_question(self):
"""
was_published_recently() returns False for questions whose pub_date
is in the future.
"""
# 現在日時から30日後の日時を取得
time = timezone.now() + datetime.timedelta(days=30)
# 上記の日時を使ってQuestionインスタンスを作成
future_question = Question(pub_date=time)
# 上記のQuestionインスタンスをつかってwas_published_recently()を呼び出し真偽値を確認
self.assertIs(future_question.was_published_recently(), False)
# modelsのwas_published_recentlyに定義した通り、pub_dateが1日前から現在日時までの間であればtrueが返される
# そのwas_published_recentlyの返り血がFalseなら上記テスト自体はTrueとはんていする仕様
はじめての Django アプリ作成、その 6
- templatesと同じようにpollsディレクトリはいかにstaticディレクトリを作る。
- そこへstyle.cssも作成
- polls.static/polls/style.css
- style.cssに以下を記載
li a {
color: green;
}
{% load static %}
<link rel="stylesheet" href="{% static 'polls/style.css' %}">
body {
background: white url("images/background.png") no-repeat;
}
- polls/static/polls/ ディレクトリの中に images サブディレクトリを作成
polls/static/polls/images/background.png となるようにする。
polls/static/polls/style.cssに以下の内容を追記
body {
background: white url("images/background.png") no-repeat;
}
終わりに
今回のDjangoチュートリアルを通して、Djangoの基本的な使い方やアプリケーション作成の流れを体験し、理解が深まりました。
今後はより複雑なプロジェクトや、実際の業務でDjangoを活用できるようスキルを磨いていきたいと思います。
それでは、また次回の記事でお会いしましょう!