Django + microCMSでつくるブログサイト ③サムネイルとタグの設定

「Django + microCMSでつくるブログサイト」シリーズの3番目の記事です。
今回はブログにサムネイル画像と記事のタグを設定していきます。

microCMSの編集

まずはmicroCMSの編集をしていきます。
記事ごとにタグを設定したいので、新しくタグAPIを作りましょう。

postAPIの中にタグフィールドを用意してもいいのですが、microCMSには外部参照機能があるので、そちらを利用します。

のちのちのことを考えると、そうした方がタグで絞った検索等がやりやすいです。


microCMSの管理ページから、コンテンツ(API)の右にある`+`ボタンをクリックするとAPIを追加できます。



上のように設定します。


単純にタグ名だけでもいいのですが、sortOrderというフィールドも加えました。

この辺りは好みだとは思いますが、例えばDjango,Pythonというタグを振っていた場合、`Python`が先にあった方がすっきりします。

そのあたりの順番を設定できるようにフィールドに情報を持たせておきます。


詳細設定をクリックすると、投稿者が分かりやすいように説明文を追記できます。こういうかゆい所に手が届く機能が本当にありがたいです…

登録が完了したら、タグを追加していきます。


Pythonは一番最初に表示したいので、順番は0です。


DjangomicroCMSも追加しました。

順番は全て1です。

ポストAPIの変更



postAPIのAPI設定からフィールドを追加していきます。

thumbnailと`tag`ですね。



tagの種類は複数コンテンツ参照を選択します。



さきほど作成したtagAPIを選択します。

記事の投稿画面




記事の投稿画面に行きますと、サムネイルとタグが設定できるようになります。


タグの追加するをクリックすると、さきほど作成したタグが一覧で表示されますので、クリックして追加できます。

左上のほうをみると、検索する機能もあります。

こういったタグはどんどん増えていきますので、検索機能がデフォルトであるのはうれしいですね。



適当な画像をアップロードし、タグも全て追加しました。

その他は適当に埋めて公開します。

これでmicroCMS側の設定は終了です。

一覧API取得値の確認


フィールドを追加した結果、以下のように一覧が返ってきます。
一度確認しておきましょう。

[{'createdAt': '2021-08-07T00:23:42.043Z',
 'description': 'サムネイルとタグの挙動をテストします。',
 'id': 'thumbnail-and-tag-test',
 'keywords': 'microCMS サムネイル',
 'publishedAt': '2021-08-07T00:30:55.419Z',
 'revisedAt': '2021-08-07T00:30:55.419Z',
 'tag': [{'createdAt': '2021-08-07T00:19:52.757Z',
      'id': 'yg4-8dn8d-n9',
      'name': 'microCMS',
      'publishedAt': '2021-08-07T00:19:52.757Z',
      'revisedAt': '2021-08-07T00:19:52.757Z',
      'sortOrder': 1,
      'updatedAt': '2021-08-07T00:19:52.757Z'},
     {'createdAt': '2021-08-07T00:19:34.205Z',
      'id': 'ingpdu3m9gc',
      'name': 'Django',
      'publishedAt': '2021-08-07T00:19:34.205Z',
      'revisedAt': '2021-08-07T00:19:34.205Z',
      'sortOrder': 1,
      'updatedAt': '2021-08-07T00:19:34.205Z'},
     {'createdAt': '2021-08-07T00:11:55.642Z',
      'id': 'vt247rw4jt',
      'name': 'Python',
      'publishedAt': '2021-08-07T00:11:55.642Z',
      'revisedAt': '2021-08-07T00:19:21.482Z',
      'sortOrder': 0,
      'updatedAt': '2021-08-07T00:19:21.482Z'}],
 'text': '<h2 '
     'id="h6f9634bf3b">この記事の内容</h2><p>サムネイル画像を設定しました。<br>タグを設定しました。<br></p>',
 'thumbnail': {'height': 540,
        'url': 'https://images.microcms-assets.io/assets/24593cd102554657b424fa2ebe3589bd/b7dd1db75af948428a218f621b690e66/image1.jpg',
        'width': 959},
 'title': 'サムネイルとタグのテスト',
 'updatedAt': '2021-08-07T00:30:55.419Z'},

...省略 次の記事が続く
]


Djangoファイルの編集

次にDjango内のファイルを編集していきます。

一覧画面の編集


index.htmlを以下のようにします。

<!-- blog/templates/blog/index.html -->
{% extends "blog/base.html" %}
{% load blog %}
{% load static %}
{% block content %}

