Это шестое пошаговое руководство из серии руководств по использованию дополнений для класса.
В этом пошаговом руководстве вы модифицируете пример из предыдущего шага, чтобы создать вложение типа оцениваемого задания. Вы также программно передаете оценку обратно в Google Classroom , которая отображается в журнале оценок учителя как черновая оценка.
Данное руководство немного отличается от других в этой серии тем, что в нем представлены два возможных подхода к передаче оценок обратно в Classroom. Оба подхода по-разному влияют на опыт разработчика и пользователя; учитывайте оба при разработке дополнения для Classroom. Для получения дополнительной информации о вариантах реализации ознакомьтесь с нашей страницей руководства «Взаимодействие с вложениями» .
Обратите внимание, что функции выставления оценок в API являются необязательными . Их можно использовать с любым типом прикрепленных файлов .
В ходе этого пошагового руководства вы выполните следующие действия:
- Измените предыдущие запросы на создание вложений к API Classroom, чтобы также установить знаменатель оценки для вложенного файла.
- Программно оценить работу студента и установить числитель оценки в прикрепленном файле.
- Реализуйте два подхода для передачи оценки за работу в Classroom, используя учетные данные учителя, вошедшего в систему, или учетные данные учителя, находящегося вне сети.
После завершения работы оценки отображаются в журнале оценок Classroom после срабатывания механизма обратной передачи. Точный момент, когда это происходит, зависит от используемого подхода к реализации.
В качестве примера повторно используйте задание из предыдущего пошагового руководства, в котором ученику показывается изображение известной достопримечательности и предлагается ввести ее название. Если ученик вводит правильное название, выставьте максимальный балл за прикрепленный файл, в противном случае — ноль баллов.
Разберитесь в функции оценивания с помощью API дополнений для класса.
Ваше дополнение может задавать как числитель, так и знаменатель оценки для вложенного файла. Эти значения задаются с помощью параметров pointsEarned и maxPoints , доступных в API. Карточка вложенного файла в пользовательском интерфейсе Classroom отображает значение maxPoints после его установки.

