1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

定期ミーティングの準備を快適に

Posted at

はじめに

この記事は「定期ミーティングの準備を簡単に」の続編(開発したプロダクトのアップデート)だが、改めて開発動機からプロジェクトについて書いていこうと思う。

開発の経緯

大学で行われる定期ミーティング。組織のメンバのグループ分けを手動で名札を並び替えて行っていた。しかし、毎週手動で行うのは負担が大きく、毎年度名札を作り増すのは更に負担だった。そこで、メンバが使い慣れているLINEを用いてグループ分けシステムを開発した。

設計の前に

上で書いたグループ分けの機能を実装するにあたって、欠席メンバの特定が必要なことに気が付いた。なぜなら、在籍メンバの集合から欠席メンバの集合を引いた差が出席予定のメンバの集合となるからである。そこで今回は、「グループ作成機能」と「欠席管理機能」を併せて実装することにした。

設計

技術スタック

技術スタック 言語・フレームワーク 補足
バックエンド Django
フロントエンド LINE, tailwindcss
ミドルウェア PostgreSQL Supabase
LINE MessagingAPI
インフラ Docker DevContainer
デプロイ Render 無料枠※1

※1 Renderの無料枠では15分操作がないとスピンダウンして、復帰に50秒かかる
  ↑後の節で対処

UX

LINEボットで、メッセージの完全一致を条件式としているので、リッチメニューを設けることで、簡単に・正確にメッセージを送信できる

アクセスサイト

開発者が関与しなくても、メンバの編集・削除や諸設定の更新が行えるように、管理者用のサイトを作成する

機能

メンバ登録・削除

該当のLINE公式アカウントに友達登録すると、ユーザのLINEIDをデータベースに登録する
その後は、フィールドの空き状況によって、順次データをセットする

該当コード
if event_type == 'follow':

    """
    友達追加されたときの処理

    データベース上にLINEIDの重複がないか確かめてから
    LINEIDをデータベースに追加する

    データベースの欠損値によってデータを受け付けてデータベースに追加する
    """

    try:
        member = Member.objects.get(user_id=line_id)
    except:
        new_member = Member(user_id=line_id)
        new_member.save()
        member = new_member

欠席連絡

欠席するメンバは該当の週に、欠席連絡を行うことができる

この後、グループを作成する関係で、実際には欠席しないものの、司会進行など(ここでは議長と書記)も(形上)欠席連絡を行う必要がある。
そこで、司会進行役などに限りその旨を、クイックリプライを用いて1タッチで送信できるようにした。

出欠席管理

欠席連絡が行われているメンバをデータベースから取得し、名前とその理由を表示する

GAS(google app script)で開発した前バージョンでは、GoogleSpreadSheetにデータを保存していたため、メンバ情報の取得にとても時間がかかっていた。
⇒SQLを使用することで高速化につながった

GASで開発したバージョンでは、排他制御を行っていなかったため、ユーザの操作次第では、予期せぬバグになる恐れがあった。
⇒GoogleSpreadSheetで管理しないことで、対策

グループ作成

欠席連絡を行っていないメンバを、任意のグループ数にランダムに割り振る
作成したグループはPillowを用いて画像にしてリプライする

人数の異なる3学年を、それぞれできるだけ均等に割り振りたい
⇒ ラウンドロビン方式で割り振る

グループを画像で提供することで、共有しやすい・見やすいという利点がある

データベースを経由してLINEに返送しているが、画像が更新されても画像のURLが同じであるがために、キャッシュが更新されない問題が発生した
⇒ urlにクエリパラメータとして、タイムスタンプを付与することで回避

MakeGroup関数
def MakeGroups(_num_groups):
    
    """
    メンバを均等にグループ分けする
    :param members: 出席予定のメンバリスト
    :param num_groups: 作成するグループ数
    :return: グループ分けされた辞書
    """
    system = System.objects.get(id=0)
    gradeIndex = system.grade_index

    gradeIndex_first = gradeIndex % 3 + 1
    gradeIndex_second = (gradeIndex + 1) % 3 + 1

    group1 = list(Member.objects.filter(absent_reason="", grade_class="GradeClass" + str(gradeIndex_first)))
    group2 = list(Member.objects.filter(absent_reason="", grade_class="GradeClass" + str(gradeIndex_second)))
    group3 = list(Member.objects.filter(absent_reason="", grade_class="GradeClass" + str(gradeIndex)))

    random.shuffle(group1)
    random.shuffle(group2)
    random.shuffle(group3)

    groups = [[] for _ in range(_num_groups)]

    # ラウンドロビン方式で各グループにメンバを分配
    idx = 0
    for member in group1:
        groups[idx % _num_groups].append(member)
        idx += 1
        
    for member in group2:
        groups[idx % _num_groups].append(member)
        idx += 1

    for member in group3:
        groups[idx % _num_groups].append(member)
        idx += 1


    return groups

グループ作成
num_member = Member.objects.filter(absent_reason="").count()

if message_text.isdigit() and int(message_text) <= num_member and int(message_text) != 0: 
    
    #入力値が出席可能なメンバ数以下の整数ならば

    try:
        num_groups = int(message_text)

        groups = MakeGroups(num_groups)

        updated_member = Member(user_id=member.user_id, name=member.name, grade_class=member.grade_class, absent_flag=0, groupsep_flag=0, absent_reason=member.absent_reason)
        updated_member.save()
        media_url = GenerateGroupImage(num_groups, groups)
        reply_messages = [
            {
                "type": "text", 
                "text": "グループを作成しました"
            },
            {
                "type": "image",
                "originalContentUrl": media_url,
                "previewImageUrl": media_url
            }
        ]

    except Exception as e:
        reply_messages = [{"type": "text", "text": f"グループの作成に失敗しました\n{e}"}]
        print(f"Error group making fail:{e}")

欠席連絡の定期削除

ミーティング後は、次のミーティングに備えて出欠席状況をリセットする必要がある

Djangoでの定期実行にはAPSchedulerを採用した

その他

委員長権限の譲渡や世代交代などの機能も実装済み

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?