コンテンツ タイプの添付ファイル

これは、Classroom アドオンのチュートリアル シリーズの 4 回目 のチュートリアルです。

このチュートリアルでは、Google Classroom API を使用して添付ファイルを作成します。ユーザーが添付ファイルのコンテンツを表示するためのルートを提供します。ビューは、クラスでのユーザーのロールによって異なります。このチュートリアルでは、生徒の提出を必要としない コンテンツ タイプ添付ファイルについて説明します。

このチュートリアルでは、次のことを行います。

  • 次のアドオン クエリ パラメータを取得して使用します。
    • addOnToken: 添付ファイル検出ビューに渡される認証トークン。
    • itemId: アドオンの添付ファイルを受け取る CourseWork、CourseWorkMaterial、または Announcement の固有識別子。
    • itemType: 「courseWork」、「courseWorkMaterials」、「announcement」のいずれか。
    • courseId: 課題が作成される Google Classroom コースの固有識別子。
    • attachmentId: 作成後に Google Classroom によってアドオンの添付ファイルに割り当てられる固有識別子。
  • コンテンツ タイプ添付ファイルの永続ストレージを実装します。
  • 添付ファイルを作成し、教師用ビューと生徒用ビューの iframe を提供するルートを指定します。
  • Google Classroom アドオン API に次のリクエストを発行します。
    • 新しい添付ファイルを作成します。
    • アドオンのコンテキストを取得します。これにより、ログインしているユーザーが生徒か教師かを識別できます。

完了すると、教師としてログインしているときに、Google Classroom UI を介して課題にコンテンツ タイプ添付ファイルを作成できます。クラスの教師と生徒もコンテンツを表示できます。

Classroom API を有効にする

このステップから Classroom API を呼び出します。API を呼び出すには、Google Cloud プロジェクトで API を有効にする必要があります。Google Classroom API ライブラリ エントリに移動し、[有効にする]を選択します。

添付ファイル検出ビューのクエリ パラメータを処理する

前述のように、Google Classroom は iframe で添付ファイル検出ビューを読み込むときに クエリ パラメータを渡します。

  • courseId: 現在の Classroom コースの ID。
  • itemId: アドオンの添付ファイルを受け取る CourseWork、CourseWorkMaterial、または Announcement の固有識別子。
  • itemType: 「courseWork」、「courseWorkMaterials」、「announcement」のいずれか。
  • addOnToken: 特定の Classroom アドオン アクションを承認するために使用されるトークン。
  • login_hint: 現在のユーザーの Google ID。

このチュートリアルでは、courseIditemIditemTypeaddOnToken について説明します。 Classroom API を呼び出すときに、これらを保持して渡します。

前のチュートリアルのステップと同様に、渡されたクエリ パラメータ値をセッションに保存します。添付ファイル検出ビューが最初に開かれたときにこれを行うことが重要です。これは、Classroom がこれらのクエリ パラメータを渡す唯一の機会であるためです。

Python

を変更します。

添付ファイル検出ビューのルートを提供する Flask サーバー ファイルに移動します(提供されている例を使用している場合は attachment-discovery-routes.py)。アドオンのランディング ルート(提供されている例では /classroom-addon)の上部で、courseIditemIditemTypeaddOnToken のクエリ パラメータを取得して保存します。

# Retrieve the itemId, courseId, and addOnToken query parameters.
if flask.request.args.get("itemId"):
    flask.session["itemId"] = flask.request.args.get("itemId")
if flask.request.args.get("itemType"):
    flask.session["itemType"] = flask.request.args.get("itemType")
if flask.request.args.get("courseId"):
    flask.session["courseId"] = flask.request.args.get("courseId")
if flask.request.args.get("addOnToken"):
    flask.session["addOnToken"] = flask.request.args.get("addOnToken")

これらの値は、存在する場合にのみセッションに書き込みます。ユーザーが iframe を閉じずに後で添付ファイル検出ビューに戻った場合、再度渡されることはありません。

コンテンツ タイプ添付ファイルの永続ストレージを追加する

作成した添付ファイルのローカル レコードが必要です。これにより、Classroom から提供された識別子を使用して、教師が選択したコンテンツを検索できます。

Attachment のデータベース スキーマを設定します。提供されている例では、画像とキャプションを表示する添付ファイルが表示されます。Attachment には次の属性が含まれます。

  • attachment_id: 添付ファイルの固有識別子。Classroom によって割り当てられ、添付ファイルの作成時にレスポンスで返されます。
  • image_filename: 表示する画像のローカル ファイル名。
  • image_caption: 画像とともに表示するキャプション。

