超初心者用: Djangoでウェブアプリを作る ④追加ページの作成

前回はホームページを作りました。

今回はそれ以外のページを作っていきます。トピックのリストのページとそれぞれのトピックの Entry のページの2ページを作ります。それぞれのページにURLパターンをつけ、view を書き、template を書きます。

その前にすべてのテンプレートで使う base template を作ります。

テンプレートの継承

ウェブサイトを作るとき、どのページでも必ず存在する要素があります。例えばロゴとかナビゲーションバーとか。このコードを枚描くのではなく、base template として繰り返すことができます。このようにすることで、時間がだいぶ節約できます。

Parent template

index.html と同じ階に base.html という template を作成します。このファイルがすべてのページに適応されることになります。つまり、すべての template が base template を受け継ぎます。今回は、タイトルだけです。タイトルにホームページへのリンクをつけましょう。

<!--base.html-->
<p>
    <a href="{% url 'practicing_logs:index' %}">Practicing Log</a>
</p>
{% block content %}{% endblock content %}

リンクを生成するには、tamplate tag の {% %} を使います。url 'practicing_logs:index' は practicing_logs という app_name の practicing_logs/urls.py 内の index という名前のURL を意味しています。

URLを直接入力するのではなくこのようにかくことで、URLを変更したときに修正する必要がなくなります。つまり URLを変えるときは、urls.py を変えるだけでOKです。

すべてのページがこの base template を持つことになるので、もうタイトルの記入は必要ありません。この template を引っ張ってくるだけです。

{% block content %}{% endblock content %}

これは content と呼ばれ、ほかページの内容がここに追加されます。

Child template

base template ができたので、ホームページを書き換えましょう。

{% extends "practicing_logs/base.html" %}
    {% block content %}
        <p>Practicing Log helps you keep track of your pracitcing, for any topic you're placticing about.</p>
    {% endblock content %}

{% extends "practicing_logs/base.html" %}

元の index.html と比較すると、タイトル部分が base template に置き換わっています。ほかの template を受け継ぐには、{% extends %} tag が必要になります。今回は practicing_logs 内の base.html を受け継ぐため、 practicing_logs/base.html と表記します。

{% block content %}

先ほど見かけた content が早速登場しました。parent template から受け継いでいない要素はすべてこの中に入ります。今回は文章が入ります。最後には {% endblock content %} tag で閉じます。

このように、すべてのページにおいて変わらない要素を別の template で作ることで、子ページには呼ぶだけで利用することができます。また、編集をしたいときには base template のみ編集するだけで全ページに適応されます。

Topics pageを作成する

では追加ページを作る本題に入っていきます。まずはトピックページです。このページでは、ユーザーが入力したすべてのトピックをリストとして表示させます。つまりデータを扱うことになります。

topics URL pattern

まずはトピックページの URL を定義します。できるだけシンプルなものにしましょう。今回は topics にします。つまり、http://localhost:8000/topics/です。practicing_logs/urls.py を変更します。

#practicing_logs/urls.py
urlpatterns = [
    # Home page
    url(r'^$', views.index, name='index'),
    #-------------------ここから下を追加---------------------
    #topics のリンク
    url(r'^topics/$', views.topics, name='topics'),
]

「r'^topics/$」 は topics/ のURL を表しています。つまり base URL + topics/ となる URL の時に適応されます。URL が一致するときは views.py の topics() を呼びます。

Topics view

topics() function はデータベースからデータを取得し、template に送る必要があります。

# views.py
from django.shortcuts import render  <-- すでにある
from .models import Topic

def index(request):          <-- すでにある
   --省略--

def topics(request):
    """すべての topics を表示する"""
    topics = Topic.objects.order_by('date_added')
    context = {'topics': topics}
    return render(request, 'practicing_logs/topics.html', context)

from .models import Topic

まずは必要となるデータに基づいたモデルをインポートします。

def topics(request):

topics() function は request parameter が必要です。

topics = Topic.objects.order_by('date_added')

ここでTopicを通すことでデータベースからデータを取得します。date_added attribute によって追加された順に topics に保存していきます。

context = {'topics': topics}

template に送るための context を定義します。context は dictionary で、key がデータを呼ぶための名前で、value がデータ自身です。この場合、1つの key に対して複数の value が存在し、それらは set として保存されています。

return render(request, 'practicing_logs/topics.html', context)

render() を使って context の内容を practicing_logs/topics.html に送っています。

Topics template

トピックページの template は context のデータを受け取っています。だから、ここで使うことができます。index.htmlと同じ階にtopics.htmlを作成してください。

# topics.html
{% extends "practicing_logs/base.html" %}
{% block content %}
    <p>Topics</p>
        <ul>
            {% for topic in topics %}
                <li>{{ topic }}</li>
            {% empty %}
                <li>No topics have been added yet.</li>
            {% endfor %}
        </ul>
{% endblock content %}

