Djangoで、ブログ等によくある関連記事欄を作る

2019-02-26 / PythonDjango

概要

ブログではよく、「関連記事」のようなリンクがあります。このブログにもありますね。
関連記事のリンク

今回はこれを実装するにあたって、2つの方法を紹介します。

models.py

次のようなモデルを例に考えていきます。タイトルと本文を持つ記事のモデルです。

from django.db import models


class Post(models.Model):
    title = models.CharField('記事タイトル', max_length=255)
    text = models.TextField('記事本文')

    def __str__(self):
        return self.title

base.html

Bulmaのスターターテンプレートです。

JavaScriptを使って配置する方法も紹介するので、{% block extra_js %}としてjsの追加をしやすくしておきます。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Hello Bulma!</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
</head>
<body>
{% block content %}{% endblock %}
{% block extra_js %}{% endblock %}
</body>
</html>

post_detail.html

記事詳細を左側に、関連記事欄を右側に配置しておきます。

JavaScriptを使って配置する方法も紹介するので、関連記事にはid="relation-posts"としておきます。

{% extends 'app/base.html' %}

{% block content %}
    <section class="section">
        <div class="container">
            <div class="columns">
                <article class="column is-9 content">
                    <h1 class="title">{{ post.title }}</h1>
                    {{ post.text | safe | linebreaks }}
                </article>
                <aside class="column is-3" id="relation-posts">
                    <h2 class="title">関連記事</h2>
                </aside>
            </div>

        </div>
    </section>
{% endblock %}

ビューは単純なgeneric.DetailViewを使っておきます。

ここまでで、見た目は次のような感じになります。
最初の見た目

JavaScriptを使う方法

まずはJavaScriptを使った関連リンクの作成方法です。記事の本文中にあるリンク...今回ならば「WindowsでのPythonインストール方法」と「MacでのPythonインストール」というリンクがありますが、これを基に関連記事リンクを作成します。

{% block extra_js %}
    <script>
        let isFound = false;  // 関連記事の作成
        const pathList = new Set();  // URLのpath部分が詰まったセット
        const ulElement = document.createElement('ul');

        // 記事中のa要素を1つずつ取り出す
        for (const a of document.querySelectorAll('article.content a')) {
            const url = new URL(a.href);
            // ドメインは同じだが、パス部分がこの記事と違っていて、まだ追加していないa要素を関連記事として登録
            if (url.hostname === document.domain && url.pathname !== location.pathname && !pathList.has(url.pathname)) {
                const liElement = document.createElement('li');
                liElement.appendChild(a.cloneNode(true));
                ulElement.appendChild(liElement);
                pathList.add(url.pathname);
                isFound = true;
            }
        }
        if (isFound) {
            document.getElementById('relation-posts').appendChild(ulElement);
        }
    </script>
{% endblock %}

次のような感じで、ちゃんと登録されますね。
JavaScriptで関連記事を追加した

関連記事は、次のようなルールで登録しています。

ManyToManyFieldを使う方法

モデルを次のように変更しておきます。

class Post(models.Model):
    title = models.CharField('記事タイトル', max_length=255)
    text = models.TextField('記事本文')
    relation_posts = models.ManyToManyField('self', verbose_name='関連記事', blank=True)

    def __str__(self):
        return self.title

relation_posts = models.ManyToManyField('self'の部分が重要です。Djangoで、コメントへの返信を無限に取り出すでもこのselfを使いました。

これにより、管理画面では次のように関連記事が指定できます。
関連記事を指定する様子

記事数が多くなってくると、関連記事の指定がちょっと面倒になってきます。100記事ある選択欄の中から、関連しそうな記事を手動で選ぶのはつらいものがあります。

その場合はDjangoで、タグのサジェスト機能つきフォームでやったように、関連記事をサジェストして登録できるようにしておくと捗ります。

post_detail.htmlでは、次のように関連記事を取り出せます。

                <aside class="column is-3" id="relation-posts">
                    <h2 class="title">関連記事</h2>
                    {% for rel_post in post.relation_posts.all %}
                         <a href="{% url 'app:post_detail' rel_post.pk %}">{{ rel_post.title }}</a>
                    {% endfor %}
                </aside>

この記事の関連記事

Djangoで、サジェスト機能付きフォームを作る

2018-12-30 / PythonDjangoBulmaJavaScriptAjaxDjangoカスタムウィジェット

- DjangoとJavaScriptを使い、入力内容に応じてデータをサジェストするフォームを作成していきます。

Djangoで、コメントへの返信を再帰的に取り出す

2019-02-26 / PythonDjango

- ブログ等でよくある、コメントを再帰的に取り出すサンプルです。

コメント欄

記事にコメントする

まだコメントはありません。