Python

前のステップで SQLite と flask_sqlalchemy の実装を拡張します。

ユーザー テーブルを定義したファイルに移動します(提供されている例を使用している場合は models.py)。ファイルの末尾の User クラスの下に、次のコードを追加します。

class Attachment(db.Model):
    # The attachmentId is the unique identifier for the attachment.
    attachment_id = db.Column(db.String(120), primary_key=True)

    # The image filename to store.
    image_filename = db.Column(db.String(120))

    # The image caption to store.
    image_caption = db.Column(db.String(120))

新しい Attachment クラスを、添付ファイル処理ルートを含むサーバー ファイルにインポートします。

新しいルートを設定する

このチュートリアルのステップでは、まずアプリケーションに新しいページを設定します。 これにより、ユーザーはアドオンを通じてコンテンツを作成して表示できます。

添付ファイル作成ルートを追加する

教師がコンテンツを選択して添付ファイル作成リクエストを発行するためのページが必要です。/attachment-options ルートを実装して、教師が選択するコンテンツ オプションを表示します。コンテンツの選択と作成の確認ページ用のテンプレートも必要です。提供されている例にはこれらのテンプレートが含まれており、Classroom API からのリクエストとレスポンスを表示することもできます。

新しい /attachment-options ページを作成する代わりに、既存の添付ファイル検出ビューのランディング ページを変更してコンテンツ オプションを表示することもできます。この演習では、新しいページを作成することをおすすめします。これにより、2 回目の チュートリアルのステップで実装した SSO の動作(アプリの権限の取り消しなど)を維持できます。これらは、アドオンのビルドとテストに役立ちます。

提供されている例では、教師はキャプション付きの画像の小さなセットから選択できます。キャプションがファイル名から派生した有名なランドマークの画像を 4 つ用意しました。

Python

提供されている例では、これは webapp/attachment_routes.py ファイルにあります。

@app.route("/attachment-options", methods=["GET", "POST"])
def attachment_options():
    """
    Render the attachment options page from the "attachment-options.html"
    template.

    This page displays a grid of images that the user can select using
    checkboxes.
    """

    # A list of the filenames in the static/images directory.
    image_filenames = os.listdir(os.path.join(app.static_folder, "images"))

    # The image_list_form_builder method creates a form that displays a grid
    # of images, checkboxes, and captions with a Submit button. All images
    # passed in image_filenames will be shown, and the captions will be the
    # title-cased filenames.

    # The form must be built dynamically due to limitations in WTForms. The
    # image_list_form_builder method therefore also returns a list of
    # attribute names in the form, which will be used by the HTML template
    # to properly render the form.
    form, var_names = image_list_form_builder(image_filenames)

    # If the form was submitted, validate the input and create the attachments.
    if form.validate_on_submit():

        # Build a dictionary that maps image filenames to captions.
        # There will be one dictionary entry per selected item in the form.
        filename_caption_pairs = construct_filename_caption_dictionary_list(
            form)

        # Check that the user selected at least one image, then proceed to
        # make requests to the Classroom API.
        if len(filename_caption_pairs) > 0:
            return create_attachments(filename_caption_pairs)
        else:
            return flask.render_template(
                "create-attachment.html",
                message="You didn't select any images.",
                form=form,
                var_names=var_names)

    return flask.render_template(
        "attachment-options.html",
        message=("You've reached the attachment options page. "
                "Select one or more images and click 'Create Attachment'."),
        form=form,
        var_names=var_names,
    )

これにより、次のような [添付ファイルを作成] ページが生成されます。

Python の例のコンテンツ選択ビュー

教師は複数の画像を選択できます。create_attachments メソッドで教師が選択した画像ごとに 1 つの添付ファイルを作成します。

添付ファイル作成リクエストを発行する

教師が添付するコンテンツがわかったら、Classroom API にリクエストを発行して、課題に添付ファイルを作成します。Classroom API からレスポンスを受け取ったら、添付ファイルの詳細をデータベースに保存します。

まず、Classroom サービスのインスタンスを取得します。

Python

提供されている例では、これは webapp/attachment_routes.py ファイルにあります。

