Оценки вложений и возврат оценок

Это шестое пошаговое руководство из серии пошаговых руководств по дополнениям Classroom.

В этом пошаговом руководстве вы модифицируете пример из предыдущего шага, чтобы создать вложение типа оцениваемого задания. Вы также программно передадите оценку обратно в Google Classroom , которая отобразится в журнале оценок учителя в качестве черновой оценки.

Это пошаговое руководство немного отличается от других в серии тем, что представлены два возможных подхода к передаче оценок обратно в Classroom. Оба подхода по-разному влияют на взаимодействие с разработчиками и пользователями; учитывайте оба при разработке надстройки для Classroom. Подробнее о вариантах реализации читайте на странице руководства «Взаимодействие с вложениями» .

Обратите внимание, что функции оценки в API являются опциональными . Их можно использовать с любым вложением типа активности .

В ходе этого пошагового руководства вы выполните следующее:

  • Измените предыдущие запросы на создание вложений к API Classroom, чтобы также задать знаменатель оценки вложения.
  • Программно оцените работу учащегося и установите числитель оценки вложения.
  • Реализуйте два подхода для передачи оценки представленной работы в Classroom с использованием зарегистрированных или автономных учетных данных учителя.

По завершении оценки появляются в журнале оценок Classroom после срабатывания функции обратного прохода. Точный момент времени зависит от подхода к реализации.

Для этого примера используйте упражнение из предыдущего пошагового руководства, в котором учащемуся показывают изображение известной достопримечательности и предлагают ввести её название. Выставьте за вложение максимальную оценку, если учащийся введёт правильное название, и ноль в противном случае.

Ознакомьтесь с функцией оценки API дополнений Classroom

Ваше дополнение может задать как числитель, так и знаменатель оценки для вложения. Они задаются с помощью значений pointsEarned и maxPoints в API соответственно. Карточка вложения в интерфейсе Classroom отображает значение maxPoints после его установки.

Пример нескольких вложений с максимальным количеством баллов в одном задании

Рисунок 1. Пользовательский интерфейс создания задания с тремя дополнительными карточками вложений, для которых установлено maxPoints .

API дополнений для Класса позволяет настраивать параметры и устанавливать баллы, начисляемые за оценки за вложения . Они не совпадают с оценками за задание . Однако настройки оценок за задание соответствуют настройкам оценок вложения, на карточке которого есть метка « Синхронизация оценок ». Когда вложение «Синхронизация оценок» устанавливает pointsEarned начисленных за работу учащегося, оно также устанавливает черновую оценку учащегося за задание.

Обычно первое вложение, добавленное к заданию, устанавливающему maxPoints получает метку «Синхронизация оценок». См. пример интерфейса создания задания на рисунке 1, где показан пример метки «Синхронизация оценок». Обратите внимание, что карточка «Вложение 1» имеет метку «Синхронизация оценок», а оценка за задание в красном поле обновлена до 50 баллов. Также обратите внимание, что, хотя на рисунке 1 показаны три карточки вложений, метка «Синхронизация оценок» есть только у одной. Это ключевое ограничение текущей реализации: метку «Синхронизация оценок» может иметь только одно вложение .

Если есть несколько вложений с меткой maxPoints , удаление вложения с меткой «Синхронизация оценок» не включает синхронизацию оценок для оставшихся вложений. Добавление другого вложения с меткой maxPoints включает синхронизацию оценок для нового вложения, и максимальная оценка за задание корректируется. Программного механизма для определения вложения с меткой «Синхронизация оценок» или количества вложений для конкретного задания не предусмотрено.

Установить максимальную оценку вложения

В этом разделе описывается настройка знаменателя для оценки приложения, то есть максимально возможного балла, который все учащиеся могут получить за свои работы. Для этого задайте значение maxPoints для приложения.

Для включения функций оценки требуется лишь незначительное изменение существующей реализации. При создании вложения добавьте значение maxPoints в тот же объект AddOnAttachment , который содержит поля studentWorkReviewUri , teacherViewUri и другие поля вложения.

Обратите внимание, что максимальный балл по умолчанию для нового задания — 100. Мы рекомендуем установить для maxPoints значение, отличное от 100, чтобы вы могли убедиться в корректности выставления оценок. Для наглядности установите для maxPoints значение 50:

Питон

Добавьте поле 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 в локальной базе данных вложений; это избавит от необходимости делать дополнительный вызов API позже при оценке работ студентов. Однако учтите, что преподаватели могут изменять настройки оценок за задание независимо от вашего дополнения. Отправьте GET запрос к конечной точке courses.courseWork , чтобы узнать значение maxPoints на уровне задания. При этом передайте itemId в поле CourseWork.id .

Теперь обновите модель базы данных, чтобы она также содержала значение maxPoints для вложения. Мы рекомендуем использовать значение maxPoints из ответа CREATE :

Питон

Сначала добавьте поле 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; см. инструкции для конкретного языка ниже, чтобы узнать, как выбрать подход при запуске представленных примеров:

Питон