まずは {% extends %} tag を使って、このページにタイトルを付けます。{% block content %} からこのページ独特の内容を書き込みます。リストとして表示されるように unordered list の

    を作ります。

    {% for topic in topics %}

    ここでは for loop を使って context 内の topics をループします。ループの終わりには{% endfor %}と書きます。

    ループの中では topic を順番に表示させます。{{ topic }}と書くことで key である topic の value が表示されます。

    {% empty %}

    これは if節でもしトピックがなかったらこれ以降を実行します。今回はトピックがないというメッセージを表示させます。

    では、ナビゲーションとして base template にトピックページを追加しましょう。

    # base.html
    <p>
        <a href="{% url 'practicing_logs:index' %}">Practicing Log</a>
        <a href="{% url 'practicing_logs:topics' %}">Topics</a>
    </p>

    Topics

    practicing_logs という名前の app_name から topics という名前の URL パターンを示しています。

    トピックページはこんな感じになっているはずです。

    f:id:kendamaaa:20200425163349j:plain

    それぞれのTopicページ

    次は、トピック全体ではなく1つのトピックに焦点を置いたページを作っていきます。このページにふくまれるのは、トピック名とそのトピックの entries です。再び URLパターンを作成していきます。さらに先ほど作ったトピックページも編集し、これから作るぺージに飛べるようにします。

    Topic URL pattern

    これから作るトピック専用ページの URLパターンは今までのURLパターンと少し異なります。トピックの id を利用します。例えば、id が1であるChess トピックページを見たいときは、http:// localhost:8000/topics/1/となります。

    # practicing_logs/urls.py
    urlpatterns = [
        #--省略--
        # トピックの詳細ページ
        url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
    ]

    r'^topics/(?P<topic_id>\d+

    r は string を raw string として扱うように示しています。(?P<topic_id>\d+ の「\」までの中身はトピックの id 意味します。

    このパターンに合うURL が見つかったときに、id とともに topic() function を呼びます。

    Topic view

    topic() function はトピック名とそれの基づいたすべての entries のデータを取得します。

    # views.py 
    --省略--
    def topic(request, topic_id):
        """1つのトピック名とそれのすべての entries を表示"""
        topic = Topic.objects.get(id=topic_id)
        entries = topic.entry_set.order_by('-date_added')
        context = {'topic': topic, 'entries': entries}
        return render(request, 'practicing_logs/topic.html', context)

    今回は request だけではなく topic_id という parameter を使います。topic_id は (?P<topic_id>\d+) で取得したものです。

    topic = Topic.objects.get(id=topic_id)

    get() を使ってその id のトピック名を取得します。

    entries = topic.entry_set.order_by('-date_added')

    そのトピックに関連した entries を時間の新しい順「-」に取得します。こうすることで最新情報を一番上に表示することができます。

    取得したデータを context dictionary に保存します。最後に template topic.html に context を送ります。

    Topic template

    この template ではトピック名と entries を表示させる必要があります。さらに entry が1つもなかった場合、その旨も知らせなければなりません。

    # topic.html
    {% extends 'practicing_logs/base.html' %}
    {% block content %}
        <p>Topic: {{ topic }}</p>
        <p>Entries:</p>
        <ul>
            {% for entry in entries %}
                <li>
                    <p>{{ entry.date_added|date:'M d, Y H:i' }}</p>
                    <p>{{ entry.text|linebreaks }}</p>
                </li>
            {% empty %}
                <li>
                    There are no entries for this topic yet.
                </li>
            {% endfor %}
        </ul>
    {% endblock content %}

    まずはいつも通り、base template を組み込みます。そして block content が始まることを知らせます。

    Topic: {{ topic }}

    一番上にトピック名を表示させます。{{ topic }} は context から引っ張ってきます。

    {% for entry in entries %}

  • {{ entry.date_added|date:'M d, Y H:i' }}

    {{ entry.text|linebreaks }}

  • for loop を使って entries の日付と内容を1つずつ表示させていきます。「|」は表示方法のフォーマットを定義しています。

    {% empty %}

  • There are no entries for this topic yet.
  • entry がない場合はこちらが実行されます。最後に for loop を閉じて終わり。

    Topicsページにリンクをつける

    個々のトピックページを見るためには、全体のトピックページにリンクをつけなければなりません。

    #--省略--
        {% for topic in topics %}
            <li>
                <a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a>
            </li>
        {% empty %}
    #--省略--

    topic の名前によって適切な URL を使わなければなりません。URL パターンは topic_id を必要としているので、URL tag に topic_id を付け加えました。今、http://localhost:8000/topics/1/ に飛ぶと、Chess の詳細が見えるはずです。

    f:id:kendamaaa:20200426050855j:plain