<div class="container" style="padding-top:112px;">
 {% for post in post_list %}
  <article class="article">
   <div class="thumbnail">
    <a href="{% url 'blog:post_detail' post.id %}">
     {% if post.thumbnail %}
      <img class="post-thumbnail" src="{{ post.thumbnail.url }}" alt="{{ post.title }}">
     {% else %}
      <img class="post-thumbnail" src="{% static 'images/noimage.png' %}" alt="{{ post.title }}">
     {% endif %}
    </a>
   </div>
   <div class="detail">
    <p class="created-at">{{ post.createdAt | date_from_isoformat }}</p>
    <a href="{% url 'blog:post_detail' post.id %}">
     <h1 class="post-title" style="margin-top:1rem;">{{ post.title }}</h1>
    </a>
    <p class="post-description">{{ post.description | linebreaks }}</p>

    {% if post.tag %}
    <div class="tag">
      <span class="post-tag">Tags:</span>
      {% for tag in post.tag %}
       <span class="post-tag" style="margin-left:3px;">{{ tag.name }}</span>
       {% if not forloop.last %}
        <span style="margin:0 3px; color:#888">/</span>
       {% endif %}
      {% endfor %}
    </div>
    {% endif %}
   </div>
  </article>
 {% endfor %}
</div>

{% endblock %}

まず、サムネイルの部分ですが、記事にサムネイルを設定しないということもあると思うので、if文で分岐させています。

`html
`

<div class="thumbnail">
	<a href="{% url 'blog:post_detail' post.id %}">
		{% if post.thumbnail %}
			<img class="post-thumbnail" src="{{ post.thumbnail.url }}" alt="{{ post.title }}">
		{% else %}
			<img class="post-thumbnail" src="{% static 'images/noimage.png' %}" alt="{{ post.title }}">
		{% endif %}
	</a>
</div>


static配下にimageディレクトリを作り、noimage.pngという名前で、サムネイルがない場合の表示画像を保存しています。
適当にネットでno imageと検索すれば拾えると思います。

次にタグの部分は以下のようになっています。

{% if post.tag %}
	<div class="tag">
		<span class="post-tag">Tags:</span>
		{% for tag in post.tag %}
			<span class="post-tag" style="margin-left:3px;">{{ tag.name }}</span>
			{% if not forloop.last %}
				<span style="margin:0 3px; color:#888">/</span>
			{% endif %}
		{% endfor %}
	</div>
{% endif %}



これも同様にif文で分岐させています。

あとは順番に表示させるだけですね。
{% if not forloop.last %}以下ブロックは、区切り文字の制御のためです。
何もしないと、DjangoPythonmicroCMSのように表示されてしまうので、間に/を入れています。

ただし Django / Python / microCMS /という風に最後にスラッシュが入るとかっこわるいです。
なので、if文で繰り返しの最後以外の時はスラッシュを入れる、という風にしています。



ここまででこのように表示されるようになります。

cssで整える


よくある画像を左、記事タイトルを右に配置するレイアウトです。

一覧ページのhtmlのブロックをまとめると、下記のようになっています。

<article class="article">
	<div class="thumbnail">
		画像
	</div>
	<div class="detail">
	日付
	タイトル
		<div class="tag">
			タグ
		</div>
	</div>
</article>


画像ブロックと記事ブロックを横並びにし、記事ブロックの中のタグブロックをボトムに配置、という形にします。

/* blog/static/css/style.css */

@charset "UTF-8";

...省略

/* --------------------------------
 * 記事一覧
 * -------------------------------- */

.article {
 margin-bottom: 6rem;
 width: 100%;
 /*追加 横並びの設定*/
 display: flex;
 justify-content: space-between;
}

/* 追加 articleの40% */
.thumbnail {
 width: 40%;
}

/* 追加 articleの55% */
.detail {
 width: 55%;
 position: relative;
}

/* 追加 タグは一番下 */
.tag {
 position: absolute;
 bottom: 0;
}

.created-at {
 font-size: 1.4rem;
 color: 888;
}

.post-title {
 font-size: 2.0rem;
 color: #0d1a3c;
 line-height: 1.6;
 letter-spacing: 1px;
}

/* 追加 タグのスタイル */
.post-tag {
 font-size: 1.2rem;
 color: 888;
 opacity: 0.7;
 letter-spacing: 1px;
}

/*追加 記事詳細のスタイル*/
.post-description {
 margin-top: 1rem;
 font-size: 1.4rem;
 color: #0d1a3c;
 line-height: 1.6;
 letter-spacing: 1px;
}

