这是 Google 课堂插件演示系列中的第四项演示。
在本演示中,您将使用 Google Classroom API 创建附件。您可以为用户提供路由来查看附件内容。视图因用户在类中的角色而异。本演示介绍了内容类型附件,这种附件不需要学生提交。
在本演示中,您完成了以下内容:
- 检索并使用以下插件查询参数:
addOnToken
:传递给附件发现视图的授权令牌。itemId
:接收插件附件的 CourseWork、CourseWorkMaterial 或 公告 的唯一标识符。itemType
:“courseWork”“courseWorkMaterials”或“公告”。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。
本演示介绍了 courseId
、itemId
、itemType
和 addOnToken
。在调用 Classroom API 时保留和传递这些内容。
与上一步一样,将传递的查询参数值存储在我们的会话中。请务必在首次打开“附件发现”视图时执行此操作,因为这是 Google 课堂传递这些查询参数的唯一机会。
Python
导航到为连接发现视图提供路由的 Flask 服务器文件(如果您遵循我们提供的示例,则为 attachment-discovery-routes.py
)。在插件着陆路线(我们所提供示例中的 /classroom-addon
)顶部,检索并存储 courseId
、itemId
、itemType
和 addOnToken
查询参数。
# 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
页面。出于本练习的目的,我们建议您创建一个新页面,以便保留第二个演示步骤中实现的单点登录行为,例如撤消应用权限。在您构建和测试插件时,这些内容应该很有用。
教师可以从我们所提供的示例中的少量带字幕图片中选择。我们提供了四张著名地标的图片,图片说明源自文件名。
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,
)
系统将生成一个类似于以下内容的“创建附件”页面:
教师可以选择多张图片。为教师在 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.
classroom_service = googleapiclient.discovery.build(
serviceName="classroom",
version="v1",
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}",
}
对于每个连接,必须至少提供 teacherViewUri
、studentViewUri
和 title
字段。teacherViewUri
和 studentViewUri
表示分别由用户类型打开附件时加载的网址。
将请求正文中的 AddOnAttachment
对象发送到相应的 addOnAttachments
端点。为每个请求提供 courseId
、itemId
、itemType
和 addOnToken
标识符。
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()
此时,请考虑将用户路由到确认页面,以确认他们已成功创建连接。
允许来自插件的附件
现在是时候将任何适当的地址添加到 Google Workspace Marketplace SDK 的应用配置页面的“允许的附件 URI 前缀”字段。您的插件只能通过本页面上列出的其中一个 URI 前缀创建连接。这是一种安全措施,有助于降低遭受中间人攻击的可能性。
最简单的方法是在此字段中提供顶级域名,例如 https://example.com
。如果您将本地机器用作 Web 服务器,https://localhost:<your port number>/
可以正常运作。
为教师和学生视图添加路线
Google 课堂插件可能会在四种 iframe 中加载。 到目前为止,您只构建了提供附件发现视图 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_hint
查询参数,并根据需要将用户路由到您的授权流程。如需详细了解此流程,请参阅之前的演示中讨论的登录指南详细信息。
然后,向与项类型匹配的 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()
此方法会返回有关当前用户在类中的角色的信息。根据用户的角色更改向其呈现的视图。响应对象中会填充 studentContext
或 teacherContext
字段中的一个。检查这些问题,以确定如何称呼用户。
在任何情况下,请使用 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)
测试插件
如需测试附件的创建情况,请完成以下步骤:
- 以您的 Teacher 测试用户的身份登录 [Google 课堂]。
- 导航至课业标签,然后创建新的作业。
- 点击文本区域下方的插件按钮,然后选择您的插件。 系统会打开 iframe,并且该插件会加载您在 Google Workspace Marketplace SDK 的应用配置页面中指定的附件设置 URI。
- 选择要附加到作业中的一段内容。
- 附件创建流程完成后,关闭 iframe。
您应该会在 Google Google 课堂的作业创建界面中看到一个附件卡片。点击相应卡片即可打开 Teacher View iframe,并确认显示了正确的附件。点击分配按钮。
如需测试学生体验,请完成以下步骤:
- 然后,以与教师测试用户位于同一课程的学生测试用户的身份登录 Google 课堂。
- 在“课业”标签页中找到测试作业。
- 展开作业,然后点击附件卡片以打开“学生视图 iframe”。
确认为学生显示正确的附件。
恭喜!您已准备好继续执行下一步:创建 activity 类型的附件。