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

Это шестое прохождение в серии прохождений надстроек Класса.

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

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

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

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

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

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

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

Познакомьтесь с функцией оценки API надстроек Класса.

В вашем дополнении можно установить как числитель, так и знаменатель оценки для вложения. Они соответственно устанавливаются с использованием pointsEarned и maxPoints в API. На карточке вложения в пользовательском интерфейсе Класса отображается значение 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()

Теперь вложение имеет максимальную оценку. Теперь вы сможете протестировать это поведение; добавьте вложение к новому заданию и обратите внимание, что на карточке вложения отображается метка «Синхронизация оценок», а значение «Баллы» задания изменяется.

Как установить оценку за работу учащегося в Классе

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

Теперь вам нужно принять важное решение: как ваше дополнение должно выдавать запрос на установку pointsEarned ?

Проблема в том, что для установки pointsEarned требуется область OAuth teacher . Вы не должны предоставлять права teacher пользователям-учащимся; это может привести к неожиданному поведению, когда учащиеся взаимодействуют с вашим дополнением, например к загрузке iframe «Представление учителя» вместо iframe «Представление ученика». Таким образом, у вас есть два варианта установки pointsEarned :

  • Использование учетных данных вошедшего в систему преподавателя.
  • Использование сохраненных (офлайн) учетных данных учителя.

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

Питон

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

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

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

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

  • Оценки в Классе не отображаются, пока учитель не выполнит действия в пользовательском интерфейсе Класса.
  • Учителю, возможно, придется открыть каждое задание учащегося, чтобы заполнить все оценки учащихся.
  • Между получением оценки в Классе и ее появлением в пользовательском интерфейсе Класса проходит небольшая задержка. Задержка обычно составляет от пяти до десяти секунд, но может достигать и 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. Получите учетные данные учителя и (необязательно) создайте новый экземпляр службы Класса.
  4. Установите оценку отправленного материала.

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

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

Для выполнения вызовов 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 к конечной точке courses API Classroom, чтобы получить текущих преподавателей. Затем запросите локальные записи пользователей, чтобы найти соответствующие учетные данные учителя.
  • При создании дополнительного вложения сохраните идентификатор учителя в локальной базе данных вложений. Затем извлеките учетные данные учителя из 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 Student View. Сразу после сохранения ответа учащегося в локальной базе данных извлеките учетные данные учителя из локального хранилища. Это должно быть несложно, учитывая подготовку на предыдущих двух шагах. Вы также можете использовать их для создания нового экземпляра службы 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,
    discoveryServiceUrl=f"https://classroom.googleapis.com/$discovery/rest?labels=ADD_ONS_ALPHA&key={GOOGLE_API_KEY}",
    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 секунд. После этого оценка конкретного учащегося также должна появиться в других журналах оценок Класса.

Убедитесь, что учащийся получил правильный балл.

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