Найдите объявление SET_GRADE_WITH_LOGGED_IN_USER_CREDENTIALS в начале файла webapp/attachment_routes.py . Установите это значение в True , чтобы оценки передавались с использованием учётных данных вошедшего в систему преподавателя. Установите это значение в False , чтобы оценки передавались с использованием сохранённых учётных данных при отправке задания учащимся.

Выставляйте оценки, используя учетные данные вошедшего в систему учителя.

Используйте учётные данные вошедшего в систему пользователя для отправки запроса на установку pointsEarned . Это должно показаться довольно интуитивно понятным, поскольку отражает остальную часть реализации до сих пор и не требует больших усилий для реализации.

Однако учтите, что преподаватель взаимодействует только с работой учащегося в iframe «Обзор студенческих работ». Это имеет ряд важных последствий:

  • Оценки в Classroom не выставляются до тех пор, пока учитель не предпримет никаких действий в пользовательском интерфейсе Classroom.
  • Учителю, возможно, придется открыть все работы учащихся, чтобы выставить им оценки.
  • Между получением оценки в Classroom и её появлением в интерфейсе Classroom существует небольшая задержка. Обычно она составляет от пяти до десяти секунд, но может достигать 30 секунд.

Сочетание этих факторов означает, что учителям, возможно, придется выполнять значительный объем трудоемкой ручной работы, чтобы полностью заполнить оценки класса.

Чтобы реализовать этот подход, добавьте один дополнительный вызов API к существующему маршруту проверки студенческих работ.

После получения записей о сдаче работы студента и прикрепленных файлах оцените работу студента и сохраните полученную оценку. Установите оценку в поле pointsEarned объекта AddOnAttachmentStudentSubmission . Затем выполните PATCH запрос к конечной точке courses.courseWork.addOnAttachments.studentSubmissions , указав экземпляр AddOnAttachmentStudentSubmission в теле запроса. Обратите внимание, что нам также необходимо указать pointsEarned в updateMask нашего PATCH запроса:

Питон

# 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 .

Важнейшим преимуществом такого подхода является то, что оценки выставляются без необходимости вмешательства учителя в интерфейс класса, что позволяет избежать упомянутых выше проблем. В результате конечные пользователи воспринимают процесс оценивания как удобный и эффективный. Кроме того, этот подход позволяет выбрать момент, когда выставлять оценки, например, по завершении задания учащимися или асинхронно.

Для реализации этого подхода выполните следующие задачи:

  1. Измените записи базы данных пользователей для хранения токена доступа.
  2. Измените записи базы данных вложений для сохранения идентификатора учителя.
  3. Получите учетные данные учителя и (при необходимости) создайте новый экземпляр службы Classroom.
  4. Установите оценку представленной работы.

Для целей данной демонстрации установите оценку, когда ученик завершает действие, то есть когда ученик отправляет форму в маршруте просмотра учеников.

Изменить записи базы данных пользователей для хранения токена доступа

Для выполнения API-вызовов требуются два уникальных токена: токен обновления и токен доступа . Если вы до сих пор следовали пошаговым инструкциям, то в схеме таблицы User уже должен храниться токен обновления. Хранения токена обновления достаточно, если вы выполняете API-вызовы только от имени вошедшего в систему пользователя, поскольку вы получаете токен доступа в процессе аутентификации.

Однако теперь вам нужно совершать вызовы от имени пользователя, отличного от вошедшего в систему, что означает, что процесс аутентификации недоступен. Поэтому вам необходимо хранить токен доступа вместе с токеном обновления. Обновите схему таблицы User , включив в неё токен доступа:

Питон

В нашем примере это файл 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 , чтобы он также сохранял токен доступа:

Питон

В нашем примере это файл 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 -запросы к конечной точке Classroom API courses , чтобы получить данные о текущих преподавателях. Затем запросите локальные записи пользователей, чтобы найти соответствующие учётные данные преподавателей.
  • При создании вложения-надстройки сохраните идентификатор преподавателя в локальной базе данных вложений. Затем извлеките учётные данные преподавателя из attachmentId , переданного в iframe представления ученика.

В этом примере показан последний вариант, поскольку вы выставляете оценки, когда ученик выполняет прикрепленный файл задания.

Добавьте поле идентификатора преподавателя в таблицу 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)

    # The ID of the teacher that created the attachment.
    teacher_id = db.Column(db.String(120))

Затем обновите любой код, который создает или обновляет запись Attachment , чтобы он также сохранял идентификатор создателя:

Питон

В нашем примере это метод 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 для пользователя-учителя:

Питон

В нашем примере это метод 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)

Установить оценку отправленной работы

Дальнейшая процедура идентична процедуре с использованием учётных данных вошедшего в систему учителя . Однако обратите внимание, что для вызова необходимо использовать учётные данные учителя, полученные на предыдущем шаге:

Питон

# 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 «Обзор работы учащегося».
  • Если вы решили вернуть оценку, когда учитель открывает iframe для проверки работ учащихся, оценка должна появиться в поле «Оценка» вскоре после загрузки iframe. Как упоминалось выше , это может занять до 30 секунд. После этого оценка конкретного учащегося должна появиться и в других видах журнала оценок в Classroom.

Убедитесь, что для учащегося отображается правильная оценка.

Поздравляем! Вы готовы перейти к следующему шагу: созданию вложений вне Google Classroom .