Рисунок 1. Пользовательский интерфейс создания задания с тремя дополнительными карточками вложений, для которых задано значение maxPoints .
API дополнений Classroom позволяет настраивать параметры и устанавливать баллы, начисляемые за оценки, полученные за вложения . Эти оценки отличаются от оценок за задания . Однако настройки оценок за задания соответствуют настройкам оценок вложения, на карточке которого есть метка « Синхронизация оценок ». Когда вложение с меткой «Синхронизация оценок» устанавливает pointsEarned для работы студента, оно также устанавливает оценку за черновик задания для этого студента.
Обычно первое вложение, добавленное к заданию, устанавливающему maxPoints , получает метку «Синхронизация оценок». Пример использования метки «Синхронизация оценок» показан на рисунке 1 в примере пользовательского интерфейса создания задания. Обратите внимание, что карточка «Вложение 1» имеет метку «Синхронизация оценок», а оценка за задание в красной рамке обновлена до 50 баллов. Также обратите внимание, что хотя на рисунке 1 показаны три карточки вложений, метку «Синхронизация оценок» имеет только одна карточка. Это ключевое ограничение текущей реализации: метку «Синхронизация оценок» может иметь только одно вложение .
Если для нескольких вложений установлено значение maxPoints , удаление вложения с параметром "Синхронизация оценок" не активирует синхронизацию оценок для оставшихся вложений. Добавление другого вложения с установленным значением maxPoints активирует синхронизацию оценок для нового вложения, и максимальная оценка за задание будет скорректирована соответствующим образом. Нет механизма для программного определения того, какое вложение имеет метку "Синхронизация оценок", а также для определения количества вложений для конкретного задания.
Установите максимальное значение для вложения.
В этом разделе описывается установка знаменателя для оценки за вложенную работу, то есть максимально возможного балла, который могут получить все студенты за свои работы. Для этого установите значение maxPoints для вложенной работы.
Для включения функций выставления оценок требуется лишь незначительная модификация существующей реализации. При создании вложения добавьте значение maxPoints в тот же объект AddOnAttachment , который содержит поля studentWorkReviewUri , teacherViewUri и другие поля вложения.
Обратите внимание, что по умолчанию максимальная оценка за новое задание составляет 100. Мы рекомендуем установить значение maxPoints , отличное от 100, чтобы вы могли убедиться в правильности выставления оценок. В качестве примера установите maxPoints равным 50:
Python
Добавьте поле maxPoints при создании объекта attachment , непосредственно перед отправкой запроса CREATE к конечной точке courses.courseWork.addOnAttachments . Вы можете найти его в файле webapp/attachment_routes.py , если следуете предоставленному нами примеру.
attachment = {
# Specifies the route for a teacher user.
"teacherViewUri": {
"uri":
flask.url_for(
"load_activity_attachment",
_scheme='https',
_external=True),
},
# Specifies the route for a student user.
"studentViewUri": {
"uri":
flask.url_for(
"load_activity_attachment",
_scheme='https',
_external=True)
},
# Specifies the route for a teacher user when the attachment is
# loaded in the Classroom grading view.
"studentWorkReviewUri": {
"uri":
flask.url_for(
"view_submission", _scheme='https', _external=True)
},
# Sets the maximum points that a student can earn for this activity.
# This is the denominator in a fractional representation of a grade.
"maxPoints": 50,
# The title of the attachment.
"title": f"Attachment {attachment_count}",
}
Для целей этой демонстрации вы также сохраняете значение maxPoints в локальной базе данных Attachment; это избавляет от необходимости делать дополнительный вызов API позже при оценивании работ студентов. Однако обратите внимание, что преподаватели могут изменять настройки оценок заданий независимо от вашего дополнения. Отправьте GET запрос к конечной точке courses.courseWork , чтобы увидеть значение maxPoints на уровне задания. При этом передайте itemId в поле CourseWork.id .
Теперь обновите модель базы данных, чтобы она также содержала значение maxPoints для вложения. Мы рекомендуем использовать значение maxPoints из ответа CREATE :
Python
Сначала добавьте поле max_points в таблицу Attachment . Вы найдете его в файле webapp/models.py если будете следовать предоставленному нами примеру.
# Database model to represent an attachment.
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))
# The maximum number of points for this activity.
max_points = db.Column(db.Integer)
Вернитесь к запросу CREATE courses.courseWork.addOnAttachments . Сохраните значение maxPoints , возвращенное в ответе.
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,
# Store the maxPoints value returned in the response.
max_points=int(resp.get("maxPoints")))
db.session.add(new_attachment)
db.session.commit()
Теперь для вложения доступна максимальная оценка. Вы можете проверить это поведение: добавьте вложение к новому заданию и убедитесь, что в карточке вложения отображается метка «Синхронизация оценок», а значение «Баллы» задания изменяется.
Установите оценку за выполненную учеником работу в Classroom.
В этом разделе описывается установка числителя для оценки за прикрепленный файл; то есть, балла, набранного отдельным студентом за этот файл. Для этого установите значение pointsEarned для отправленного студентом файла.
Теперь вам предстоит принять важное решение: как ваше дополнение должно отправлять запрос на установку количества pointsEarned ?
Проблема в том, что для установки параметра pointsEarned требуется область действия OAuth для teacher . Не следует предоставлять область действия teacher пользователям-студентам; это может привести к неожиданному поведению при взаимодействии студентов с вашим дополнением, например, к загрузке iframe «Просмотр учителя» вместо iframe «Просмотр студента». Поэтому у вас есть два варианта установки pointsEarned :
- Используя учетные данные учителя, вошедшего в систему.
- Использование сохраненных (в автономном режиме) учетных данных преподавателя.
В следующих разделах обсуждаются компромиссы каждого подхода, прежде чем будет продемонстрирована каждая реализация. Обратите внимание, что приведенные нами примеры демонстрируют оба подхода к передаче оценки в Classroom; см. инструкции для конкретного языка ниже, чтобы узнать, как выбрать подход при запуске приведенных примеров:
Python
Найдите объявление SET_GRADE_WITH_LOGGED_IN_USER_CREDENTIALS в верхней части файла webapp/attachment_routes.py . Установите это значение в True , чтобы передавать оценки, используя учетные данные вошедшего в систему преподавателя. Установите это значение в False , чтобы передавать оценки, используя сохраненные учетные данные, когда студент отправляет задание.
Выставляйте оценки, используя учетные данные вошедшего в систему учителя.
Используйте учетные данные вошедшего в систему пользователя для отправки запроса на установку значения pointsEarned . Это должно показаться довольно интуитивно понятным, поскольку отражает остальную реализацию на данный момент и не требует больших усилий для реализации.
Однако следует учитывать, что преподаватель взаимодействует с работой студента только во фрейме «Обзор студенческой работы». Это имеет ряд важных последствий:
- Оценки в Classroom не отображаются до тех пор, пока учитель не предпримет соответствующие действия в пользовательском интерфейсе Classroom.
- Учителю, возможно, придётся открыть каждую работу ученика, чтобы выставить оценки всем ученикам.
- Между получением оценки системой Classroom и её отображением в пользовательском интерфейсе Classroom существует небольшая задержка. Обычно она составляет от пяти до десяти секунд, но может достигать 30 секунд.
Сочетание этих факторов означает, что учителям, возможно, придется выполнять значительную и трудоемкую ручную работу, чтобы полностью заполнить оценки по классу.
Для реализации этого подхода добавьте один дополнительный вызов API к существующему маршруту проверки студенческих работ.
После получения записей о студенческих работах и прикрепленных файлах оцените работу студента и сохраните полученную оценку. Установите оценку в поле pointsEarned объекта AddOnAttachmentStudentSubmission . Наконец, отправьте PATCH запрос к конечной точке courses.courseWork.addOnAttachments.studentSubmissions , указав в теле запроса экземпляр AddOnAttachmentStudentSubmission . Обратите внимание, что нам также необходимо указать pointsEarned в updateMask в нашем PATCH запросе:
Python
# Look up the student's submission in our database.
student_submission = Submission.query.get(flask.session["submissionId"])
# Look up the attachment in the database.
attachment = Attachment.query.get(student_submission.attachment_id)
grade = 0
# See if the student response matches the stored name.
if student_submission.student_response.lower(
) == attachment.image_caption.lower():
grade = attachment.max_points
# Create an instance of the Classroom service.
classroom_service = ch._credential_handler.get_classroom_service()
# Build an AddOnAttachmentStudentSubmission instance.
add_on_attachment_student_submission = {
# Specifies the student's score for this attachment.
"pointsEarned": grade,
}
# Issue a PATCH request to set the grade numerator for this attachment.
patch_grade_response = classroom_service.courses().courseWork(
).addOnAttachments().studentSubmissions().patch(
courseId=flask.session["courseId"],
itemId=flask.session["itemId"],
attachmentId=flask.session["attachmentId"],
submissionId=flask.session["submissionId"],
# updateMask is a list of fields being modified.
updateMask="pointsEarned",
body=add_on_attachment_student_submission).execute()
Выставлять оценки, используя учетные данные учителя, не подключенного к интернету.
Второй подход к выставлению оценок требует использования сохраненных учетных данных преподавателя, создавшего вложение. Для этого необходимо создать учетные данные, используя токены обновления и доступа ранее авторизованного преподавателя, а затем использовать эти учетные данные для установки значения pointsEarned .
Ключевым преимуществом этого подхода является то, что оценки отображаются без участия учителя в пользовательском интерфейсе Classroom, что позволяет избежать упомянутых выше проблем. В результате конечные пользователи воспринимают процесс выставления оценок как удобный и эффективный. Кроме того, этот подход позволяет выбирать момент отправки оценок, например, когда студенты завершают задание или асинхронно.
Для реализации данного подхода выполните следующие задачи:
- Измените записи в базе данных пользователей, чтобы они хранили токен доступа.
- Измените записи в базе данных "Вложения", чтобы в них хранился идентификатор учителя.
- Получите учетные данные преподавателя и (при необходимости) создайте новый экземпляр службы Classroom.
- Установите оценку для выполненной работы.
В целях данной демонстрации, оценка будет выставлена по завершении студентом задания, то есть в момент отправки формы в режиме просмотра для студентов.
Измените записи в базе данных пользователей, чтобы сохранить токен доступа.
Для выполнения вызовов API требуются два уникальных токена: токен обновления и токен доступа . Если вы следовали инструкциям в этой серии руководств, то в схеме таблицы User уже должен храниться токен обновления. Хранение токена обновления достаточно, если вы выполняете вызовы API только с авторизованным пользователем, поскольку токен доступа получается в рамках процесса аутентификации.
Однако теперь вам нужно совершать вызовы от имени пользователя, отличного от авторизованного, а это значит, что процесс аутентификации недоступен. Поэтому вам необходимо хранить токен доступа вместе с токеном обновления. Обновите схему таблицы User , добавив в нее токен доступа:
Python
В приведенном нами примере это находится в файле webapp/models.py .
# Database model to represent a user.
class User(db.Model):
# The user's identifying information:
id = db.Column(db.String(120), primary_key=True)
display_name = db.Column(db.String(80))
email = db.Column(db.String(120), unique=True)
portrait_url = db.Column(db.Text())
# The user's refresh token, which will be used to obtain an access token.
# Note that refresh tokens will become invalid if:
# - The refresh token has not been used for six months.
# - The user revokes your app's access permissions.
# - The user changes passwords.
# - The user belongs to a Google Cloud organization
# that has session control policies in effect.
refresh_token = db.Column(db.Text())
# An access token for this user.
access_token = db.Column(db.Text())
Затем обновите любой код, который создает или обновляет запись User , чтобы он также сохранял токен доступа:
Python
В приведенном нами примере это находится в файле webapp/credential_handler.py .
def save_credentials_to_storage(self, credentials):
# Issue a request for the user's profile details.
user_info_service = googleapiclient.discovery.build(
serviceName="oauth2", version="v2", credentials=credentials)
user_info = user_info_service.userinfo().get().execute()
flask.session["username"] = user_info.get("name")
flask.session["login_hint"] = user_info.get("id")
# See if we have any stored credentials for this user. If they have used
# the add-on before, we should have received login_hint in the query
# parameters.
existing_user = self.get_credentials_from_storage(user_info.get("id"))
# If we do have stored credentials, update the database.
if existing_user:
if user_info:
existing_user.id = user_info.get("id")
existing_user.display_name = user_info.get("name")
existing_user.email = user_info.get("email")
existing_user.portrait_url = user_info.get("picture")
if credentials and credentials.refresh_token is not None:
existing_user.refresh_token = credentials.refresh_token
# Update the access token.
existing_user.access_token = credentials.token
# If not, this must be a new user, so add a new entry to the database.
else:
new_user = User(
id=user_info.get("id"),
display_name=user_info.get("name"),
email=user_info.get("email"),
portrait_url=user_info.get("picture"),
refresh_token=credentials.refresh_token,
# Store the access token as well.
access_token=credentials.token)
db.session.add(new_user)
db.session.commit()
Измените записи в базе данных вложений, чтобы сохранить идентификатор учителя.
Чтобы выставить оценку за задание, необходимо, как преподаватель курса, установить pointsEarned . Это можно сделать несколькими способами:
- Сохраните локальное сопоставление учетных данных преподавателя с идентификаторами курсов. Однако следует отметить, что один и тот же преподаватель не всегда может быть связан с конкретным курсом.
- Отправляйте
GETзапросы к конечной точкеcoursesClassroom API, чтобы получить информацию о текущем преподавателе (преподавателях). Затем запросите локальные записи пользователей, чтобы найти соответствующие учетные данные преподавателя. - При создании дополнительного вложения сохраните идентификатор преподавателя в локальной базе данных вложений. Затем получите учетные данные преподавателя из
attachmentIdпереданного в iframe представления студента.
В этом примере показан последний вариант, поскольку оценки выставляются по мере выполнения студентом прикрепленного задания.
Добавьте поле "Идентификатор учителя" в таблицу Attachment " вашей базы данных:
Python
В приведенном нами примере это находится в файле webapp/models.py .
# Database model to represent an attachment.
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))
# The maximum number of points for this activity.
max_points = db.Column(db.Integer)
# The ID of the teacher that created the attachment.
teacher_id = db.Column(db.String(120))
Затем обновите любой код, который создает или обновляет запись Attachment , чтобы он также сохранял идентификатор создателя:
Python
В приведенном нами примере это находится в методе create_attachments в файле webapp/attachment_routes.py .
# Store the attachment 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,
max_points=int(resp.get("maxPoints")),
teacher_id=flask.session["login_hint"])
db.session.add(new_attachment)
db.session.commit()
Получите данные об образовании и квалификации учителя.
Найдите маршрут, который обслуживает iframe с представлением студента. Сразу после сохранения ответа студента в локальной базе данных получите учетные данные преподавателя из локального хранилища. Это должно быть несложно, учитывая подготовку, выполненную на предыдущих двух шагах. Вы также можете использовать их для создания нового экземпляра сервиса Classroom для пользователя-преподавателя:
Python
В приведенном нами примере это находится в методе load_activity_attachment в файле webapp/attachment_routes.py .
# Create an instance of the Classroom service using the tokens for the
# teacher that created the attachment.
# We're assuming that there are already credentials in the session, which
# should be true given that we are adding this within the Student View
# route; we must have had valid credentials for the student to reach this
# point. The student credentials will be valid to construct a Classroom
# service for another user except for the tokens.
if not flask.session.get("credentials"):
raise ValueError(
"No credentials found in session for the requested user.")
# Make a copy of the student credentials so we don't modify the original.
teacher_credentials_dict = deepcopy(flask.session.get("credentials"))
# Retrieve the requested user's stored record.
teacher_record = User.query.get(attachment.teacher_id)
# Apply the user's tokens to the copied credentials.
teacher_credentials_dict["refresh_token"] = teacher_record.refresh_token
teacher_credentials_dict["token"] = teacher_record.access_token
# Construct a temporary credentials object.
teacher_credentials = google.oauth2.credentials.Credentials(
**teacher_credentials_dict)
# Refresh the credentials if necessary; we don't know when this teacher last
# made a call.
if teacher_credentials.expired:
teacher_credentials.refresh(Request())
# Request the Classroom service for the specified user.
teacher_classroom_service = googleapiclient.discovery.build(
serviceName=CLASSROOM_API_SERVICE_NAME,
version=CLASSROOM_API_VERSION,
credentials=teacher_credentials)
Установить оценку за выполненную работу
Дальнейшая процедура идентична той, что используется при использовании учетных данных вошедшего в систему преподавателя . Однако обратите внимание, что вызов следует совершать, используя учетные данные преподавателя, полученные на предыдущем шаге:
Python
# Issue a PATCH request as the teacher to set the grade numerator for this
# attachment.
patch_grade_response = teacher_classroom_service.courses().courseWork(
).addOnAttachments().studentSubmissions().patch(
courseId=flask.session["courseId"],
itemId=flask.session["itemId"],
attachmentId=flask.session["attachmentId"],
submissionId=flask.session["submissionId"],
# updateMask is a list of fields being modified.
updateMask="pointsEarned",
body=add_on_attachment_student_submission).execute()
Протестируйте дополнение
Аналогично предыдущему руководству, создайте задание с прикрепленным элементом типа «деятельность» от имени преподавателя, отправьте ответ от имени студента, а затем откройте его работу в iframe «Проверка студенческой работы». Вы сможете увидеть оценку в разное время в зависимости от выбранного вами подхода:
- Если вы решили выставить оценку после завершения студентом задания, вы должны увидеть его предварительную оценку в пользовательском интерфейсе еще до открытия окна «Обзор студенческой работы». Вы также можете увидеть ее в списке студентов при открытии задания и в поле «Оценка» рядом с окном «Обзор студенческой работы».
- Если вы выбрали отправку оценки при открытии учителем iframe «Проверка работы ученика», оценка должна появиться в поле «Оценка» вскоре после загрузки iframe. Как упоминалось выше , это может занять до 30 секунд. После этого оценка для конкретного ученика также должна отобразиться в других окнах журнала оценок Classroom.
Убедитесь, что для студента отображается правильный балл.
Поздравляем! Вы готовы перейти к следующему шагу: созданию вложений вне Google Classroom .