/* 追加 画像のスタイル*/
.post-thumbnail {
 width: 100%;
 height: auto;
}

...省略


ここまでで以下のような表示になります。



レスポンシブ対応も行っておきましょう。
画像幅が狭まったときに画像と文字が縦に並ぶようにします。

/* 追加 */

/* 完全に縦に並べる */
@media(max-width:768px) {
 .article {
  flex-direction: column;
 }

 .thumbnail {
  width: 100%;
 }

 .detail
 {
  width: 100%;
  margin-top: 10px;
  padding-left: 0;
 }

 .tag {
  margin-top: 10px;
  position: relative;
 }
}

/* 横並びだけど、記事部分がせまくなる。
そのため、文字が被る可能性があるので、タグ部分のbottom配置を解除 */
@media(max-width:992px) {
 .tag {
  margin-top: 10px;
  position: relative;
 }
}


768px以下は完全に縦に並べます。



768px以上、992px以下は横並びだけど、タグの部分のボトム配置を解除します。



相対的にスペースがせまくなるので、例えば、タイトルや詳細文が長い記事だと、tagの部分が文字被りを起こす可能性があるためです。

タグの並び順を指定する


現状はmicroCMS,Django,Pythonの順で並んでいます。

これをタグのAPIを作るときに設定したsortOrder順に並び変えたいです。
ついでにABC順で並んだ方がいいでしょう。

関係のない部分を端折りますが、postのtagの中身はこのような構成になっています。

[
  {
    'name': 'microCMS',
    'sortOrder': 1
  },
  {
    'name': 'Django',
    'sortOrder': 1
  },
  {
    'name': 'Python',
    'sortOrder': 0
  }
]


辞書のリストなので、key引数を用いたsort関数で並び替えます。

このようなケースではフィルターを自作するのが最短でしょう。
blog.pyを以下のように編集します。

# blog/templatetags/blog.py

from django import template
import datetime

register = template.Library()

...省略

# 追加
@register.filter
def sorted_post_tag(post_tag):
  """ポストに紐づいたタグを並び替える"""
  post_tag.sort(key=lambda x: (x['sortOrder'], x['name']))
  return post_tag


今回のように複数指定する際はkey=lambda x: (x['sortOrder'], x['name'])というようにタプルで指定します。
後ろから評価されるので、名前順→sortOrder順で並び替えられます。

次にindex.htmlの編集です。

<div class="tag">
	<span class="post-tag">Tags:</span>
	<!-- 作成したフィルターを呼び出す -->
	{% for tag in post.tag|sorted_post_tag %}
		<span class="post-tag" style="margin-left:3px;">{{ tag.name }}</span>
		{% if not forloop.last %}
			<span style="margin:0 3px; color:#888">/</span>
		{% endif %}
	{% endfor %}
</div>



意図している通りに並びました。

詳細ページの編集


詳細ページにも画像とタグを表示させます。
ここは新しいことはないです。

<!-- blog/templates/blog/post_detail.html -->

{% extends 'blog/base.html' %}
{% load blog %}
{% load static %}
{% block meta_title %}{{ post.title }} - {{ block.super }}{% endblock %}
{% block meta_description %}{{ post.description }}{% endblock %}
{% block meta_keywords %}{{ post.keywords }}{% endblock %}

{% block content %}

<div class="container" style="padding-top:112px;">
 <article class="post">
  <!-- 追加 サムネイル-->
  {% if post.thumbnail %}
   <img class="post-thumbnail" src="{{ post.thumbnail.url }}" alt="{{ post.title }}" style="margin-bottom:20px;">
  {% endif %}
  <!-- 追加 タグ -->
  {% if post.tag %}
   <div style="margin-bottom:10px;">
     {% for tag in post.tag|sorted_post_tag %}
      <span class="post-tag" style="margin-left:3px;">{{ tag.name }}</span>
      {% if not forloop.last %}
       <span style="margin:0 3px; color:#888">/</span>
      {% endif %}
     {% endfor %}
    </div>
  {% endif %}
  <p class="created-at">{{ post.createdAt | date_from_isoformat }}</p>
  <h1 class="post-title">
   {{ post.title }}
  </h1>
  <div class="markdown-body">
   {% autoescape off %}
   {{ post.text }}
   {% endautoescape %}
  </div>
 </article>
</div>

{% endblock %}

{% block extrajs %}
<!-- コードハイライト -->
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.16.2/build/styles/hybrid.min.css">
<script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.16.2/build/highlight.min.js"></script>
<script type="text/javascript">
 hljs.initHighlightingOnLoad();
</script>
{% endblock %}


TOPページ