内容类型附件

这是 Google 课堂插件演示系列中的第四个演示。

在本演示中,您将与 Google Classroom API 互动以创建附件。您可以为用户提供查看附件内容的路由。视图因用户在类中的角色而异。本演示介绍了内容类型附件,此类附件不需要学生提交。

在本演示过程中,您将完成以下操作:

  • 检索并使用以下插件查询参数:
    • addOnToken:传递给附件发现视图的授权令牌。
    • itemId:用于接收插件附件的 CourseWork、CourseWorkMaterial 或 公告的唯一标识符。
    • itemType:“courseWork”“courseWorkMaterials”或“announcement”。
    • courseId:在其中创建作业的 Google 课堂课程的唯一标识符。
    • attachmentId:Google 课堂在创建插件附件后为其分配的唯一标识符。
  • 为内容类型附件实现永久性存储。
  • 提供创建附件和提供教师视图 iframe 和学生视图 iframe 的路由。
  • 向 Google Classroom 插件 API 发出以下请求:
    • 创建新附件。
    • 获取插件上下文,该上下文可识别登录的用户是学生还是教师。

完成后,您可以以教师身份登录,通过 Google 课堂界面为作业创建内容类型附件。课程中的教师和学生也可以查看内容。

启用 Classroom API

从此步骤开始调用 Classroom API。您必须先为自己的 Google Cloud 项目启用该 API,然后才能调用它。找到 Google Classroom API 库条目,然后选择启用

处理附件发现视图查询参数

如前所述,Google 课堂会在 iframe 中加载附件发现视图时传递查询参数:

  • courseId:当前 Google 课堂课程的 ID。
  • itemId:用于接收插件附件的 CourseWork、CourseWorkMaterial 或 公告的唯一标识符。
  • itemType:“courseWork”“courseWorkMaterials”或“announcement”。
  • addOnToken:用于授权某些 Google 课堂插件操作的令牌。
  • login_hint:当前用户的 Google ID。
  • hd:当前用户的主机网域,例如 example.com

本演示介绍了 courseIditemIditemTypeaddOnToken。在向 Classroom API 发出调用时保留并传递这些代码。

与上一个演示步骤一样,将传递的查询参数值存储在我们的会话中。请务必在首次打开附件发现视图时执行此操作,因为这是 Google 课堂传递这些查询参数的唯一机会。

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,则系统不会再次传递这些值。

为内容类型附件添加永久性存储空间

您需要任何已创建附件的本地记录。这样,您就可以查询教师使用 Google 课堂提供的标识符选择的内容。

Attachment 设置数据库架构。我们提供的示例展示了显示图片和图片说明的附件。Attachment 包含以下属性:

  • attachment_id:附件的唯一标识符。由 Google 课堂分配,并在创建连接时在响应中返回。
  • image_filename:要显示的图片的本地文件名。
  • image_caption:与图片一起显示的说明。

Python

扩展前面步骤中的 SQLite 和 flask_sqlalchemy 实现。

转到您定义了 User 表的文件(如果您遵循我们提供的示例,则为 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 页面。我们建议您为本练习创建一个新页面,以便保留在第二个演示步骤中实现的 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 的响应后,将附件详细信息存储在数据库中。

首先,获取 Google 课堂服务的实例:

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 标识符。

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

在本地数据库中为此连接创建一个条目,以便稍后加载正确的内容。Google 课堂会在对创建请求的响应中返回一个唯一的 id 值,因此请将其用作数据库中的主键。另请注意,打开“教师视图”和“学生视图”时,Google 课堂会传递 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。如果您将本地机器用作 Web 服务器,则 https://localhost:<your port number>/ 可以正常工作。

为教师视图和学生视图添加路线

有四个 iframe 可以加载 Google 课堂插件。 到目前为止,您仅构建了传送附件发现视图 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_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 或学生视图 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 课堂]。
  • 找到课业标签,然后创建新的作业
  • 点击文本区域下方的插件按钮,然后选择您的插件。 iframe 会打开,并且该插件会加载您在 GWM SDK 的应用配置页面中指定的附件设置 URI
  • 选择要附加到作业中的一段内容。
  • 附件创建流程完成后,请关闭 iframe。

您应该会在 Google 课堂的作业创建界面中看到附件卡片。点击该卡片以打开教师视图 iframe,并确认显示了正确的附件。点击分配按钮。

若要测试学生体验,请完成以下步骤:

  • 然后,以教师测试用户所在课程中的学生测试用户的身份登录 Google 课堂。
  • 在“课业”标签页中找到测试作业。
  • 展开作业并点击附件卡片,以打开“学生视图”iframe。

确认学生看到的附件正确无误。

恭喜!您可以继续执行下一步:创建 activity 类型的连接