Help us understand the problem. What is going on with this article?

Djangoでフォロー機能を実装する

前段

SNS系アプリにほぼ必須のフォロー機能をDjangoで実装しました。
ライブラリ不使用の自前実装になります。
各自用途や要件に合わせてカスタマイズしてください。

開発環境

  • Django
  • Python 3.8.5
  • MySQL
  • Visual Studio Code

※SQLite3でもほぼコード変更なしで書けます。

フォロー機能の具体的な要件

  • フォローすることができる/解除もできる
  • 自分自身をフォローすることはできない
  • フォロー数、フォロワー数を確認できる

フォロワーの一覧表示などはここでは省きます。
必要最小限の機能要件を実装します。

テーブル設計

テーブルを1つだけ作成して済ましてしまう方法もありますが、個人的にSQL文が冗長になったり、テーブル管理がしにくい印象を持ったので今回は2つ用意します。

follow_relationテーブル

データ 格納する情報 データ型
user_id フォローする側のユーザーID UUID
followed_id フォローされる側のユーザーID UUID

follow_countテーブル

データ 格納する情報 データ型
user_id ユーザーID UUID
follow フォローしている数 INT
followed フォローされている数 INT

follow_relationテーブルは、ユーザー間のフォロー状態の管理、follow_countテーブルはユーザーのフォロー数、フォロワー数の管理をしています。

views.pyの編集

テーブルができたらさっそくフォロー機能を実装していきましょう。
フォロー機能のおおまかな流れは以下のようになります。

ページのリクエスト
↓
followd_status関数にてフォロー状態を確認&表示
↓
フォローボタンを押す
↓
follows関数がフォロー状態に応じて処理
↓
元のページにリダイレクト

では以下コードになります。

followed_status関数
@login_required
def followed_status(request, accesskey):
    # 省略
    # 各自必要なパラメータを取得してください

    # データベースへの接続
    connection = MySQLdb.connect(
    host='localhost',
    user='***',
    passwd='***',
    db='***',
    charset="utf8"
    )  

    # カーソルの取得
    cursor = connection.cursor() 

    # クエリのセット
    follow_relation = "SELECT COUNT(*) FROM follow_relation where user_id = UUID_TO_BIN(%s) and followed_id = UUID_TO_BIN(%s);"

    # クエリの実行
    # user_id、followed_idは各自で取得
    cursor.execute(follow_relation, (user_id, followed_id))
    row_follow = cursor.fetchone()
    follows = row_follow[0]

    # ログインユーザーとページユーザーが同一人物か判定
    # 同一人物ならフォローできないようにします
    if followed_id == user_id:
        followed_status_code = 1
    else:
        followed_status_code = 2

    # もしfollowsが0より大きかったらTrue
    # followsが0だったらFalseを返す
    followed = False
    if follows > 0 :
        followed = True

    return followed_status_code, followed

必要なパラメータは各自別テーブルから引き出してください。
今回必要なのはフォローする側とされる側のIDのみです。
user_idがフォローする側、followed_idがフォローされる側です。

  • ユーザー同士のIDを照合します

ステータスコードを付与して同一人物か判別します。

followed_status_code = 1なら自分自身(フォローできない)
followed_status_code = 2なら他人同士(フォローできる)

になります。

  • フォロー状態を確認します

TrueとFalseでフォロー状態を確認します。

Falseならまだフォローしていない
Trueなら既にフォローしている

になります。
では、次にfollows関数を見ていきます。