def create_attachments(filename_caption_pairs):
    """
    Create attachments and show an acknowledgement page.

    Args:
        filename_caption_pairs: A dictionary that maps image filenames to
            captions.
    """
    # Get the Google Classroom service.
    classroom_service = googleapiclient.discovery.build(
        serviceName="classroom",
        version="v1",
        credentials=credentials)

CREATE リクエストを courses.courseWork.addOnAttachments エンドポイントに発行します。教師が選択した画像ごとに、まず AddOnAttachment オブジェクトを作成します。

Python

提供されている例では、これは create_attachments メソッドの続きです。

# Create a new attachment for each image that was selected.
attachment_count = 0
for key, value in filename_caption_pairs.items():
    attachment_count += 1

    # Create a dictionary with values for the AddOnAttachment object fields.
    attachment = {
        # Specifies the route for a teacher user.
        "teacherViewUri": {
            "uri":
                flask.url_for(
                    "load_content_attachment", _scheme='https', _external=True),
        },
        # Specifies the route for a student user.
        "studentViewUri": {
            "uri":
                flask.url_for(
                    "load_content_attachment", _scheme='https', _external=True)
        },
        # The title of the attachment.
        "title": f"Attachment {attachment_count}",
    }

各添付ファイルには、少なくとも teacherViewUristudentViewUrititle フィールドを指定する必要があります。teacherViewUristudentViewUri は、それぞれのユーザータイプが添付ファイルを開いたときに読み込まれる URL を表します。

リクエストの本文で AddOnAttachment オブジェクトを適切な addOnAttachments エンドポイントに送信します。各リクエストに courseIditemIditemTypeaddOnToken 識別子を指定します。

Python

提供されている例では、これは create_attachments メソッドの続きです。

# Use the itemType to determine which stream item type the teacher created
match flask.session["itemType"]:
    case "announcements":
        parent = classroom_service.courses().announcements()
    case "courseWorkMaterials":
        parent = classroom_service.courses().courseWorkMaterials()
    case _:
        parent = classroom_service.courses().courseWork()

# Issue a request to create the attachment.
resp = parent.addOnAttachments().create(
    courseId=flask.session["courseId"],
    itemId=flask.session["itemId"],
    addOnToken=flask.session["addOnToken"],
    body=attachment).execute()

後で正しいコンテンツを読み込めるように、ローカル データベースにこの添付ファイルのエントリを作成します。Classroom は、作成リクエストへのレスポンスで一意の id 値を返すため、これをデータベースの主キーとして使用します。また、Classroom は教師用ビューと生徒用ビューを開くときに attachmentId クエリ パラメータを渡します。

Python

提供されている例では、これは create_attachments メソッドの続きです。

# Store the value by id.
new_attachment = Attachment(
    # The new attachment's unique ID, returned in the CREATE response.
    attachment_id=resp.get("id"),
    image_filename=key,
    image_caption=value)
db.session.add(new_attachment)
db.session.commit()

この時点で、添付ファイルが正常に作成されたことを確認する確認ページにユーザーをルーティングすることを検討してください。

アドオンからの添付ファイルを許可する

この時点で、Google Workspace Marketplace SDK の [アプリの構成] ページの [許可された添付ファイル URI 接頭辞] フィールドに適切なアドレスを追加することをおすすめします。アドオンで作成できる添付ファイルは、このページに記載されている URI 接頭辞のいずれかからのみです。これは、中間者攻撃の可能性を減らすためのセキュリティ対策です。

