rubric は、教師が生徒の提出物を採点する際に使用できるテンプレートです。Classroom API を使用すると、教師に代わってこれらのルーブリックを管理したり、生徒の提出物のルーブリックの成績を読み取ったりできます。
図 1.Classroom の課題のルーブリックのサンプル。
このガイドでは、ルーブリック API の基本的なコンセプトと機能について説明します。 ルーブリックの一般的な構造と、Classroom UI でルーブリックを使用して採点する方法については、こちらのヘルプセンター記事をご覧ください。
前提条件
このガイドでは、次のものがあることを前提としています。
- Python 3.8.6 以降
- pip パッケージ管理ツール
- Google Cloud プロジェクト。
Google Classroom が有効になっていて、 Google Workspace for Education Plus ライセンスが割り当てられている Google Workspace for Education アカウント。アカウントをお持ちでない場合は、アップグレードされた デベロッパー デモアカウントをリクエストできます。
少なくとも 1 つのテスト生徒アカウントを含むテストクラス。テストに使用できる Classroom クラスがない場合は、UI でクラスを作成して テスト生徒を追加します。
デスクトップ アプリケーションの認証情報を認可する
エンドユーザーとして認証を行い、アプリ内でユーザーデータにアクセスするには、1 つ以上の OAuth 2.0 クライアント ID を作成する必要があります。クライアント ID は、Google の OAuth サーバーで個々のアプリを識別するために使用します。アプリが複数のプラットフォームで実行される場合は、プラットフォームごとに個別のクライアント ID を作成する必要があります。
- Google Cloud コンソールの Google Cloud の [認証情報] ページに 移動します。
- [認証情報を作成] > [OAuth クライアント ID] をクリックします。
- [アプリケーションの種類] > [デスクトップ アプリ] をクリックします。
- [名前] フィールドに、認証情報の名前を入力します。この名前は Google Cloud コンソールにのみ表示されます。例: 「ルーブリック クライアント」。
- [作成] をクリックします。[OAuth クライアントを作成しました] 画面が表示され、新しいクライアント ID とクライアント シークレットが表示されます。
- [Download JSON] をクリックし、[OK] をクリックします。新しく作成した認証情報が [OAuth 2.0 クライアント ID] に表示されます。
- ダウンロードした JSON ファイルを
credentials.jsonとして保存し、作業ディレクトリに移動します。 - [認証情報を作成] > [API キー] をクリックし、API キーをメモします。
詳細については、アクセス認証情報を作成するをご覧ください。
OAuth スコープを構成する
プロジェクトの既存の OAuth スコープによっては、追加のスコープを構成する必要があります。
- OAuth 同意画面に移動します。
- [アプリを編集] > [保存して次へ] をクリックして、[スコープ] 画面に移動します。
- [スコープを追加または削除] をクリックします。
- まだ追加していない場合は、次のスコープを追加します。
https://www.googleapis.com/auth/classroom.coursework.studentshttps://www.googleapis.com/auth/classroom.courses
- 次に、[更新] > [保存して次へ] > [保存して次へ] > [ダッシュボードに戻る] をクリックします。
詳細については、OAuth 同意画面を構成するをご覧ください。
classroom.coursework.students スコープを使用すると、ルーブリック(CourseWork へのアクセスを含む)に対する読み取りと書き込みのアクセスが可能になります。classroom.courses スコープを使用すると、コースの読み取りと書き込みが可能になります。
特定のメソッドに必要なスコープは、そのメソッドのリファレンス ドキュメントに記載されています。例として、courses.courseWork.rubrics.create 認可スコープ
をご覧ください。Classroom のすべてのスコープは、Google API の OAuth 2.0 スコープ
で確認できます。
サンプルを構成する
作業ディレクトリで、Python 用 Google クライアント ライブラリをインストールします。
pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
クライアント ライブラリをビルドしてユーザーを認可する main.py というファイルを作成します。YOUR_API_KEY の代わりに API キーを使用します。
import json
import os.path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/classroom.courses',
'https://www.googleapis.com/auth/classroom.coursework.students']
def build_authenticated_service(api_key):
"""Builds the Classroom service."""
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run.
with open('token.json', 'w') as token:
token.write(creds.to_json())
try:
# Build the Classroom service.
service = build(
serviceName="classroom",
version="v1",
credentials=creds,
discoveryServiceUrl=f"https://classroom.googleapis.com/$discovery/rest?labels=DEVELOPER_PREVIEW&key={api_key}")
return service
except HttpError as error:
print('An error occurred: %s' % error)
if __name__ == '__main__':
service = build_authenticated_service(YOUR_API_KEY)
python main.py を使用してスクリプトを実行します。ログインして OAuth スコープに同意するよう求められます。
課題を作成する
ルーブリックは課題(CourseWork)に関連付けられており、その CourseWork のコンテキストでのみ意味を持ちます。ルーブリックを作成できるのは
親の CourseWork アイテムを作成した Google Cloud プロジェクトのみです。このガイドでは、スクリプトを使用して新しい CourseWork 課題を作成します。
main.py に以下を追加します。
def get_latest_course(service):
"""Retrieves the last created course."""
try:
response = service.courses().list(pageSize=1).execute()
courses = response.get("courses", [])
if not courses:
print("No courses found. Did you remember to create one in the UI?")
return
course = courses[0]
return course
except HttpError as error:
print(f"An error occurred: {error}")
return error
def create_coursework(service, course_id):
"""Creates and returns a sample coursework."""
try:
coursework = {
"title": "Romeo and Juliet analysis.",
"description": """Write a paper arguing that Romeo and Juliet were
time travelers from the future.""",
"workType": "ASSIGNMENT",
"state": "PUBLISHED",
}
coursework = service.courses().courseWork().create(
courseId=course_id, body=coursework).execute()
return coursework
except HttpError as error:
print(f"An error occurred: {error}")
return error
次に、main.py を更新して、作成したテストクラスの course_id を取得し、新しいサンプル課題を作成して、課題の coursework_id を取得します。
if __name__ == '__main__':
service = build_authenticated_service(YOUR_API_KEY)
course = get_latest_course(service)
course_id = course.get("id")
course_name = course.get("name")
print(f"'{course_name}' course ID: {course_id}")
coursework = create_coursework(service, course_id)
coursework_id = coursework.get("id")
print(f"Assignment created with ID {coursework_id}")
#TODO(developer): Save the printed course and coursework IDs.
course_id と coursework_id を保存します。これらは、すべてのルーブリック CRUD オペレーションに必要です。
これで、Classroom にサンプル CourseWork が作成されました。
図 2.Classroom の課題のサンプル。
ユーザーの資格を確認する
ルーブリックを作成して更新するには、リクエストを行うユーザーと対応するコースのオーナーの両方に Google Workspace for Education Plusライセンスが割り当てられている必要があります。 Classroom は、ユーザーがアクセスできる機能をデベロッパーが判断できるように、ユーザーの 資格エンドポイントをサポートしています。
main.py を更新して実行し、テストアカウントがルーブリック機能にアクセスできることを確認します。
if __name__ == '__main__':
service = build_authenticated_service(YOUR_API_KEY)
capability = service.userProfiles().checkUserCapability(
userId='me',
# Specify the preview version. checkUserCapability is
# supported in V1_20240930_PREVIEW and later.
previewVersion="V1_20240930_PREVIEW",
capability="CREATE_RUBRIC").execute()
if not capability.get('allowed'):
print('User ineligible for rubrics creation.')
# TODO(developer): in a production app, this signal could be used to
# proactively hide any rubrics related features from users or encourage
# them to upgrade to the appropriate license.
else:
print('User eligible for rubrics creation.')
ルーブリックの作成
これで、ルーブリックの管理を開始する準備が整いました。
ルーブリックは、完全なルーブリック オブジェクトを含む CourseWork を使用して create() 呼び出しで作成できます。条件とレベルの ID プロパティは
省略されます(これらは作成時に生成されます)。
次の関数を main.py に追加します。
def create_rubric(service, course_id, coursework_id):
"""Creates an example rubric on a coursework."""
try:
body = {
"criteria": [
{
"title": "Argument",
"description": "How well structured your argument is.",
"levels": [
{"title": "Convincing",
"description": "A compelling case is made.", "points": 30},
{"title": "Passable",
"description": "Missing some evidence.", "points": 20},
{"title": "Needs Work",
"description": "Not enough strong evidence..", "points": 0},
]
},
{
"title": "Spelling",
"description": "How well you spelled all the words.",
"levels": [
{"title": "Perfect",
"description": "No mistakes.", "points": 20},
{"title": "Great",
"description": "A mistake or two.", "points": 15},
{"title": "Needs Work",
"description": "Many mistakes.", "points": 5},
]
},
{
"title": "Grammar",
"description": "How grammatically correct your sentences are.",
"levels": [
{"title": "Perfect",
"description": "No mistakes.", "points": 20},
{"title": "Great",
"description": "A mistake or two.", "points": 15},
{"title": "Needs Work",
"description": "Many mistakes.", "points": 5},
]
},
]
}
rubric = service.courses().courseWork().rubrics().create(
courseId=course_id, courseWorkId=coursework_id, body=body
).execute()
print(f"Rubric created with ID {rubric.get('id')}")
return rubric
except HttpError as error:
print(f"An error occurred: {error}")
return error
次に、main.py を更新して実行し、以前の Course
と CourseWork ID を使用してルーブリックの例を作成します。
if __name__ == '__main__':
service = build_authenticated_service(YOUR_API_KEY)
capability = service.userProfiles().checkUserCapability(
userId='me',
# Specify the preview version. checkUserCapability is
# supported in V1_20240930_PREVIEW and later.
previewVersion="V1_20240930_PREVIEW",
capability="CREATE_RUBRIC").execute()
if not capability.get('allowed'):
print('User ineligible for rubrics creation.')
# TODO(developer): in a production app, this signal could be used to
# proactively hide any rubrics related features from users or encourage
# them to upgrade to the appropriate license.
else:
rubric = create_rubric(service, YOUR_COURSE_ID, YOUR_COURSEWORK_ID)
print(json.dumps(rubric, indent=4))
ルーブリックの表現に関する注意点:
- 条件とレベルの順序は Classroom UI に反映されます。
- スコア付きレベル(
pointsプロパティを持つレベル)は、ポイントで昇順または降順に並べ替える必要があります(ランダムに並べ替えることはできません)。 - 教師は UI で条件とスコア付きレベル(スコアなしレベルは除く)を並べ替えることができ、データ内の順序が変更されます。
ルーブリックの構造に関するその他の注意事項については、制限事項をご覧ください。
UI に戻ると、課題にルーブリックが表示されます。
図 3.Classroom の課題のルーブリックのサンプル。
ルーブリックを読み取る
ルーブリックは、標準の list() と get() メソッドで読み取ることができます。
課題にルーブリックは 1 つしか存在できないため、list() は直感的ではないように思えますが、ルーブリック ID がまだない場合に便利です。CourseWork に関連付けられたルーブリックがない場合、list() レスポンスは空になります。
次の関数を main.py に追加します。
def get_rubric(service, course_id, coursework_id):
"""
Get the rubric on a coursework. There can only be at most one.
Returns null if there is no rubric.
"""
try:
response = service.courses().courseWork().rubrics().list(
courseId=course_id, courseWorkId=coursework_id
).execute()
rubrics = response.get("rubrics", [])
if not rubrics:
print("No rubric found for this assignment.")
return
rubric = rubrics[0]
return rubric
except HttpError as error:
print(f"An error occurred: {error}")
return error
main.py を更新して実行し、追加したルーブリックを取得します。
if __name__ == '__main__':
service = build_authenticated_service(YOUR_API_KEY)
rubric = get_rubric(service, YOUR_COURSE_ID, YOUR_COURSEWORK_ID)
print(json.dumps(rubric, indent=4))
#TODO(developer): Save the printed rubric ID.
後のステップで使用するため、ルーブリックの id プロパティをメモします。
ルーブリック ID がある場合は、Get() が適しています。関数の代わりに get() を使用すると、次のようになります。
def get_rubric(service, course_id, coursework_id, rubric_id):
"""
Get the rubric on a coursework. There can only be at most one.
Returns a 404 if there is no rubric.
"""
try:
rubric = service.courses().courseWork().rubrics().get(
courseId=course_id,
courseWorkId=coursework_id,
id=rubric_id
).execute()
return rubric
except HttpError as error:
print(f"An error occurred: {error}")
return error
ルーブリックがない場合、この実装は 404 を返します。
ルーブリックを更新する
ルーブリックの更新は patch() 呼び出しで行います。ルーブリックの構造が複雑なため、更新は読み取り、変更、書き込みのパターンで行う必要があります。このパターンでは、criteria プロパティ全体が置き換えられます。
更新ルールは次のとおりです。
- ID なしで追加 された条件またはレベルは、追加 とみなされます。
- 以前に存在していた条件またはレベルが欠落 している場合は、削除 とみなされます。
- 既存の ID があり、データが変更されている 条件またはレベルは、編集 とみなされます。変更されていないプロパティはそのまま残ります。
- 新しい ID または不明な ID が指定された条件またはレベルは、エラー とみなされます。
- 新しい条件とレベルの順序は、新しい UI の順序とみなされます (前述の制限があります)。
ルーブリックを更新する関数を追加します。
def update_rubric(service, course_id, coursework_id, rubric_id, body):
"""
Updates the rubric on a coursework.
"""
try:
rubric = service.courses().courseWork().rubrics().patch(
courseId=course_id,
courseWorkId=coursework_id,
id=rubric_id,
body=body,
updateMask='criteria'
).execute()
return rubric
except HttpError as error:
print(f"An error occurred: {error}")
return error
この例では、criteria フィールドを、
updateMask を使用して、変更するフィールドとして指定しています。
次に、前述の更新ルールごとに変更を行うように main.py を変更します。
if __name__ == '__main__':
service = build_authenticated_service(YOUR_API_KEY)
capability = service.userProfiles().checkUserCapability(
userId='me',
# Specify the preview version. checkUserCapability is
# supported in V1_20240930_PREVIEW and later.
previewVersion="V1_20240930_PREVIEW",
capability="CREATE_RUBRIC").execute()
if not capability.get('allowed'):
print('User ineligible for rubrics creation.')
# TODO(developer): in a production app, this signal could be used to
# proactively hide any rubrics related features from users or encourage
# them to upgrade to the appropriate license.
else:
# Get the latest rubric.
rubric = get_rubric(service, YOUR_COURSE_ID, YOUR_COURSEWORK_ID)
criteria = rubric.get("criteria")
"""
The "criteria" property should look like this:
[
{
"id": "NkEyMdMyMzM2Nxkw",
"title": "Argument",
"description": "How well structured your argument is.",
"levels": [
{
"id": "NkEyMdMyMzM2Nxkx",
"title": "Convincing",
"description": "A compelling case is made.",
"points": 30
},
{
"id": "NkEyMdMyMzM2Nxky",
"title": "Passable",
"description": "Missing some evidence.",
"points": 20
},
{
"id": "NkEyMdMyMzM2Nxkz",
"title": "Needs Work",
"description": "Not enough strong evidence..",
"points": 0
}
]
},
{
"id": "NkEyMdMyMzM2Nxk0",
"title": "Spelling",
"description": "How well you spelled all the words.",
"levels": [...]
},
{
"id": "NkEyMdMyMzM2Nxk4",
"title": "Grammar",
"description": "How grammatically correct your sentences are.",
"levels": [...]
}
]
"""
# Make edits. This example will make one of each type of change.
# Add a new level to the first criteria. Levels must remain sorted by
# points.
new_level = {
"title": "Profound",
"description": "Truly unique insight.",
"points": 50
}
criteria[0]["levels"].insert(0, new_level)
# Remove the last criteria.
del criteria[-1]
# Update the criteria titles with numeric prefixes.
for index, criterion in enumerate(criteria):
criterion["title"] = f"{index}: {criterion['title']}"
# Resort the levels from descending to ascending points.
for criterion in criteria:
criterion["levels"].sort(key=lambda level: level["points"])
# Update the rubric with a patch call.
new_rubric = update_rubric(
service, YOUR_COURSE_ID, YOUR_COURSEWORK_ID, YOUR_RUBRIC_ID, rubric)
print(json.dumps(new_rubric, indent=4))
変更は Classroom の教師に反映されます。
図 4.更新されたルーブリック。
ルーブリックで採点された提出物を表示する
現時点では、API でルーブリックを使用して生徒の提出物を採点することはできませんが、Classroom UI でルーブリックを使用して採点された提出物のルーブリックの成績を読み取ることができます。
Classroom UI で生徒として、サンプル課題を完了して提出します。 次に、教師として、ルーブリックを使用して課題を手動で採点します。
図 5.採点中のルーブリックの教師ビュー。
StudentSubmissions がルーブリックで採点された場合、2 つの新しい
プロパティがあります。draftRubricGrades と assignedRubricGrades です。これらは、下書きと割り当てられた採点
状態のときに教師が選択した
ポイントとレベルを表します。
既存の studentSubmissions.get() と
studentSubmissions.list() メソッドを使用して、採点済みの提出物を表示できます。
次の関数を main.py に追加して、生徒の提出物を一覧表示します。
def get_latest_submission(service, course_id, coursework_id):
"""Retrieves the last submission for an assignment."""
try:
response = service.courses().courseWork().studentSubmissions().list(
courseId = course_id,
courseWorkId = coursework_id,
pageSize=1
).execute()
submissions = response.get("studentSubmissions", [])
if not submissions:
print(
"""No submissions found. Did you remember to turn in and grade
the assignment in the UI?""")
return
submission = submissions[0]
return submission
except HttpError as error:
print(f"An error occurred: {error}")
return error
次に、main.py を更新して実行し、提出物の成績を表示します。
if __name__ == '__main__':
service = build_authenticated_service(YOUR_API_KEY)
submission = get_latest_submission(
service, YOUR_COURSE_ID, YOUR_COURSEWORK_ID)
print(json.dumps(submission, indent=4))
draftRubricGrades と assignedRubricGrades には次のものが含まれます。
- 対応するルーブリック条件の
criterionId。 - 教師が各条件に割り当てた
points。これは選択したレベルから取得できますが、教師がオーバーライドすることもできます。 - 各条件に選択したレベルの
levelId。教師がレベルを選択しなかったが、条件にポイントを割り当てた場合、このフィールドは存在しません。
これらのリストには、教師がレベルを選択したか、ポイントを設定した条件のエントリのみが含まれます。たとえば、教師が採点中に 1 つの条件のみを操作する場合、ルーブリックに多くの条件があっても、draftRubricGrades と assignedRubricGrades には 1 つのアイテムしか含まれません。
ルーブリックを削除する
ルーブリックは、標準の delete() リクエストで削除できます。次のコードは、完全な関数例を示していますが、採点がすでに開始されているため、現在のルーブリックを削除することはできません。
def delete_rubric(service, course_id, coursework_id, rubric_id):
"""Deletes the rubric on a coursework."""
try:
service.courses().courseWork().rubrics().delete(
courseId=course_id,
courseWorkId=coursework_id,
id=rubric_id
).execute()
except HttpError as error:
print(f"An error occurred: {error}")
return error
ルーブリックをエクスポートしてインポートする
ルーブリックは、教師が再利用できるように Google スプレッドシートに手動でエクスポートできます。
コードでルーブリック条件を指定するだけでなく、criteria の代わりにルーブリック本文に sourceSpreadsheetId を指定することで、エクスポートしたシートからルーブリックを作成して更新することもできます。
def create_rubric_from_sheet(service, course_id, coursework_id, sheet_id):
"""Creates an example rubric on a coursework."""
try:
body = {
"sourceSpreadsheetId": sheet_id
}
rubric = service.courses().courseWork().rubrics().create(
courseId=course_id, courseWorkId=coursework_id, body=body
).execute()
print(f"Rubric created with ID {rubric.get('id')}")
return rubric
except HttpError as error:
print(f"An error occurred: {error}")
return error