內容類型附件

這是 Classroom 外掛程式逐步操作說明系列中的第四個逐步操作說明。

在本逐步操作說明中,您將與 Google Classroom API 互動以建立附件。請提供路徑,讓使用者查看附件內容。檢視畫面會因使用者在類別中的角色而異。這份逐步操作說明會介紹不需要學生繳交的內容類型附件

在本逐步操作說明中,您將完成下列操作:

  • 擷取並使用下列外掛程式查詢參數:
    • addOnToken:傳遞至附件探索檢視畫面的授權權杖。
    • itemId:CourseWork、CourseWorkMaterial 或公告收到外掛程式附件的專屬 ID。
    • itemType:可為「courseWork」、「courseWorkMaterials」或「公告」。
    • courseId:建立作業的 Google Classroom 課程的專屬 ID。
    • attachmentId:建立完成後,Google Classroom 為外掛程式附件指派的專屬 ID。
  • 實作內容類型附件的永久儲存空間。
  • 提供建立附件的路徑,以及為教師檢視畫面和學生檢視畫面 iframe 提供路徑。
  • 向 Google Classroom Add-ons API 發出下列要求:
    • 建立新附件。
    • 取得外掛程式結構定義,以識別登入的使用者是學生還是老師。

完成之後,請以老師身分登入,透過 Google Classroom UI 在作業中建立內容類型附件。課程的老師和學生也可以查看內容。

啟用 Classroom API

從這個步驟開始呼叫 Classroom API。您必須為 Google Cloud 專案啟用 API,才能呼叫該 API。前往 Google Classroom API 程式庫項目,然後選擇「啟用」

處理附件探索檢視畫面查詢參數

先前所述,在 iframe 中載入附件探索檢視畫面時,Google Classroom 會傳送查詢參數:

  • courseId:目前 Classroom 課程的 ID。
  • itemId:CourseWork、CourseWorkMaterial 或公告收到外掛程式附件的專屬 ID。
  • itemType:可為「courseWork」、「courseWorkMaterials」或「公告」。
  • addOnToken:用來授權特定 Classroom 外掛程式動作的權杖。
  • login_hint:目前使用者的 Google ID。
  • hd:目前使用者的主機網域,例如 example.com

這份逐步操作說明會介紹 courseIditemIditemTypeaddOnToken。在發出呼叫 Classroom API 時保留並傳遞這些內容。

與先前的逐步操作說明,將傳遞的查詢參數值儲存在我們的工作階段中。由於這是 Classroom 唯一能傳遞這些查詢參數的機會,因此請務必在初次開啟附件探索檢視畫面時這麼做。

Python

前往提供附件探索檢視畫面路徑的 Flask 伺服器檔案 (如果您按照我們提供的範例操作,則為 attachment-discovery-routes.py)。請擷取並儲存 courseIditemIditemTypeaddOnToken 查詢參數,則請加在外掛程式到達路徑上方 (此處提供的範例為 /classroom-addon)。

# 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 提供的 ID 查詢老師選取的內容。

Attachment 設定資料庫結構定義。我們提供的範例中有顯示圖片和說明文字的附件。Attachment 包含下列屬性:

  • attachment_id:附件的專屬 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-options 路徑以顯示老師可選取的內容選項。此外,您也需要提供內容選擇和建立確認頁面的範本。我們提供的範例包含這類範本,也可以顯示來自 Classroom API 的要求與回應。

請注意,您也可以修改現有的附件探索檢視到達網頁來顯示內容選項,而不必建立新的 /attachment-options 頁面。建議您針對本練習建立新頁面,以便保留第二個逐步操作說明中實作的 SSO 行為,例如撤銷應用程式權限。這些應可在建構及測試外掛程式時派上用場。

老師可以從我們提供的範例中,從幾張說明文字圖片中選取。我們提供了四張圖片中的知名地標,這些地標的說明文字均取自檔案名稱。

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 方法中選取的每張圖片建立一個附件。

問題附件建立要求

現在您已知道老師要附加哪些內容,接著向 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.
    # We need to request the Classroom API from a specific URL while add-ons
    # are in Early Access.

    # A Google API Key can be created in your Google Cloud project's Credentials
    # settings: https://console.cloud.google.com/apis/credentials.
    # Click "Create Credentials" at top and choose "API key", then provide
    # the key in the discoveryServiceUrl below.
    classroom_service = googleapiclient.discovery.build(
        serviceName="classroom",
        version="v1",
        discoveryServiceUrl=f"https://classroom.googleapis.com/$discovery/rest?labels=ADD_ONS_ALPHA&key={GOOGLE_API_KEY}",
        credentials=credentials)

courses.courseWork.addOnAttachments 端點發出 CREATE 要求。對於老師選取的每個圖片,請先建構 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 代表由各自的使用者類型開啟附件時載入的網址。

將要求主體中的 AddOnAttachment 物件傳送至適當的 addOnAttachments 端點。針對每個要求提供 courseIditemIditemTypeaddOnToken ID。

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()

此時請考慮將使用者轉送到確認頁面,確認他們已成功建立連結。

允許外掛程式附加的附件

現在,您可以趁著在 GWM SDK 應用程式設定頁面的「允許附件 URI 前置字串」欄位中,加入任何適當的位址。您的外掛程式只能從本頁列出的其中一個 URI 前置字串建立連結。這項安全措施有助於降低中間人攻擊的可能性。

最簡單的方法就是在這個欄位中提供頂層網域,例如 https://example.com。如果使用本機電腦做為網路伺服器,https://localhost:<your port number>/ 可正常運作。

新增路徑以供老師和學生檢視畫面查看

有四個 iframe 可能會載入 Google Classroom 外掛程式。您只有建構了附件探索檢視 iframe 的路徑。接著,請新增為老師和學生檢視畫面 iframe 提供的路徑。

您必須使用「Teacher View」 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_hinthd 查詢參數,並視需要將使用者轉送至授權流程。如要進一步瞭解這個流程,請參閱先前逐步操作說明中討論的登入指南詳細資料

然後將要求傳送至與項目類型相符的 getAddOnContext 端點。

Python

在我們提供的範例中,這是 load_content_attachment 方法的接續。

# Create an instance of the Classroom service.
classroom_service = googleapiclient.discovery.build(
    serviceName="classroom"
    version="v1",
    discoveryServiceUrl=f"https://classroom.googleapis.com/$discovery/rest?labels=ADD_ONS_ALPHA&key={GOOGLE_API_KEY}",
    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()

這個方法會傳回目前使用者在類別中的角色相關資訊。根據使用者的角色,變更他們看到的檢視畫面。系統恰好會在回應物件中填入其中一個 studentContextteacherContext 欄位。請查看這些資訊,判斷如何稱呼使用者。

無論如何,請使用 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 會隨即開啟,外掛程式會載入您在 GWM SDK「App Configuration」頁面中指定的連結設定 URI
  • 選擇要附加到作業的內容。
  • 請在附件建立流程完成後關閉 iframe。

Google Classroom 的作業建立使用者介面中應該會顯示附件資訊卡。按一下資訊卡即可開啟「教師檢視畫面」iframe,並確認顯示正確的附件。按一下「Assign」按鈕。

完成下列步驟即可測試學生的學習體驗:

  • 接著,以老師測試使用者的身分,以學生測試使用者身分登入 Classroom。
  • 在「課堂作業」分頁中找到測試作業。
  • 展開作業,然後按一下附件資訊卡,開啟「學生 View」iframe。

確認學生看到的附件正確無誤。

恭喜!您可以開始進行下一個步驟:建立活動類型附件