follows関数
@login_required
def followes(request, accesskey):
    # 省略
    # 各自必要なパラメータを取得してください

    # データベースへの接続
    connection = MySQLdb.connect(
    host='localhost',
    user='***',
    passwd='***',
    db='***',
    charset="utf8"
    )  

    # カーソルの取得
    cursor = connection.cursor()

    # 共通クエリをセットしてください
    # ここで必要な情報を取得しておきます

    # 自分自身はフォローできないようにする
    if user_id == followed_id:
        followed = False
        # セッションにフォロー状態を保存します
        request.session['page_followed_status'] = followed
    else:
        if followed == False:
            # フォローするときの処理になります
            # クエリのセット
            # 新規フォロー関係の挿入
            follow_reation = "INSERT INTO follow_relation (user_id, followed_id) values(UUID_TO_BIN(%s), UUID_TO_BIN(%s));"

            # フォローする側ユーザーのフォロー数を1つ増やす
            follow_update = "UPDATE follow_count SET follow = follow + 1 where user_id=UUID_TO_BIN(%s);"  

            # フォローされる側ユーザーのフォロワー数を1つ増やす
            followed_update = "UPDATE follow_count SET followed = followed + 1 where user_id=UUID_TO_BIN(%s);"

            # クエリの実行
            cursor.execute(follow_relation, (user_id, followed_id))

            cursor.execute(follow_update, (user_id, ))

            cursor.execute(followed_update, (followed_id, ))

            # 接続を終了する
            cursor.close()
            connection.commit()
            connection.close() 

            # フォローした状態にする
            followed = True
            request.session['page_followed_status'] = followed

        else:
            # フォローを外すときの処理になります
            # クエリのセット
            # フォロー関係の削除
            follow_relation = "DELETE FROM follow_relation where user_id=UUID_TO_BIN(%s) and followed_id = UUID_TO_BIN(%s);"

            # フォローを外す側ユーザーのフォロー数を1つ減らす
            follow_update = "UPDATE follow_count SET follow = follow - 1 where user_id=UUID_TO_BIN(%s);"

            # フォローを外される側ページユーザーのフォロワー数を1つ減らす
            followed_update = "UPDATE follow_count SET followed = followed - 1 where user_id=UUID_TO_BIN(%s);"

            # クエリの実行
            cursor.execute(sql_followrelation_delete, (user_id, followed_id))

            cursor.execute(sql_follow_update, (user_id, ))

            cursor.execute(sql_followed_update, (followed_id, ))

            # 接続を終了する
            cursor.close()
            connection.commit()
            connection.close() 

            # フォローしていない状態にする
            followed = False
            request.session['page_followed_status'] = followed

    return redirect('元のページ', accesskey)

こちらはif文で分岐させてフォローするorフォロー解除の実際の処理をおこなっています。そして最後にフォローの状態を切り替えています。元のページにリダイレクトして終了です。(今回はAjaxなどを使用しないためページ遷移が生じています)

最後に表示用のviewを編集します。

result関数
@login_required
def result(request, accesskey):

    # 省略

    # フォローの状態を把握する
    status = followed_status(request, accesskey)
    followed_status_code = status[0]
    followed = status[1]
    # フォローの状態をセッションに保存(一時的)
    request.session['page_followed_status'] = followed

    params = {
        # 省略

        'page_followed_status': followed,
        'page_followed_status_code': followed_status_code,
    }
    return render(request, 'result.html', params)

該当箇所のみ記載しています。
followed_status関数を呼び出してフォロー状態を確認しています。
(followed_status関数では、ステータスコードとフォロー状態が返されます。)
そしてセッションに一時的に保存してます。

urls.pyの編集

urls.py
urlpatterns = [
    # 省略
    path('元のページ', views.pages, name='各自で設定'),
    path('元のページ/follow/', views.follows, name="follows"),
    # 省略
]

テンプレートの編集

最後にテンプレートです。

result.html
            <!-- フォローボタンを追記 -->
            {% if page_followed_status_code is 2 %}
                {% if page_followed_status is False %}
                <p><a href="{% url 'follows' page_accesskey %}">フォローする</a></p>
                {% else %}
                <p><a href="{% url 'follows' page_accesskey %}">フォローをやめる</a></p>
                {% endif %}
            {% endif %}

ステータスコードが2の場合(自分自身でなく他人同士の場合)のみ表示されるようになっています。

そしてフォロー状態がFalseのとき「フォローする」、Trueのとき「フォローをやめる」と表示させるためif分岐させています。

リンクが押されるとページ移動先でfollows関数が呼び出されフォロー処理をして、また元のページにリダイレクトされるような流れになってます。

最後に

自分なりにコードを改変して使ってみてください。
「いいね機能」もほぼ同様に実装することができます!

senobi_qiita
Java / Python / Django /Cassandra / MySQL 日本ちょこみん党の党首。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away