最も簡単な方法は、このフィールドに最上位ドメイン(https://example.com など)を指定することです。https://localhost:<your port number>/ ローカルマシンをウェブサーバーとして使用している場合は、 が機能します。

教師用ビューと生徒用ビューのルートを追加する

Google Classroom アドオンを読み込むことができる iframe は 4 つあります。 これまでに、添付ファイル検出ビューの iframe を提供するルートのみを構築しました。次に、教師用ビューと生徒用ビューの iframe を提供するルートを追加します。

教師用ビュー の iframe は、生徒の操作のプレビューを表示するために必要ですが、必要に応じて追加情報や編集機能を含めることができます。

生徒用ビュー は、生徒がアドオンの添付ファイルを開いたときに表示されるページです。

この演習では、教師用ビューと生徒用ビューの両方を提供する単一の /load-content-attachment ルートを作成します。ページが読み込まれたときに、Classroom API メソッドを使用して、ユーザーが教師か生徒かを判断します。

Python

提供されている例では、これは webapp/attachment_routes.py ファイルにあります。

@app.route("/load-content-attachment")
def load_content_attachment():
    """
    Load the attachment for the user's role."""

    # Since this is a landing page for the Teacher and Student View iframes, we
    # need to preserve the incoming query parameters.
    if flask.request.args.get("itemId"):
        flask.session["itemId"] = flask.request.args.get("itemId")
    if flask.request.args.get("itemType"):
        flask.session["itemType"] = flask.request.args.get("itemType")
    if flask.request.args.get("courseId"):
        flask.session["courseId"] = flask.request.args.get("courseId")
    if flask.request.args.get("attachmentId"):
        flask.session["attachmentId"] = flask.request.args.get("attachmentId")

この時点でユーザーを認証する必要があることに注意してください。また、ここで login_hint クエリ パラメータを処理し、必要に応じてユーザーを承認フローにルーティングする必要があります。このフローの詳細については、前のチュートリアルで説明したログイン ガイダンスの詳細をご覧ください。

次に、アイテムタイプに一致する getAddOnContext エンドポイントにリクエストを送信します。

Python

提供されている例では、これは load_content_attachment メソッドの続きです。

# Create an instance of the Classroom service.
classroom_service = googleapiclient.discovery.build(
    serviceName="classroom"
    version="v1",
    credentials=credentials)

# Use the itemType to determine which stream item type the teacher created
match flask.session["itemType"]:
    case "announcements":
        parent = classroom_service.courses().announcements()
    case "courseWorkMaterials":
        parent = classroom_service.courses().courseWorkMaterials()
    case _:
        parent = classroom_service.courses().courseWork()

addon_context_response = parent.getAddOnContext(
    courseId=flask.session["courseId"],
    itemId=flask.session["itemId"]).execute()

このメソッドは、現在のユーザーのクラスでのロールに関する情報を返します。 ユーザーに表示されるビューは、ロールに応じて変更します。レスポンス オブジェクトには、 studentContext フィールドまたは teacherContext フィールドのいずれか 1 つだけが入力されます。これらを調べて、ユーザーへの対応方法を決定します。

いずれにしても、attachmentId クエリ パラメータ値を使用して、データベースから取得する添付ファイルを把握します。このクエリ パラメータは、教師用ビューまたは生徒用ビューの URI を開くときに指定されます。

Python

提供されている例では、これは load_content_attachment メソッドの続きです。

# Determine which view we are in by testing the returned context type.
user_context = "student" if addon_context_response.get(
    "studentContext") else "teacher"

# Look up the attachment in the database.
attachment = Attachment.query.get(flask.session["attachmentId"])

# Set the text for the next page depending on the user's role.
message_str = f"I see that you're a {user_context}! "
message_str += (
    f"I've loaded the attachment with ID {attachment.attachment_id}. "
    if user_context == "teacher" else
    "Please enjoy this image of a famous landmark!")

# Show the content with the customized message text.
return flask.render_template(
    "show-content-attachment.html",
    message=message_str,
    image_filename=attachment.image_filename,
    image_caption=attachment.image_caption,
    responses=response_strings)

アドオンをテストする

次の手順で添付ファイルの作成をテストします。

  • [Google Classroom] に、教師のテストユーザーとしてログインします。
  • [授業] タブに移動し、新しい課題 を作成します。
  • テキスト エリアの下にある [アドオン] ボタンをクリックし、アドオンを選択します。 iframe が開き、Google Workspace Marketplace SDK の [アプリの構成] ページで 指定した **添付ファイルの設定 URI** がアドオンによって読み込まれます。
  • 課題に添付するコンテンツを選択します。
  • 添付ファイル作成フローが完了したら、iframe を閉じます。

Google Google Classroom の課題作成 UI に添付ファイル カードが表示されます。カードをクリックして教師用ビューの iframe を開き、正しい添付ファイルが表示されることを確認します。[割り当て] ボタンをクリックします。

次の手順で生徒の操作をテストします。

  • 次に、教師のテストユーザーと同じクラスの生徒のテストユーザーとして Classroom にログインします。
  • [授業] タブでテスト課題を見つけます。
  • 課題を展開し、添付ファイル カードをクリックして生徒用ビューの iframe を開きます。

生徒に正しい添付ファイルが表示されることを確認します。

おめでとうございます。次のステップ(アクティビティ タイプ添付ファイルの作成)に進む準備ができました。